video-format-utils.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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. 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. const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
  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. userHistory: userHistory ? {
  79. currentTime: userHistory.currentTime
  80. } : undefined
  81. }
  82. if (options) {
  83. if (options.additionalAttributes.state === true) {
  84. videoObject.state = {
  85. id: video.state,
  86. label: VideoModel.getStateLabel(video.state)
  87. }
  88. }
  89. if (options.additionalAttributes.waitTranscoding === true) {
  90. videoObject.waitTranscoding = video.waitTranscoding
  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. if (options.additionalAttributes.blacklistInfo === true) {
  99. videoObject.blacklisted = !!video.VideoBlacklist
  100. videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null
  101. }
  102. }
  103. return videoObject
  104. }
  105. function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails {
  106. const formattedJson = video.toFormattedJSON({
  107. additionalAttributes: {
  108. scheduledUpdate: true,
  109. blacklistInfo: true
  110. }
  111. })
  112. const tags = video.Tags ? video.Tags.map(t => t.name) : []
  113. const detailsJson = {
  114. support: video.support,
  115. descriptionPath: video.getDescriptionAPIPath(),
  116. channel: video.VideoChannel.toFormattedJSON(),
  117. account: video.VideoChannel.Account.toFormattedJSON(),
  118. tags,
  119. commentsEnabled: video.commentsEnabled,
  120. waitTranscoding: video.waitTranscoding,
  121. state: {
  122. id: video.state,
  123. label: VideoModel.getStateLabel(video.state)
  124. },
  125. files: []
  126. }
  127. // Format and sort video files
  128. detailsJson.files = videoFilesModelToFormattedJSON(video, video.VideoFiles)
  129. return Object.assign(formattedJson, detailsJson)
  130. }
  131. function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] {
  132. const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
  133. return videoFiles
  134. .map(videoFile => {
  135. let resolutionLabel = videoFile.resolution + 'p'
  136. return {
  137. resolution: {
  138. id: videoFile.resolution,
  139. label: resolutionLabel
  140. },
  141. magnetUri: video.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs),
  142. size: videoFile.size,
  143. fps: videoFile.fps,
  144. torrentUrl: video.getTorrentUrl(videoFile, baseUrlHttp),
  145. torrentDownloadUrl: video.getTorrentDownloadUrl(videoFile, baseUrlHttp),
  146. fileUrl: video.getVideoFileUrl(videoFile, baseUrlHttp),
  147. fileDownloadUrl: video.getVideoFileDownloadUrl(videoFile, baseUrlHttp)
  148. } as VideoFile
  149. })
  150. .sort((a, b) => {
  151. if (a.resolution.id < b.resolution.id) return 1
  152. if (a.resolution.id === b.resolution.id) return 0
  153. return -1
  154. })
  155. }
  156. function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
  157. const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
  158. if (!video.Tags) video.Tags = []
  159. const tag = video.Tags.map(t => ({
  160. type: 'Hashtag' as 'Hashtag',
  161. name: t.name
  162. }))
  163. let language
  164. if (video.language) {
  165. language = {
  166. identifier: video.language,
  167. name: VideoModel.getLanguageLabel(video.language)
  168. }
  169. }
  170. let category
  171. if (video.category) {
  172. category = {
  173. identifier: video.category + '',
  174. name: VideoModel.getCategoryLabel(video.category)
  175. }
  176. }
  177. let licence
  178. if (video.licence) {
  179. licence = {
  180. identifier: video.licence + '',
  181. name: VideoModel.getLicenceLabel(video.licence)
  182. }
  183. }
  184. const url: ActivityUrlObject[] = []
  185. for (const file of video.VideoFiles) {
  186. url.push({
  187. type: 'Link',
  188. mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any,
  189. mediaType: VIDEO_EXT_MIMETYPE[ file.extname ] as any,
  190. href: video.getVideoFileUrl(file, baseUrlHttp),
  191. height: file.resolution,
  192. size: file.size,
  193. fps: file.fps
  194. })
  195. url.push({
  196. type: 'Link',
  197. mimeType: 'application/x-bittorrent' as 'application/x-bittorrent',
  198. mediaType: 'application/x-bittorrent' as 'application/x-bittorrent',
  199. href: video.getTorrentUrl(file, baseUrlHttp),
  200. height: file.resolution
  201. })
  202. url.push({
  203. type: 'Link',
  204. mimeType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet',
  205. mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet',
  206. href: video.generateMagnetUri(file, baseUrlHttp, baseUrlWs),
  207. height: file.resolution
  208. })
  209. }
  210. // Add video url too
  211. url.push({
  212. type: 'Link',
  213. mimeType: 'text/html',
  214. mediaType: 'text/html',
  215. href: CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
  216. })
  217. const subtitleLanguage = []
  218. for (const caption of video.VideoCaptions) {
  219. subtitleLanguage.push({
  220. identifier: caption.language,
  221. name: VideoCaptionModel.getLanguageLabel(caption.language)
  222. })
  223. }
  224. return {
  225. type: 'Video' as 'Video',
  226. id: video.url,
  227. name: video.name,
  228. duration: getActivityStreamDuration(video.duration),
  229. uuid: video.uuid,
  230. tag,
  231. category,
  232. licence,
  233. language,
  234. views: video.views,
  235. sensitive: video.nsfw,
  236. waitTranscoding: video.waitTranscoding,
  237. state: video.state,
  238. commentsEnabled: video.commentsEnabled,
  239. published: video.publishedAt.toISOString(),
  240. updated: video.updatedAt.toISOString(),
  241. mediaType: 'text/markdown',
  242. content: video.getTruncatedDescription(),
  243. support: video.support,
  244. subtitleLanguage,
  245. icon: {
  246. type: 'Image',
  247. url: video.getThumbnailUrl(baseUrlHttp),
  248. mediaType: 'image/jpeg',
  249. width: THUMBNAILS_SIZE.width,
  250. height: THUMBNAILS_SIZE.height
  251. },
  252. url,
  253. likes: getVideoLikesActivityPubUrl(video),
  254. dislikes: getVideoDislikesActivityPubUrl(video),
  255. shares: getVideoSharesActivityPubUrl(video),
  256. comments: getVideoCommentsActivityPubUrl(video),
  257. attributedTo: [
  258. {
  259. type: 'Person',
  260. id: video.VideoChannel.Account.Actor.url
  261. },
  262. {
  263. type: 'Group',
  264. id: video.VideoChannel.Actor.url
  265. }
  266. ]
  267. }
  268. }
  269. function getActivityStreamDuration (duration: number) {
  270. // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
  271. return 'PT' + duration + 'S'
  272. }
  273. export {
  274. videoModelToFormattedJSON,
  275. videoModelToFormattedDetailsJSON,
  276. videoFilesModelToFormattedJSON,
  277. videoModelToActivityPubObject,
  278. getActivityStreamDuration
  279. }