Init commit
This commit is contained in:
2
server/core/models/video/formatter/index.ts
Normal file
2
server/core/models/video/formatter/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './video-activity-pub-format.js'
|
||||
export * from './video-api-format.js'
|
||||
1
server/core/models/video/formatter/shared/index.ts
Normal file
1
server/core/models/video/formatter/shared/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './video-format-utils.js'
|
||||
@@ -0,0 +1,7 @@
|
||||
import { MVideoFile } from '@server/types/models/index.js'
|
||||
|
||||
export function sortByResolutionDesc (fileA: MVideoFile, fileB: MVideoFile) {
|
||||
if (fileA.resolution < fileB.resolution) return 1
|
||||
if (fileA.resolution === fileB.resolution) return 0
|
||||
return -1
|
||||
}
|
||||
322
server/core/models/video/formatter/video-activity-pub-format.ts
Normal file
322
server/core/models/video/formatter/video-activity-pub-format.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
import {
|
||||
ActivityHashTagObject,
|
||||
ActivityIconObject,
|
||||
ActivityPlaylistUrlObject,
|
||||
ActivityPubStoryboard,
|
||||
ActivitySensitiveTagObject,
|
||||
ActivityTagObject,
|
||||
ActivityTrackerUrlObject,
|
||||
ActivityUrlObject,
|
||||
nsfwFlagsToString,
|
||||
VideoCommentPolicy,
|
||||
VideoObject
|
||||
} from '@peertube/peertube-models'
|
||||
import { getAPPublicValue } from '@server/helpers/activity-pub-utils.js'
|
||||
import { isArray } from '@server/helpers/custom-validators/misc.js'
|
||||
import { generateMagnetUri } from '@server/lib/webtorrent.js'
|
||||
import { getActivityStreamDuration } from '@server/lib/activitypub/activity.js'
|
||||
import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls.js'
|
||||
import { WEBSERVER } from '../../../initializers/constants.js'
|
||||
import {
|
||||
getLocalVideoChaptersActivityPubUrl,
|
||||
getLocalVideoCommentsActivityPubUrl,
|
||||
getLocalVideoDislikesActivityPubUrl,
|
||||
getLocalVideoLikesActivityPubUrl,
|
||||
getLocalVideoPlayerSettingsActivityPubUrl,
|
||||
getLocalVideoSharesActivityPubUrl
|
||||
} from '../../../lib/activitypub/url.js'
|
||||
import { MStreamingPlaylistFiles, MUserId, MVideo, MVideoAP, MVideoFile } from '../../../types/models/index.js'
|
||||
import { sortByResolutionDesc } from './shared/index.js'
|
||||
import { getCategoryLabel, getLanguageLabel, getLicenceLabel } from './video-api-format.js'
|
||||
|
||||
export function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
|
||||
const language = video.language
|
||||
? { identifier: video.language, name: getLanguageLabel(video.language) }
|
||||
: undefined
|
||||
|
||||
const category = video.category
|
||||
? { identifier: video.category + '', name: getCategoryLabel(video.category) }
|
||||
: undefined
|
||||
|
||||
const licence = video.licence
|
||||
? { identifier: video.licence + '', name: getLicenceLabel(video.licence) }
|
||||
: undefined
|
||||
|
||||
const url: ActivityUrlObject[] = [
|
||||
// HTML url should be the first element in the array so Mastodon correctly displays the embed
|
||||
{
|
||||
type: 'Link',
|
||||
mediaType: 'text/html',
|
||||
href: WEBSERVER.URL + video.getWatchStaticPath()
|
||||
} as ActivityUrlObject,
|
||||
|
||||
{
|
||||
type: 'Link',
|
||||
mediaType: 'text/html',
|
||||
href: video.url
|
||||
} as ActivityUrlObject,
|
||||
|
||||
...buildVideoFileUrls({ video, files: video.VideoFiles }),
|
||||
|
||||
...buildStreamingPlaylistUrls(video),
|
||||
|
||||
...buildTrackerUrls(video)
|
||||
]
|
||||
|
||||
return {
|
||||
type: 'Video' as 'Video',
|
||||
id: video.url,
|
||||
name: video.name,
|
||||
duration: getActivityStreamDuration(video.duration),
|
||||
uuid: video.uuid,
|
||||
category,
|
||||
licence,
|
||||
language,
|
||||
views: video.views,
|
||||
|
||||
sensitive: video.nsfw,
|
||||
summary: video.nsfwSummary,
|
||||
|
||||
waitTranscoding: video.waitTranscoding,
|
||||
|
||||
state: video.state,
|
||||
|
||||
canReply: video.commentsPolicy === VideoCommentPolicy.ENABLED
|
||||
? null
|
||||
: getAPPublicValue(), // Requires approval
|
||||
|
||||
commentsPolicy: video.commentsPolicy,
|
||||
|
||||
downloadEnabled: video.downloadEnabled,
|
||||
published: video.publishedAt.toISOString(),
|
||||
|
||||
originallyPublishedAt: video.originallyPublishedAt
|
||||
? video.originallyPublishedAt.toISOString()
|
||||
: null,
|
||||
|
||||
schedules: (video.VideoLive?.LiveSchedules || []).map(s => ({
|
||||
startDate: s.startAt
|
||||
})),
|
||||
|
||||
updated: video.updatedAt.toISOString(),
|
||||
|
||||
uploadDate: video.inputFileUpdatedAt?.toISOString(),
|
||||
|
||||
tag: buildTags(video),
|
||||
|
||||
mediaType: 'text/markdown',
|
||||
content: video.description,
|
||||
support: video.support,
|
||||
|
||||
subtitleLanguage: buildSubtitleLanguage(video),
|
||||
|
||||
icon: buildIcon(video),
|
||||
|
||||
preview: buildPreviewAPAttribute(video),
|
||||
|
||||
aspectRatio: video.aspectRatio,
|
||||
|
||||
url,
|
||||
|
||||
likes: getLocalVideoLikesActivityPubUrl(video),
|
||||
dislikes: getLocalVideoDislikesActivityPubUrl(video),
|
||||
shares: getLocalVideoSharesActivityPubUrl(video),
|
||||
comments: getLocalVideoCommentsActivityPubUrl(video),
|
||||
hasParts: getLocalVideoChaptersActivityPubUrl(video),
|
||||
playerSettings: getLocalVideoPlayerSettingsActivityPubUrl(video),
|
||||
|
||||
attributedTo: [
|
||||
video.VideoChannel.Account.Actor.url,
|
||||
video.VideoChannel.Actor.url
|
||||
],
|
||||
|
||||
...buildLiveAPAttributes(video)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function buildPreviewAPAttribute (video: MVideoAP): ActivityPubStoryboard[] {
|
||||
if (!video.Storyboard) return undefined
|
||||
|
||||
const storyboard = video.Storyboard
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'Image',
|
||||
rel: [ 'storyboard' ],
|
||||
url: [
|
||||
{
|
||||
mediaType: 'image/jpeg',
|
||||
|
||||
href: storyboard.getOriginFileUrl(video),
|
||||
|
||||
width: storyboard.totalWidth,
|
||||
height: storyboard.totalHeight,
|
||||
|
||||
tileWidth: storyboard.spriteWidth,
|
||||
tileHeight: storyboard.spriteHeight,
|
||||
tileDuration: getActivityStreamDuration(storyboard.spriteDuration)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function buildVideoFileUrls (options: {
|
||||
video: MVideo
|
||||
files: MVideoFile[]
|
||||
user?: MUserId
|
||||
}): ActivityUrlObject[] {
|
||||
const { video, files } = options
|
||||
|
||||
if (!isArray(files)) return []
|
||||
|
||||
const urls: ActivityUrlObject[] = []
|
||||
|
||||
const trackerUrls = video.getTrackerUrls()
|
||||
const sortedFiles = files
|
||||
.filter(f => !f.isLive())
|
||||
.sort(sortByResolutionDesc)
|
||||
|
||||
for (const file of sortedFiles) {
|
||||
const fileAP = file.toActivityPubObject(video)
|
||||
urls.push(fileAP)
|
||||
|
||||
urls.push({
|
||||
type: 'Link',
|
||||
rel: [ 'metadata', fileAP.mediaType ],
|
||||
mediaType: 'application/json' as 'application/json',
|
||||
href: getLocalVideoFileMetadataUrl(video, file),
|
||||
height: file.height || file.resolution,
|
||||
width: file.width,
|
||||
fps: file.fps
|
||||
})
|
||||
|
||||
if (file.hasTorrent()) {
|
||||
urls.push({
|
||||
type: 'Link',
|
||||
mediaType: 'application/x-bittorrent' as 'application/x-bittorrent',
|
||||
href: file.getTorrentUrl(),
|
||||
height: file.height || file.resolution,
|
||||
width: file.width,
|
||||
fps: file.fps
|
||||
})
|
||||
|
||||
urls.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.height || file.resolution,
|
||||
width: file.width,
|
||||
fps: file.fps
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildStreamingPlaylistUrls (video: MVideoAP): ActivityPlaylistUrlObject[] {
|
||||
if (!isArray(video.VideoStreamingPlaylists)) return []
|
||||
|
||||
return video.VideoStreamingPlaylists
|
||||
.map(playlist => ({
|
||||
type: 'Link',
|
||||
mediaType: 'application/x-mpegURL' as 'application/x-mpegURL',
|
||||
href: playlist.getMasterPlaylistUrl(video),
|
||||
tag: buildStreamingPlaylistTags(video, playlist)
|
||||
}))
|
||||
}
|
||||
|
||||
function buildStreamingPlaylistTags (video: MVideoAP, playlist: MStreamingPlaylistFiles) {
|
||||
return [
|
||||
...playlist.p2pMediaLoaderInfohashes.map(i => ({ type: 'Infohash' as 'Infohash', name: i })),
|
||||
|
||||
{
|
||||
type: 'Link',
|
||||
name: 'sha256',
|
||||
mediaType: 'application/json' as 'application/json',
|
||||
href: playlist.getSha256SegmentsUrl(video)
|
||||
},
|
||||
|
||||
...buildVideoFileUrls({ video, files: playlist.VideoFiles })
|
||||
] as ActivityTagObject[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildTrackerUrls (video: MVideoAP): ActivityTrackerUrlObject[] {
|
||||
return video.getTrackerUrls()
|
||||
.map(trackerUrl => {
|
||||
const rel2 = trackerUrl.startsWith('http')
|
||||
? 'http'
|
||||
: 'websocket'
|
||||
|
||||
return {
|
||||
type: 'Link',
|
||||
name: `tracker-${rel2}`,
|
||||
rel: [ 'tracker', rel2 ],
|
||||
href: trackerUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildTags (video: MVideoAP): (ActivitySensitiveTagObject | ActivityHashTagObject)[] {
|
||||
const tags = isArray(video.Tags)
|
||||
? video.Tags
|
||||
: []
|
||||
|
||||
return [
|
||||
...tags.map(t =>
|
||||
({
|
||||
type: 'Hashtag' as 'Hashtag',
|
||||
name: t.name
|
||||
}) as ActivityHashTagObject
|
||||
),
|
||||
|
||||
...nsfwFlagsToString(video.nsfwFlags).map(f =>
|
||||
({
|
||||
type: 'SensitiveTag' as 'SensitiveTag',
|
||||
name: f
|
||||
}) as ActivitySensitiveTagObject
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
function buildIcon (video: MVideoAP): ActivityIconObject[] {
|
||||
return [ video.getMiniature(), video.getPreview() ]
|
||||
.filter(i => !!i)
|
||||
.map(i => i.toActivityPubObject(video))
|
||||
}
|
||||
|
||||
function buildSubtitleLanguage (video: MVideoAP) {
|
||||
if (!isArray(video.VideoCaptions)) return []
|
||||
|
||||
return video.VideoCaptions
|
||||
.map(caption => caption.toActivityPubObject(video))
|
||||
}
|
||||
374
server/core/models/video/formatter/video-api-format.ts
Normal file
374
server/core/models/video/formatter/video-api-format.ts
Normal file
@@ -0,0 +1,374 @@
|
||||
import { getResolutionLabel } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
Video,
|
||||
VideoAdditionalAttributes,
|
||||
VideoDetails,
|
||||
VideoFile,
|
||||
VideoInclude,
|
||||
VideosCommonQueryAfterSanitize,
|
||||
VideoStreamingPlaylist
|
||||
} from '@peertube/peertube-models'
|
||||
import { uuidToShort } from '@peertube/peertube-node-utils'
|
||||
import { generateMagnetUri } from '@server/lib/webtorrent.js'
|
||||
import { tracer } from '@server/lib/opentelemetry/tracing.js'
|
||||
import { getHLSResolutionPlaylistFilename } from '@server/lib/paths.js'
|
||||
import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls.js'
|
||||
import { VideoViewsManager } from '@server/lib/views/video-views-manager.js'
|
||||
import { isArray } from '../../../helpers/custom-validators/misc.js'
|
||||
import {
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_COMMENTS_POLICY,
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_PRIVACIES,
|
||||
VIDEO_STATES
|
||||
} from '../../../initializers/constants.js'
|
||||
import { MServer, MStreamingPlaylistRedundanciesOpt, MVideoFormattable, MVideoFormattableDetails } from '../../../types/models/index.js'
|
||||
import { MVideoFile } from '../../../types/models/video/video-file.js'
|
||||
import { sortByResolutionDesc } from './shared/index.js'
|
||||
|
||||
export type VideoFormattingJSONOptions = {
|
||||
completeDescription?: boolean
|
||||
|
||||
additionalAttributes?: {
|
||||
state?: boolean
|
||||
waitTranscoding?: boolean
|
||||
scheduledUpdate?: boolean
|
||||
blacklistInfo?: boolean
|
||||
files?: boolean
|
||||
source?: boolean
|
||||
blockedOwner?: boolean
|
||||
automaticTags?: boolean
|
||||
liveSchedules?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export function guessAdditionalAttributesFromQuery (
|
||||
query: Pick<VideosCommonQueryAfterSanitize, 'include' | 'includeScheduledLive'>
|
||||
): VideoFormattingJSONOptions {
|
||||
return {
|
||||
additionalAttributes: {
|
||||
state: query.includeScheduledLive || !!(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),
|
||||
source: !!(query.include & VideoInclude.SOURCE),
|
||||
blockedOwner: !!(query.include & VideoInclude.BLOCKED_OWNER),
|
||||
automaticTags: !!(query.include & VideoInclude.AUTOMATIC_TAGS),
|
||||
liveSchedules: query.includeScheduledLive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export 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 = {
|
||||
id: video.id,
|
||||
uuid: video.uuid,
|
||||
shortUUID: uuidToShort(video.uuid),
|
||||
|
||||
url: video.url,
|
||||
|
||||
name: video.name,
|
||||
category: {
|
||||
id: video.category,
|
||||
label: getCategoryLabel(video.category)
|
||||
},
|
||||
licence: {
|
||||
id: video.licence,
|
||||
label: getLicenceLabel(video.licence)
|
||||
},
|
||||
language: {
|
||||
id: video.language,
|
||||
label: getLanguageLabel(video.language)
|
||||
},
|
||||
privacy: {
|
||||
id: video.privacy,
|
||||
label: getPrivacyLabel(video.privacy)
|
||||
},
|
||||
|
||||
nsfw: video.nsfw,
|
||||
nsfwFlags: video.nsfwFlags,
|
||||
nsfwSummary: video.nsfwSummary,
|
||||
|
||||
truncatedDescription: video.getTruncatedDescription(),
|
||||
description: options?.completeDescription === true
|
||||
? video.description
|
||||
: video.getTruncatedDescription(),
|
||||
|
||||
isLocal: video.isLocal(),
|
||||
duration: video.duration,
|
||||
|
||||
aspectRatio: video.aspectRatio,
|
||||
|
||||
views: video.views,
|
||||
viewers: VideoViewsManager.Instance.getTotalViewersOf(video),
|
||||
|
||||
likes: video.likes,
|
||||
dislikes: video.dislikes,
|
||||
thumbnailPath: video.getMiniatureStaticPath(),
|
||||
previewPath: video.getPreviewStaticPath(),
|
||||
embedPath: video.getEmbedStaticPath(),
|
||||
createdAt: video.createdAt,
|
||||
updatedAt: video.updatedAt,
|
||||
publishedAt: video.publishedAt,
|
||||
originallyPublishedAt: video.originallyPublishedAt,
|
||||
|
||||
isLive: video.isLive,
|
||||
|
||||
account: video.VideoChannel.Account.toFormattedSummaryJSON(),
|
||||
channel: video.VideoChannel.toFormattedSummaryJSON(),
|
||||
|
||||
userHistory: userHistory
|
||||
? { currentTime: userHistory.currentTime }
|
||||
: undefined,
|
||||
|
||||
comments: video.comments,
|
||||
|
||||
// Can be added by external plugins
|
||||
pluginData: (video as any).pluginData,
|
||||
|
||||
...buildAdditionalAttributes(video, options)
|
||||
}
|
||||
|
||||
span.end()
|
||||
|
||||
return videoObject
|
||||
}
|
||||
|
||||
export function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails {
|
||||
const span = tracer.startSpan('peertube.VideoModel.toFormattedDetailsJSON')
|
||||
|
||||
const videoJSON = video.toFormattedJSON({
|
||||
completeDescription: true,
|
||||
additionalAttributes: {
|
||||
liveSchedules: true,
|
||||
scheduledUpdate: true,
|
||||
blacklistInfo: true,
|
||||
files: true
|
||||
}
|
||||
}) as Video & Required<Pick<Video, 'files' | 'streamingPlaylists' | 'scheduledUpdate' | 'blacklisted' | 'blacklistedReason'>>
|
||||
|
||||
const tags = video.Tags
|
||||
? video.Tags.map(t => t.name)
|
||||
: []
|
||||
|
||||
const detailsJSON = {
|
||||
...videoJSON,
|
||||
|
||||
support: video.support,
|
||||
channel: video.VideoChannel.toFormattedJSON(),
|
||||
account: video.VideoChannel.Account.toFormattedJSON(),
|
||||
tags,
|
||||
|
||||
commentsPolicy: {
|
||||
id: video.commentsPolicy,
|
||||
label: VIDEO_COMMENTS_POLICY[video.commentsPolicy]
|
||||
},
|
||||
|
||||
downloadEnabled: video.downloadEnabled,
|
||||
waitTranscoding: video.waitTranscoding,
|
||||
inputFileUpdatedAt: video.inputFileUpdatedAt,
|
||||
state: {
|
||||
id: video.state,
|
||||
label: getStateLabel(video.state)
|
||||
},
|
||||
|
||||
trackerUrls: video.getTrackerUrls()
|
||||
}
|
||||
|
||||
span.end()
|
||||
|
||||
return detailsJSON
|
||||
}
|
||||
|
||||
export function streamingPlaylistsModelToFormattedJSON (
|
||||
video: MVideoFormattable,
|
||||
playlists: MStreamingPlaylistRedundanciesOpt[]
|
||||
): VideoStreamingPlaylist[] {
|
||||
if (isArray(playlists) === false) return []
|
||||
|
||||
return playlists
|
||||
.map(playlist => ({
|
||||
id: playlist.id,
|
||||
type: playlist.type,
|
||||
|
||||
playlistUrl: playlist.getMasterPlaylistUrl(video),
|
||||
segmentsSha256Url: playlist.getSha256SegmentsUrl(video),
|
||||
|
||||
redundancies: isArray(playlist.RedundancyVideos)
|
||||
? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl }))
|
||||
: [],
|
||||
|
||||
files: videoFilesModelToFormattedJSON(video, playlist.VideoFiles, { includePlaylistUrl: true })
|
||||
}))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function videoFilesModelToFormattedJSON (
|
||||
video: MVideoFormattable,
|
||||
videoFiles: MVideoFile[],
|
||||
options?: {
|
||||
includePlaylistUrl?: true
|
||||
includeMagnet?: boolean
|
||||
}
|
||||
): (VideoFile & { playlistUrl: string })[]
|
||||
|
||||
export function videoFilesModelToFormattedJSON (
|
||||
video: MVideoFormattable,
|
||||
videoFiles: MVideoFile[],
|
||||
options: {
|
||||
includePlaylistUrl?: boolean // default false
|
||||
includeMagnet?: boolean // default true
|
||||
} = {}
|
||||
): VideoFile[] {
|
||||
const { includePlaylistUrl = false, includeMagnet = true } = options
|
||||
|
||||
if (isArray(videoFiles) === false) return []
|
||||
|
||||
const trackerUrls = includeMagnet
|
||||
? video.getTrackerUrls()
|
||||
: []
|
||||
|
||||
return videoFiles
|
||||
.filter(f => !f.isLive())
|
||||
.sort(sortByResolutionDesc)
|
||||
.map(videoFile => {
|
||||
const fileUrl = videoFile.getFileUrl(video)
|
||||
|
||||
return {
|
||||
id: videoFile.id,
|
||||
|
||||
resolution: {
|
||||
id: videoFile.resolution,
|
||||
|
||||
label: getResolutionLabel({
|
||||
resolution: videoFile.resolution,
|
||||
height: videoFile.height,
|
||||
width: videoFile.width
|
||||
})
|
||||
},
|
||||
|
||||
width: videoFile.width,
|
||||
height: videoFile.height,
|
||||
|
||||
magnetUri: includeMagnet && videoFile.hasTorrent()
|
||||
? generateMagnetUri(video, videoFile, trackerUrls)
|
||||
: undefined,
|
||||
|
||||
size: videoFile.size,
|
||||
fps: videoFile.fps,
|
||||
|
||||
torrentUrl: videoFile.getTorrentUrl(),
|
||||
torrentDownloadUrl: videoFile.getTorrentDownloadUrl(),
|
||||
|
||||
fileUrl,
|
||||
fileDownloadUrl: videoFile.getFileDownloadUrl(video),
|
||||
|
||||
metadataUrl: videoFile.metadataUrl ?? getLocalVideoFileMetadataUrl(video, videoFile),
|
||||
|
||||
hasAudio: videoFile.hasAudio(),
|
||||
hasVideo: videoFile.hasVideo(),
|
||||
|
||||
playlistUrl: includePlaylistUrl === true
|
||||
? getHLSResolutionPlaylistFilename(fileUrl)
|
||||
: undefined,
|
||||
|
||||
storage: video.remote
|
||||
? null
|
||||
: videoFile.storage
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function getCategoryLabel (id: number) {
|
||||
return VIDEO_CATEGORIES[id] || 'Unknown'
|
||||
}
|
||||
|
||||
export function getLicenceLabel (id: number) {
|
||||
return VIDEO_LICENCES[id] || 'Unknown'
|
||||
}
|
||||
|
||||
export function getLanguageLabel (id: string) {
|
||||
return VIDEO_LANGUAGES[id] || 'Unknown'
|
||||
}
|
||||
|
||||
export function getPrivacyLabel (id: number) {
|
||||
return VIDEO_PRIVACIES[id] || 'Unknown'
|
||||
}
|
||||
|
||||
export function getStateLabel (id: number) {
|
||||
return VIDEO_STATES[id] || 'Unknown'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildAdditionalAttributes (video: MVideoFormattable, options: VideoFormattingJSONOptions) {
|
||||
const add = options.additionalAttributes
|
||||
|
||||
const result: Partial<VideoAdditionalAttributes> = {}
|
||||
|
||||
if (add?.state === true) {
|
||||
result.state = {
|
||||
id: video.state,
|
||||
label: getStateLabel(video.state)
|
||||
}
|
||||
}
|
||||
|
||||
if (add?.waitTranscoding === true) {
|
||||
result.waitTranscoding = video.waitTranscoding
|
||||
}
|
||||
|
||||
if (add?.scheduledUpdate === true && video.ScheduleVideoUpdate) {
|
||||
result.scheduledUpdate = {
|
||||
updateAt: video.ScheduleVideoUpdate.updateAt,
|
||||
privacy: video.ScheduleVideoUpdate.privacy || undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (add?.blacklistInfo === true) {
|
||||
result.blacklisted = !!video.VideoBlacklist
|
||||
result.blacklistedReason = video.VideoBlacklist
|
||||
? video.VideoBlacklist.reason
|
||||
: null
|
||||
}
|
||||
|
||||
if (add?.blockedOwner === true) {
|
||||
result.blockedOwner = video.VideoChannel.Account.isBlocked()
|
||||
|
||||
const server = video.VideoChannel.Account.Actor.Server as MServer
|
||||
result.blockedServer = !!(server?.isBlocked())
|
||||
}
|
||||
|
||||
if (add?.files === true) {
|
||||
result.streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists)
|
||||
result.files = videoFilesModelToFormattedJSON(video, video.VideoFiles)
|
||||
}
|
||||
|
||||
if (add?.source === true) {
|
||||
result.videoSource = video.VideoSource?.toFormattedJSON() || null
|
||||
}
|
||||
|
||||
if (add?.automaticTags === true) {
|
||||
result.automaticTags = (video.VideoAutomaticTags || []).map(t => t.AutomaticTag.name)
|
||||
}
|
||||
|
||||
if (add?.liveSchedules === true) {
|
||||
result.liveSchedules = (video.VideoLive?.LiveSchedules || []).map(s => s.toFormattedJSON())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user