aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video/formatter/video-api-format.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video/formatter/video-api-format.ts')
-rw-r--r--server/models/video/formatter/video-api-format.ts304
1 files changed, 304 insertions, 0 deletions
diff --git a/server/models/video/formatter/video-api-format.ts b/server/models/video/formatter/video-api-format.ts
new file mode 100644
index 000000000..1af51d132
--- /dev/null
+++ b/server/models/video/formatter/video-api-format.ts
@@ -0,0 +1,304 @@
1import { generateMagnetUri } from '@server/helpers/webtorrent'
2import { tracer } from '@server/lib/opentelemetry/tracing'
3import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls'
4import { VideoViewsManager } from '@server/lib/views/video-views-manager'
5import { uuidToShort } from '@shared/extra-utils'
6import {
7 Video,
8 VideoAdditionalAttributes,
9 VideoDetails,
10 VideoFile,
11 VideoInclude,
12 VideosCommonQueryAfterSanitize,
13 VideoStreamingPlaylist
14} from '@shared/models'
15import { isArray } from '../../../helpers/custom-validators/misc'
16import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES, VIDEO_STATES } from '../../../initializers/constants'
17import { MServer, MStreamingPlaylistRedundanciesOpt, MVideoFormattable, MVideoFormattableDetails } from '../../../types/models'
18import { MVideoFileRedundanciesOpt } from '../../../types/models/video/video-file'
19import { sortByResolutionDesc } from './shared'
20
21export type VideoFormattingJSONOptions = {
22 completeDescription?: boolean
23
24 additionalAttributes?: {
25 state?: boolean
26 waitTranscoding?: boolean
27 scheduledUpdate?: boolean
28 blacklistInfo?: boolean
29 files?: boolean
30 blockedOwner?: boolean
31 }
32}
33
34export function guessAdditionalAttributesFromQuery (query: VideosCommonQueryAfterSanitize): VideoFormattingJSONOptions {
35 if (!query?.include) return {}
36
37 return {
38 additionalAttributes: {
39 state: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
40 waitTranscoding: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
41 scheduledUpdate: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
42 blacklistInfo: !!(query.include & VideoInclude.BLACKLISTED),
43 files: !!(query.include & VideoInclude.FILES),
44 blockedOwner: !!(query.include & VideoInclude.BLOCKED_OWNER)
45 }
46 }
47}
48
49// ---------------------------------------------------------------------------
50
51export function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoFormattingJSONOptions = {}): Video {
52 const span = tracer.startSpan('peertube.VideoModel.toFormattedJSON')
53
54 const userHistory = isArray(video.UserVideoHistories)
55 ? video.UserVideoHistories[0]
56 : undefined
57
58 const videoObject: Video = {
59 id: video.id,
60 uuid: video.uuid,
61 shortUUID: uuidToShort(video.uuid),
62
63 url: video.url,
64
65 name: video.name,
66 category: {
67 id: video.category,
68 label: getCategoryLabel(video.category)
69 },
70 licence: {
71 id: video.licence,
72 label: getLicenceLabel(video.licence)
73 },
74 language: {
75 id: video.language,
76 label: getLanguageLabel(video.language)
77 },
78 privacy: {
79 id: video.privacy,
80 label: getPrivacyLabel(video.privacy)
81 },
82 nsfw: video.nsfw,
83
84 truncatedDescription: video.getTruncatedDescription(),
85 description: options && options.completeDescription === true
86 ? video.description
87 : video.getTruncatedDescription(),
88
89 isLocal: video.isOwned(),
90 duration: video.duration,
91
92 views: video.views,
93 viewers: VideoViewsManager.Instance.getViewers(video),
94
95 likes: video.likes,
96 dislikes: video.dislikes,
97 thumbnailPath: video.getMiniatureStaticPath(),
98 previewPath: video.getPreviewStaticPath(),
99 embedPath: video.getEmbedStaticPath(),
100 createdAt: video.createdAt,
101 updatedAt: video.updatedAt,
102 publishedAt: video.publishedAt,
103 originallyPublishedAt: video.originallyPublishedAt,
104
105 isLive: video.isLive,
106
107 account: video.VideoChannel.Account.toFormattedSummaryJSON(),
108 channel: video.VideoChannel.toFormattedSummaryJSON(),
109
110 userHistory: userHistory
111 ? { currentTime: userHistory.currentTime }
112 : undefined,
113
114 // Can be added by external plugins
115 pluginData: (video as any).pluginData,
116
117 ...buildAdditionalAttributes(video, options)
118 }
119
120 span.end()
121
122 return videoObject
123}
124
125export function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails {
126 const span = tracer.startSpan('peertube.VideoModel.toFormattedDetailsJSON')
127
128 const videoJSON = video.toFormattedJSON({
129 completeDescription: true,
130 additionalAttributes: {
131 scheduledUpdate: true,
132 blacklistInfo: true,
133 files: true
134 }
135 }) as Video & Required<Pick<Video, 'files' | 'streamingPlaylists' | 'scheduledUpdate' | 'blacklisted' | 'blacklistedReason'>>
136
137 const tags = video.Tags
138 ? video.Tags.map(t => t.name)
139 : []
140
141 const detailsJSON = {
142 ...videoJSON,
143
144 support: video.support,
145 descriptionPath: video.getDescriptionAPIPath(),
146 channel: video.VideoChannel.toFormattedJSON(),
147 account: video.VideoChannel.Account.toFormattedJSON(),
148 tags,
149 commentsEnabled: video.commentsEnabled,
150 downloadEnabled: video.downloadEnabled,
151 waitTranscoding: video.waitTranscoding,
152 state: {
153 id: video.state,
154 label: getStateLabel(video.state)
155 },
156
157 trackerUrls: video.getTrackerUrls()
158 }
159
160 span.end()
161
162 return detailsJSON
163}
164
165export function streamingPlaylistsModelToFormattedJSON (
166 video: MVideoFormattable,
167 playlists: MStreamingPlaylistRedundanciesOpt[]
168): VideoStreamingPlaylist[] {
169 if (isArray(playlists) === false) return []
170
171 return playlists
172 .map(playlist => ({
173 id: playlist.id,
174 type: playlist.type,
175
176 playlistUrl: playlist.getMasterPlaylistUrl(video),
177 segmentsSha256Url: playlist.getSha256SegmentsUrl(video),
178
179 redundancies: isArray(playlist.RedundancyVideos)
180 ? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl }))
181 : [],
182
183 files: videoFilesModelToFormattedJSON(video, playlist.VideoFiles)
184 }))
185}
186
187export function videoFilesModelToFormattedJSON (
188 video: MVideoFormattable,
189 videoFiles: MVideoFileRedundanciesOpt[],
190 options: {
191 includeMagnet?: boolean // default true
192 } = {}
193): VideoFile[] {
194 const { includeMagnet = true } = options
195
196 if (isArray(videoFiles) === false) return []
197
198 const trackerUrls = includeMagnet
199 ? video.getTrackerUrls()
200 : []
201
202 return videoFiles
203 .filter(f => !f.isLive())
204 .sort(sortByResolutionDesc)
205 .map(videoFile => {
206 return {
207 id: videoFile.id,
208
209 resolution: {
210 id: videoFile.resolution,
211 label: videoFile.resolution === 0
212 ? 'Audio'
213 : `${videoFile.resolution}p`
214 },
215
216 magnetUri: includeMagnet && videoFile.hasTorrent()
217 ? generateMagnetUri(video, videoFile, trackerUrls)
218 : undefined,
219
220 size: videoFile.size,
221 fps: videoFile.fps,
222
223 torrentUrl: videoFile.getTorrentUrl(),
224 torrentDownloadUrl: videoFile.getTorrentDownloadUrl(),
225
226 fileUrl: videoFile.getFileUrl(video),
227 fileDownloadUrl: videoFile.getFileDownloadUrl(video),
228
229 metadataUrl: videoFile.metadataUrl ?? getLocalVideoFileMetadataUrl(video, videoFile)
230 }
231 })
232}
233
234// ---------------------------------------------------------------------------
235
236export function getCategoryLabel (id: number) {
237 return VIDEO_CATEGORIES[id] || 'Unknown'
238}
239
240export function getLicenceLabel (id: number) {
241 return VIDEO_LICENCES[id] || 'Unknown'
242}
243
244export function getLanguageLabel (id: string) {
245 return VIDEO_LANGUAGES[id] || 'Unknown'
246}
247
248export function getPrivacyLabel (id: number) {
249 return VIDEO_PRIVACIES[id] || 'Unknown'
250}
251
252export function getStateLabel (id: number) {
253 return VIDEO_STATES[id] || 'Unknown'
254}
255
256// ---------------------------------------------------------------------------
257// Private
258// ---------------------------------------------------------------------------
259
260function buildAdditionalAttributes (video: MVideoFormattable, options: VideoFormattingJSONOptions) {
261 const add = options.additionalAttributes
262
263 const result: Partial<VideoAdditionalAttributes> = {}
264
265 if (add?.state === true) {
266 result.state = {
267 id: video.state,
268 label: getStateLabel(video.state)
269 }
270 }
271
272 if (add?.waitTranscoding === true) {
273 result.waitTranscoding = video.waitTranscoding
274 }
275
276 if (add?.scheduledUpdate === true && video.ScheduleVideoUpdate) {
277 result.scheduledUpdate = {
278 updateAt: video.ScheduleVideoUpdate.updateAt,
279 privacy: video.ScheduleVideoUpdate.privacy || undefined
280 }
281 }
282
283 if (add?.blacklistInfo === true) {
284 result.blacklisted = !!video.VideoBlacklist
285 result.blacklistedReason =
286 video.VideoBlacklist
287 ? video.VideoBlacklist.reason
288 : null
289 }
290
291 if (add?.blockedOwner === true) {
292 result.blockedOwner = video.VideoChannel.Account.isBlocked()
293
294 const server = video.VideoChannel.Account.Actor.Server as MServer
295 result.blockedServer = !!(server?.isBlocked())
296 }
297
298 if (add?.files === true) {
299 result.streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists)
300 result.files = videoFilesModelToFormattedJSON(video, video.VideoFiles)
301 }
302
303 return result
304}