X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo-format-utils.ts;h=551cb2842aa6883ae69f02506c802c843b63e9ec;hb=1c5e49e75284100b7b1fc8b4e73c8ba53fe22e89;hp=d71a3a5dbee34b3e5a1c8719e646b3f3b3827bf0;hpb=7557704eec3995092136b87154a1fbe644bc0fec;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index d71a3a5db..551cb2842 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts @@ -1,29 +1,28 @@ +import { generateMagnetUri } from '@server/helpers/webtorrent' +import { getLocalVideoFileMetadataUrl } from '@server/lib/video-paths' +import { VideoFile } from '@shared/models/videos/video-file.model' +import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../shared/models/activitypub/objects' import { Video, VideoDetails } from '../../../shared/models/videos' -import { VideoModel } from './video' -import { ActivityTagObject, ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects' +import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' +import { isArray } from '../../helpers/custom-validators/misc' import { MIMETYPES, WEBSERVER } from '../../initializers/constants' -import { VideoCaptionModel } from './video-caption' import { - getVideoCommentsActivityPubUrl, - getVideoDislikesActivityPubUrl, - getVideoLikesActivityPubUrl, - getVideoSharesActivityPubUrl + getLocalVideoCommentsActivityPubUrl, + getLocalVideoDislikesActivityPubUrl, + getLocalVideoLikesActivityPubUrl, + getLocalVideoSharesActivityPubUrl } from '../../lib/activitypub/url' -import { isArray } from '../../helpers/custom-validators/misc' -import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' import { MStreamingPlaylistRedundanciesOpt, - MStreamingPlaylistVideo, MVideo, MVideoAP, MVideoFile, MVideoFormattable, MVideoFormattableDetails -} from '../../typings/models' -import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file' -import { VideoFile } from '@shared/models/videos/video-file.model' -import { generateMagnetUri } from '@server/helpers/webtorrent' -import { extractVideo } from '@server/helpers/video' +} from '../../types/models' +import { MVideoFileRedundanciesOpt } from '../../types/models/video/video-file' +import { VideoModel } from './video' +import { VideoCaptionModel } from './video-caption' export type VideoFormattingJSONOptions = { completeDescription?: boolean @@ -59,7 +58,11 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor label: VideoModel.getPrivacyLabel(video.privacy) }, nsfw: video.nsfw, - description: options && options.completeDescription === true ? video.description : video.getTruncatedDescription(), + + description: options && options.completeDescription === true + ? video.description + : video.getTruncatedDescription(), + isLocal: video.isOwned(), duration: video.duration, views: video.views, @@ -73,12 +76,17 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor publishedAt: video.publishedAt, originallyPublishedAt: video.originallyPublishedAt, + isLive: video.isLive, + account: video.VideoChannel.Account.toFormattedSummaryJSON(), channel: video.VideoChannel.toFormattedSummaryJSON(), - userHistory: userHistory ? { - currentTime: userHistory.currentTime - } : undefined + userHistory: userHistory + ? { currentTime: userHistory.currentTime } + : undefined, + + // Can be added by external plugins + pluginData: (video as any).pluginData } if (options) { @@ -117,8 +125,6 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid } }) - const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() - const tags = video.Tags ? video.Tags.map(t => t.name) : [] const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) @@ -137,32 +143,31 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid label: VideoModel.getStateLabel(video.state) }, - trackerUrls: video.getTrackerUrls(baseUrlHttp, baseUrlWs), + trackerUrls: video.getTrackerUrls(), files: [], streamingPlaylists } // Format and sort video files - detailsJson.files = videoFilesModelToFormattedJSON(video, baseUrlHttp, baseUrlWs, video.VideoFiles) + detailsJson.files = videoFilesModelToFormattedJSON(video, video.VideoFiles) return Object.assign(formattedJson, detailsJson) } -function streamingPlaylistsModelToFormattedJSON (video: MVideo, playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] { +function streamingPlaylistsModelToFormattedJSON ( + video: MVideoFormattableDetails, + playlists: MStreamingPlaylistRedundanciesOpt[] +): VideoStreamingPlaylist[] { if (isArray(playlists) === false) return [] - const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() - return playlists .map(playlist => { - const playlistWithVideo = Object.assign(playlist, { Video: video }) - const redundancies = isArray(playlist.RedundancyVideos) ? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl })) : [] - const files = videoFilesModelToFormattedJSON(playlistWithVideo, baseUrlHttp, baseUrlWs, playlist.VideoFiles) + const files = videoFilesModelToFormattedJSON(video, playlist.VideoFiles) return { id: playlist.id, @@ -175,50 +180,65 @@ function streamingPlaylistsModelToFormattedJSON (video: MVideo, playlists: MStre }) } +function sortByResolutionDesc (fileA: MVideoFile, fileB: MVideoFile) { + if (fileA.resolution < fileB.resolution) return 1 + if (fileA.resolution === fileB.resolution) return 0 + return -1 +} + function videoFilesModelToFormattedJSON ( - model: MVideo | MStreamingPlaylistVideo, - baseUrlHttp: string, - baseUrlWs: string, - videoFiles: MVideoFileRedundanciesOpt[] + video: MVideoFormattableDetails, + videoFiles: MVideoFileRedundanciesOpt[], + includeMagnet = true ): VideoFile[] { - const video = extractVideo(model) + const trackerUrls = includeMagnet + ? video.getTrackerUrls() + : [] - return videoFiles + return [ ...videoFiles ] + .filter(f => !f.isLive()) + .sort(sortByResolutionDesc) .map(videoFile => { return { resolution: { id: videoFile.resolution, label: videoFile.resolution + 'p' }, - magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), + + magnetUri: includeMagnet && videoFile.hasTorrent() + ? generateMagnetUri(video, videoFile, trackerUrls) + : undefined, + size: videoFile.size, fps: videoFile.fps, - torrentUrl: model.getTorrentUrl(videoFile, baseUrlHttp), - torrentDownloadUrl: model.getTorrentDownloadUrl(videoFile, baseUrlHttp), - fileUrl: model.getVideoFileUrl(videoFile, baseUrlHttp), - fileDownloadUrl: model.getVideoFileDownloadUrl(videoFile, baseUrlHttp), - metadataUrl: video.getVideoFileMetadataUrl(videoFile, baseUrlHttp) + + torrentUrl: videoFile.getTorrentUrl(), + torrentDownloadUrl: videoFile.getTorrentDownloadUrl(), + + fileUrl: videoFile.getFileUrl(video), + fileDownloadUrl: videoFile.getFileDownloadUrl(video), + + metadataUrl: videoFile.metadataUrl ?? getLocalVideoFileMetadataUrl(video, videoFile) } as VideoFile }) - .sort((a, b) => { - if (a.resolution.id < b.resolution.id) return 1 - if (a.resolution.id === b.resolution.id) return 0 - return -1 - }) } function addVideoFilesInAPAcc ( acc: ActivityUrlObject[] | ActivityTagObject[], - model: MVideoAP | MStreamingPlaylistVideo, - baseUrlHttp: string, - baseUrlWs: string, + video: MVideo, files: MVideoFile[] ) { - for (const file of files) { + const trackerUrls = video.getTrackerUrls() + + const sortedFiles = [ ...files ] + .filter(f => !f.isLive()) + .sort(sortByResolutionDesc) + + for (const file of sortedFiles) { acc.push({ type: 'Link', mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any, - href: model.getVideoFileUrl(file, baseUrlHttp), + href: file.getFileUrl(video), height: file.resolution, size: file.size, fps: file.fps @@ -228,29 +248,30 @@ function addVideoFilesInAPAcc ( type: 'Link', rel: [ 'metadata', MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] ], mediaType: 'application/json' as 'application/json', - href: extractVideo(model).getVideoFileMetadataUrl(file, baseUrlHttp), + href: getLocalVideoFileMetadataUrl(video, file), height: file.resolution, fps: file.fps }) - acc.push({ - type: 'Link', - mediaType: 'application/x-bittorrent' as 'application/x-bittorrent', - href: model.getTorrentUrl(file, baseUrlHttp), - height: file.resolution - }) - - acc.push({ - type: 'Link', - mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', - href: generateMagnetUri(model, file, baseUrlHttp, baseUrlWs), - height: file.resolution - }) + if (file.hasTorrent()) { + acc.push({ + type: 'Link', + mediaType: 'application/x-bittorrent' as 'application/x-bittorrent', + href: file.getTorrentUrl(), + height: file.resolution + }) + + acc.push({ + type: 'Link', + mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', + href: generateMagnetUri(video, file, trackerUrls), + height: file.resolution + }) + } } } -function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { - const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() +function videoModelToActivityPubObject (video: MVideoAP): VideoObject { if (!video.Tags) video.Tags = [] const tag = video.Tags.map(t => ({ @@ -291,7 +312,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { } ] - addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) + addVideoFilesInAPAcc(url, video, video.VideoFiles || []) for (const playlist of (video.VideoStreamingPlaylists || [])) { const tag = playlist.p2pMediaLoaderInfohashes @@ -303,8 +324,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { href: playlist.segmentsSha256Url }) - const playlistWithVideo = Object.assign(playlist, { Video: video }) - addVideoFilesInAPAcc(tag, playlistWithVideo, baseUrlHttp, baseUrlWs, playlist.VideoFiles || []) + addVideoFilesInAPAcc(tag, video, playlist.VideoFiles || []) url.push({ type: 'Link', @@ -314,6 +334,19 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { }) } + for (const trackerUrl of video.getTrackerUrls()) { + const rel2 = trackerUrl.startsWith('http') + ? 'http' + : 'websocket' + + url.push({ + type: 'Link', + name: `tracker-${rel2}`, + rel: [ 'tracker', rel2 ], + href: trackerUrl + }) + } + const subtitleLanguage = [] for (const caption of video.VideoCaptions) { subtitleLanguage.push({ @@ -323,10 +356,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { }) } - // FIXME: remove and uncomment in PT 2.3 - // Breaks compatibility with PT <= 2.1 - // const icons = [ video.getMiniature(), video.getPreview() ] - const miniature = video.getMiniature() + const icons = [ video.getMiniature(), video.getPreview() ] return { type: 'Video' as 'Video', @@ -341,35 +371,42 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { views: video.views, sensitive: video.nsfw, waitTranscoding: video.waitTranscoding, + isLiveBroadcast: video.isLive, + + liveSaveReplay: video.isLive + ? video.VideoLive.saveReplay + : null, + + permanentLive: video.isLive + ? video.VideoLive.permanentLive + : null, + state: video.state, commentsEnabled: video.commentsEnabled, downloadEnabled: video.downloadEnabled, published: video.publishedAt.toISOString(), - originallyPublishedAt: video.originallyPublishedAt ? video.originallyPublishedAt.toISOString() : null, + + originallyPublishedAt: video.originallyPublishedAt + ? video.originallyPublishedAt.toISOString() + : null, + updated: video.updatedAt.toISOString(), mediaType: 'text/markdown', - content: video.getTruncatedDescription(), + content: video.description, support: video.support, subtitleLanguage, - icon: { + icon: icons.map(i => ({ type: 'Image', - url: miniature.getFileUrl(video), + url: i.getFileUrl(video), mediaType: 'image/jpeg', - width: miniature.width, - height: miniature.height - } as any, - // icon: icons.map(i => ({ - // type: 'Image', - // url: i.getFileUrl(video), - // mediaType: 'image/jpeg', - // width: i.width, - // height: i.height - // })), + width: i.width, + height: i.height + })), url, - likes: getVideoLikesActivityPubUrl(video), - dislikes: getVideoDislikesActivityPubUrl(video), - shares: getVideoSharesActivityPubUrl(video), - comments: getVideoCommentsActivityPubUrl(video), + likes: getLocalVideoLikesActivityPubUrl(video), + dislikes: getLocalVideoDislikesActivityPubUrl(video), + shares: getLocalVideoSharesActivityPubUrl(video), + comments: getLocalVideoCommentsActivityPubUrl(video), attributedTo: [ { type: 'Person',