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