X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fformatter%2Fvideo-format-utils.ts;h=6f05dbdc8bf2008126d85d1d1b99418593daf604;hb=32fde390f465b9ceef9e49c4037acdacbe176d65;hp=3310b3b4683c5a8f3fac9fc37bde47cec17b2916;hpb=764b1a14fc494f2cfd7ea590d2f07b01df65c7ad;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/formatter/video-format-utils.ts b/server/models/video/formatter/video-format-utils.ts index 3310b3b46..6f05dbdc8 100644 --- a/server/models/video/formatter/video-format-utils.ts +++ b/server/models/video/formatter/video-format-utils.ts @@ -1,10 +1,20 @@ -import { uuidToShort } from '@server/helpers/uuid' 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 { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model' +import { getActivityStreamDuration } from '@server/lib/activitypub/activity' +import { tracer } from '@server/lib/opentelemetry/tracing' +import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls' +import { VideoViewsManager } from '@server/lib/views/video-views-manager' +import { uuidToShort } from '@shared/extra-utils' +import { + ActivityTagObject, + ActivityUrlObject, + Video, + VideoDetails, + VideoFile, + VideoInclude, + VideoObject, + VideosCommonQueryAfterSanitize, + VideoStreamingPlaylist +} from '@shared/models' import { isArray } from '../../../helpers/custom-validators/misc' import { MIMETYPES, @@ -22,7 +32,9 @@ import { getLocalVideoSharesActivityPubUrl } from '../../../lib/activitypub/url' import { + MServer, MStreamingPlaylistRedundanciesOpt, + MUserId, MVideo, MVideoAP, MVideoFile, @@ -34,15 +46,35 @@ import { VideoCaptionModel } from '../video-caption' export type VideoFormattingJSONOptions = { completeDescription?: boolean - additionalAttributes: { + + additionalAttributes?: { state?: boolean waitTranscoding?: boolean scheduledUpdate?: boolean blacklistInfo?: boolean + files?: boolean + blockedOwner?: boolean + } +} + +function guessAdditionalAttributesFromQuery (query: VideosCommonQueryAfterSanitize): VideoFormattingJSONOptions { + if (!query?.include) return {} + + return { + additionalAttributes: { + state: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE), + waitTranscoding: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE), + scheduledUpdate: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE), + blacklistInfo: !!(query.include & VideoInclude.BLACKLISTED), + files: !!(query.include & VideoInclude.FILES), + blockedOwner: !!(query.include & VideoInclude.BLOCKED_OWNER) + } } } -function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { +function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoFormattingJSONOptions = {}): Video { + const span = tracer.startSpan('peertube.VideoModel.toFormattedJSON') + const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined const videoObject: Video = { @@ -50,6 +82,8 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor uuid: video.uuid, shortUUID: uuidToShort(video.uuid), + url: video.url, + name: video.name, category: { id: video.category, @@ -69,13 +103,17 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor }, nsfw: video.nsfw, + truncatedDescription: video.getTruncatedDescription(), description: options && options.completeDescription === true ? video.description : video.getTruncatedDescription(), isLocal: video.isOwned(), duration: video.duration, + views: video.views, + viewers: VideoViewsManager.Instance.getViewers(video), + likes: video.likes, dislikes: video.dislikes, thumbnailPath: video.getMiniatureStaticPath(), @@ -99,47 +137,62 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor pluginData: (video as any).pluginData } - if (options) { - if (options.additionalAttributes.state === true) { - videoObject.state = { - id: video.state, - label: getStateLabel(video.state) - } + const add = options.additionalAttributes + if (add?.state === true) { + videoObject.state = { + id: video.state, + label: getStateLabel(video.state) } + } - if (options.additionalAttributes.waitTranscoding === true) { - videoObject.waitTranscoding = video.waitTranscoding - } + if (add?.waitTranscoding === true) { + videoObject.waitTranscoding = video.waitTranscoding + } - if (options.additionalAttributes.scheduledUpdate === true && video.ScheduleVideoUpdate) { - videoObject.scheduledUpdate = { - updateAt: video.ScheduleVideoUpdate.updateAt, - privacy: video.ScheduleVideoUpdate.privacy || undefined - } + if (add?.scheduledUpdate === true && video.ScheduleVideoUpdate) { + videoObject.scheduledUpdate = { + updateAt: video.ScheduleVideoUpdate.updateAt, + privacy: video.ScheduleVideoUpdate.privacy || undefined } + } - if (options.additionalAttributes.blacklistInfo === true) { - videoObject.blacklisted = !!video.VideoBlacklist - videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null - } + if (add?.blacklistInfo === true) { + videoObject.blacklisted = !!video.VideoBlacklist + videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null + } + + if (add?.blockedOwner === true) { + videoObject.blockedOwner = video.VideoChannel.Account.isBlocked() + + const server = video.VideoChannel.Account.Actor.Server as MServer + videoObject.blockedServer = !!(server?.isBlocked()) } + if (add?.files === true) { + videoObject.streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) + videoObject.files = videoFilesModelToFormattedJSON(video, video.VideoFiles) + } + + span.end() + return videoObject } function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails { - const formattedJson = video.toFormattedJSON({ + const span = tracer.startSpan('peertube.VideoModel.toFormattedDetailsJSON') + + const videoJSON = video.toFormattedJSON({ + completeDescription: true, additionalAttributes: { scheduledUpdate: true, - blacklistInfo: true + blacklistInfo: true, + files: true } - }) + }) as Video & Required> const tags = video.Tags ? video.Tags.map(t => t.name) : [] - const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) - - const detailsJson = { + const detailsJSON = { support: video.support, descriptionPath: video.getDescriptionAPIPath(), channel: video.VideoChannel.toFormattedJSON(), @@ -153,20 +206,16 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid label: getStateLabel(video.state) }, - trackerUrls: video.getTrackerUrls(), - - files: [], - streamingPlaylists + trackerUrls: video.getTrackerUrls() } - // Format and sort video files - detailsJson.files = videoFilesModelToFormattedJSON(video, video.VideoFiles) + span.end() - return Object.assign(formattedJson, detailsJson) + return Object.assign(videoJSON, detailsJSON) } function streamingPlaylistsModelToFormattedJSON ( - video: MVideoFormattableDetails, + video: MVideoFormattable, playlists: MStreamingPlaylistRedundanciesOpt[] ): VideoStreamingPlaylist[] { if (isArray(playlists) === false) return [] @@ -197,19 +246,25 @@ function sortByResolutionDesc (fileA: MVideoFile, fileB: MVideoFile) { } function videoFilesModelToFormattedJSON ( - video: MVideoFormattableDetails, + video: MVideoFormattable, videoFiles: MVideoFileRedundanciesOpt[], - includeMagnet = true + options: { + includeMagnet?: boolean // default true + } = {} ): VideoFile[] { + const { includeMagnet = true } = options + const trackerUrls = includeMagnet ? video.getTrackerUrls() : [] - return [ ...videoFiles ] + return (videoFiles || []) .filter(f => !f.isLive()) .sort(sortByResolutionDesc) .map(videoFile => { return { + id: videoFile.id, + resolution: { id: videoFile.resolution, label: videoFile.resolution === 0 ? 'Audio' : `${videoFile.resolution}p` @@ -233,14 +288,17 @@ function videoFilesModelToFormattedJSON ( }) } -function addVideoFilesInAPAcc ( - acc: ActivityUrlObject[] | ActivityTagObject[], - video: MVideo, +function addVideoFilesInAPAcc (options: { + acc: ActivityUrlObject[] | ActivityTagObject[] + video: MVideo files: MVideoFile[] -) { + user?: MUserId +}) { + const { acc, video, files } = options + const trackerUrls = video.getTrackerUrls() - const sortedFiles = [ ...files ] + const sortedFiles = (files || []) .filter(f => !f.isLive()) .sort(sortByResolutionDesc) @@ -322,7 +380,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { } ] - addVideoFilesInAPAcc(url, video, video.VideoFiles || []) + addVideoFilesInAPAcc({ acc: url, video, files: video.VideoFiles || [] }) for (const playlist of (video.VideoStreamingPlaylists || [])) { const tag = playlist.p2pMediaLoaderInfohashes @@ -334,7 +392,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { href: playlist.getSha256SegmentsUrl(video) }) - addVideoFilesInAPAcc(tag, video, playlist.VideoFiles || []) + addVideoFilesInAPAcc({ acc: tag, video, files: playlist.VideoFiles || [] }) url.push({ type: 'Link', @@ -381,15 +439,6 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { 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, @@ -401,10 +450,13 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { : null, updated: video.updatedAt.toISOString(), + mediaType: 'text/markdown', content: video.description, support: video.support, + subtitleLanguage, + icon: icons.map(i => ({ type: 'Image', url: i.getFileUrl(video), @@ -412,11 +464,14 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { width: i.width, height: i.height })), + url, + likes: getLocalVideoLikesActivityPubUrl(video), dislikes: getLocalVideoDislikesActivityPubUrl(video), shares: getLocalVideoSharesActivityPubUrl(video), comments: getLocalVideoCommentsActivityPubUrl(video), + attributedTo: [ { type: 'Person', @@ -426,17 +481,14 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { type: 'Group', id: video.VideoChannel.Actor.url } - ] - } -} + ], -function getActivityStreamDuration (duration: number) { - // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration - return 'PT' + duration + 'S' + ...buildLiveAPAttributes(video) + } } function getCategoryLabel (id: number) { - return VIDEO_CATEGORIES[id] || 'Misc' + return VIDEO_CATEGORIES[id] || 'Unknown' } function getLicenceLabel (id: number) { @@ -460,7 +512,8 @@ export { videoModelToFormattedDetailsJSON, videoFilesModelToFormattedJSON, videoModelToActivityPubObject, - getActivityStreamDuration, + + guessAdditionalAttributesFromQuery, getCategoryLabel, getLicenceLabel, @@ -468,3 +521,23 @@ export { getPrivacyLabel, getStateLabel } + +// --------------------------------------------------------------------------- + +function buildLiveAPAttributes (video: MVideoAP) { + if (!video.isLive) { + return { + isLiveBroadcast: false, + liveSaveReplay: null, + permanentLive: null, + latencyMode: null + } + } + + return { + isLiveBroadcast: true, + liveSaveReplay: video.VideoLive.saveReplay, + permanentLive: video.VideoLive.permanentLive, + latencyMode: video.VideoLive.latencyMode + } +}