Add downloadingEnabled property to video model
[oweals/peertube.git] / server / models / video / video-format-utils.ts
1 import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
2 import { VideoModel } from './video'
3 import { VideoFileModel } from './video-file'
4 import { ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects'
5 import { CONFIG, THUMBNAILS_SIZE, VIDEO_EXT_MIMETYPE } from '../../initializers'
6 import { VideoCaptionModel } from './video-caption'
7 import {
8   getVideoCommentsActivityPubUrl,
9   getVideoDislikesActivityPubUrl,
10   getVideoLikesActivityPubUrl,
11   getVideoSharesActivityPubUrl
12 } from '../../lib/activitypub'
13 import { isArray } from '../../helpers/custom-validators/misc'
14
15 export type VideoFormattingJSONOptions = {
16   completeDescription?: boolean
17   additionalAttributes: {
18     state?: boolean,
19     waitTranscoding?: boolean,
20     scheduledUpdate?: boolean,
21     blacklistInfo?: boolean
22   }
23 }
24 function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video {
25   const formattedAccount = video.VideoChannel.Account.toFormattedJSON()
26   const formattedVideoChannel = video.VideoChannel.toFormattedJSON()
27
28   const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
29
30   const videoObject: Video = {
31     id: video.id,
32     uuid: video.uuid,
33     name: video.name,
34     category: {
35       id: video.category,
36       label: VideoModel.getCategoryLabel(video.category)
37     },
38     licence: {
39       id: video.licence,
40       label: VideoModel.getLicenceLabel(video.licence)
41     },
42     language: {
43       id: video.language,
44       label: VideoModel.getLanguageLabel(video.language)
45     },
46     privacy: {
47       id: video.privacy,
48       label: VideoModel.getPrivacyLabel(video.privacy)
49     },
50     nsfw: video.nsfw,
51     description: options && options.completeDescription === true ? video.description : video.getTruncatedDescription(),
52     isLocal: video.isOwned(),
53     duration: video.duration,
54     views: video.views,
55     likes: video.likes,
56     dislikes: video.dislikes,
57     thumbnailPath: video.getThumbnailStaticPath(),
58     previewPath: video.getPreviewStaticPath(),
59     embedPath: video.getEmbedStaticPath(),
60     createdAt: video.createdAt,
61     updatedAt: video.updatedAt,
62     publishedAt: video.publishedAt,
63     account: {
64       id: formattedAccount.id,
65       uuid: formattedAccount.uuid,
66       name: formattedAccount.name,
67       displayName: formattedAccount.displayName,
68       url: formattedAccount.url,
69       host: formattedAccount.host,
70       avatar: formattedAccount.avatar
71     },
72     channel: {
73       id: formattedVideoChannel.id,
74       uuid: formattedVideoChannel.uuid,
75       name: formattedVideoChannel.name,
76       displayName: formattedVideoChannel.displayName,
77       url: formattedVideoChannel.url,
78       host: formattedVideoChannel.host,
79       avatar: formattedVideoChannel.avatar
80     },
81
82     userHistory: userHistory ? {
83       currentTime: userHistory.currentTime
84     } : undefined
85   }
86
87   if (options) {
88     if (options.additionalAttributes.state === true) {
89       videoObject.state = {
90         id: video.state,
91         label: VideoModel.getStateLabel(video.state)
92       }
93     }
94
95     if (options.additionalAttributes.waitTranscoding === true) {
96       videoObject.waitTranscoding = video.waitTranscoding
97     }
98
99     if (options.additionalAttributes.scheduledUpdate === true && video.ScheduleVideoUpdate) {
100       videoObject.scheduledUpdate = {
101         updateAt: video.ScheduleVideoUpdate.updateAt,
102         privacy: video.ScheduleVideoUpdate.privacy || undefined
103       }
104     }
105
106     if (options.additionalAttributes.blacklistInfo === true) {
107       videoObject.blacklisted = !!video.VideoBlacklist
108       videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null
109     }
110   }
111
112   return videoObject
113 }
114
115 function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails {
116   const formattedJson = video.toFormattedJSON({
117     additionalAttributes: {
118       scheduledUpdate: true,
119       blacklistInfo: true
120     }
121   })
122
123   const tags = video.Tags ? video.Tags.map(t => t.name) : []
124   const detailsJson = {
125     support: video.support,
126     descriptionPath: video.getDescriptionAPIPath(),
127     channel: video.VideoChannel.toFormattedJSON(),
128     account: video.VideoChannel.Account.toFormattedJSON(),
129     tags,
130     commentsEnabled: video.commentsEnabled,
131     downloadingEnabled: video.downloadingEnabled,
132     waitTranscoding: video.waitTranscoding,
133     state: {
134       id: video.state,
135       label: VideoModel.getStateLabel(video.state)
136     },
137     files: []
138   }
139
140   // Format and sort video files
141   detailsJson.files = videoFilesModelToFormattedJSON(video, video.VideoFiles)
142
143   return Object.assign(formattedJson, detailsJson)
144 }
145
146 function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] {
147   const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
148
149   return videoFiles
150     .map(videoFile => {
151       let resolutionLabel = videoFile.resolution + 'p'
152
153       return {
154         resolution: {
155           id: videoFile.resolution,
156           label: resolutionLabel
157         },
158         magnetUri: video.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs),
159         size: videoFile.size,
160         fps: videoFile.fps,
161         torrentUrl: video.getTorrentUrl(videoFile, baseUrlHttp),
162         torrentDownloadUrl: video.getTorrentDownloadUrl(videoFile, baseUrlHttp),
163         fileUrl: video.getVideoFileUrl(videoFile, baseUrlHttp),
164         fileDownloadUrl: video.getVideoFileDownloadUrl(videoFile, baseUrlHttp)
165       } as VideoFile
166     })
167     .sort((a, b) => {
168       if (a.resolution.id < b.resolution.id) return 1
169       if (a.resolution.id === b.resolution.id) return 0
170       return -1
171     })
172 }
173
174 function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
175   const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
176   if (!video.Tags) video.Tags = []
177
178   const tag = video.Tags.map(t => ({
179     type: 'Hashtag' as 'Hashtag',
180     name: t.name
181   }))
182
183   let language
184   if (video.language) {
185     language = {
186       identifier: video.language,
187       name: VideoModel.getLanguageLabel(video.language)
188     }
189   }
190
191   let category
192   if (video.category) {
193     category = {
194       identifier: video.category + '',
195       name: VideoModel.getCategoryLabel(video.category)
196     }
197   }
198
199   let licence
200   if (video.licence) {
201     licence = {
202       identifier: video.licence + '',
203       name: VideoModel.getLicenceLabel(video.licence)
204     }
205   }
206
207   const url: ActivityUrlObject[] = []
208   for (const file of video.VideoFiles) {
209     url.push({
210       type: 'Link',
211       mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any,
212       href: video.getVideoFileUrl(file, baseUrlHttp),
213       height: file.resolution,
214       size: file.size,
215       fps: file.fps
216     })
217
218     url.push({
219       type: 'Link',
220       mimeType: 'application/x-bittorrent' as 'application/x-bittorrent',
221       href: video.getTorrentUrl(file, baseUrlHttp),
222       height: file.resolution
223     })
224
225     url.push({
226       type: 'Link',
227       mimeType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet',
228       href: video.generateMagnetUri(file, baseUrlHttp, baseUrlWs),
229       height: file.resolution
230     })
231   }
232
233   // Add video url too
234   url.push({
235     type: 'Link',
236     mimeType: 'text/html',
237     href: CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
238   })
239
240   const subtitleLanguage = []
241   for (const caption of video.VideoCaptions) {
242     subtitleLanguage.push({
243       identifier: caption.language,
244       name: VideoCaptionModel.getLanguageLabel(caption.language)
245     })
246   }
247
248   return {
249     type: 'Video' as 'Video',
250     id: video.url,
251     name: video.name,
252     duration: getActivityStreamDuration(video.duration),
253     uuid: video.uuid,
254     tag,
255     category,
256     licence,
257     language,
258     views: video.views,
259     sensitive: video.nsfw,
260     waitTranscoding: video.waitTranscoding,
261     state: video.state,
262     commentsEnabled: video.commentsEnabled,
263     downloadingEnabled: video.downloadingEnabled,
264     published: video.publishedAt.toISOString(),
265     updated: video.updatedAt.toISOString(),
266     mediaType: 'text/markdown',
267     content: video.getTruncatedDescription(),
268     support: video.support,
269     subtitleLanguage,
270     icon: {
271       type: 'Image',
272       url: video.getThumbnailUrl(baseUrlHttp),
273       mediaType: 'image/jpeg',
274       width: THUMBNAILS_SIZE.width,
275       height: THUMBNAILS_SIZE.height
276     },
277     url,
278     likes: getVideoLikesActivityPubUrl(video),
279     dislikes: getVideoDislikesActivityPubUrl(video),
280     shares: getVideoSharesActivityPubUrl(video),
281     comments: getVideoCommentsActivityPubUrl(video),
282     attributedTo: [
283       {
284         type: 'Person',
285         id: video.VideoChannel.Account.Actor.url
286       },
287       {
288         type: 'Group',
289         id: video.VideoChannel.Actor.url
290       }
291     ]
292   }
293 }
294
295 function getActivityStreamDuration (duration: number) {
296   // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
297   return 'PT' + duration + 'S'
298 }
299
300 export {
301   videoModelToFormattedJSON,
302   videoModelToFormattedDetailsJSON,
303   videoFilesModelToFormattedJSON,
304   videoModelToActivityPubObject,
305   getActivityStreamDuration
306 }