diff options
Diffstat (limited to 'server/controllers/api/video-playlist.ts')
-rw-r--r-- | server/controllers/api/video-playlist.ts | 514 |
1 files changed, 0 insertions, 514 deletions
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts deleted file mode 100644 index 73362e1e3..000000000 --- a/server/controllers/api/video-playlist.ts +++ /dev/null | |||
@@ -1,514 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists' | ||
3 | import { VideoMiniaturePermanentFileCache } from '@server/lib/files-cache' | ||
4 | import { Hooks } from '@server/lib/plugins/hooks' | ||
5 | import { getServerActor } from '@server/models/application/application' | ||
6 | import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models' | ||
7 | import { forceNumber } from '@shared/core-utils' | ||
8 | import { uuidToShort } from '@shared/extra-utils' | ||
9 | import { VideoPlaylistCreateResult, VideoPlaylistElementCreateResult } from '@shared/models' | ||
10 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | ||
11 | import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model' | ||
12 | import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model' | ||
13 | import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model' | ||
14 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
15 | import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/video-playlist-reorder.model' | ||
16 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' | ||
17 | import { resetSequelizeInstance } from '../../helpers/database-utils' | ||
18 | import { createReqFiles } from '../../helpers/express-utils' | ||
19 | import { logger } from '../../helpers/logger' | ||
20 | import { getFormattedObjects } from '../../helpers/utils' | ||
21 | import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants' | ||
22 | import { sequelizeTypescript } from '../../initializers/database' | ||
23 | import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send' | ||
24 | import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' | ||
25 | import { updateLocalPlaylistMiniatureFromExisting } from '../../lib/thumbnail' | ||
26 | import { | ||
27 | apiRateLimiter, | ||
28 | asyncMiddleware, | ||
29 | asyncRetryTransactionMiddleware, | ||
30 | authenticate, | ||
31 | optionalAuthenticate, | ||
32 | paginationValidator, | ||
33 | setDefaultPagination, | ||
34 | setDefaultSort | ||
35 | } from '../../middlewares' | ||
36 | import { videoPlaylistsSortValidator } from '../../middlewares/validators' | ||
37 | import { | ||
38 | commonVideoPlaylistFiltersValidator, | ||
39 | videoPlaylistsAddValidator, | ||
40 | videoPlaylistsAddVideoValidator, | ||
41 | videoPlaylistsDeleteValidator, | ||
42 | videoPlaylistsGetValidator, | ||
43 | videoPlaylistsReorderVideosValidator, | ||
44 | videoPlaylistsUpdateOrRemoveVideoValidator, | ||
45 | videoPlaylistsUpdateValidator | ||
46 | } from '../../middlewares/validators/videos/video-playlists' | ||
47 | import { AccountModel } from '../../models/account/account' | ||
48 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
49 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | ||
50 | |||
51 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) | ||
52 | |||
53 | const videoPlaylistRouter = express.Router() | ||
54 | |||
55 | videoPlaylistRouter.use(apiRateLimiter) | ||
56 | |||
57 | videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies) | ||
58 | |||
59 | videoPlaylistRouter.get('/', | ||
60 | paginationValidator, | ||
61 | videoPlaylistsSortValidator, | ||
62 | setDefaultSort, | ||
63 | setDefaultPagination, | ||
64 | commonVideoPlaylistFiltersValidator, | ||
65 | asyncMiddleware(listVideoPlaylists) | ||
66 | ) | ||
67 | |||
68 | videoPlaylistRouter.get('/:playlistId', | ||
69 | asyncMiddleware(videoPlaylistsGetValidator('summary')), | ||
70 | getVideoPlaylist | ||
71 | ) | ||
72 | |||
73 | videoPlaylistRouter.post('/', | ||
74 | authenticate, | ||
75 | reqThumbnailFile, | ||
76 | asyncMiddleware(videoPlaylistsAddValidator), | ||
77 | asyncRetryTransactionMiddleware(addVideoPlaylist) | ||
78 | ) | ||
79 | |||
80 | videoPlaylistRouter.put('/:playlistId', | ||
81 | authenticate, | ||
82 | reqThumbnailFile, | ||
83 | asyncMiddleware(videoPlaylistsUpdateValidator), | ||
84 | asyncRetryTransactionMiddleware(updateVideoPlaylist) | ||
85 | ) | ||
86 | |||
87 | videoPlaylistRouter.delete('/:playlistId', | ||
88 | authenticate, | ||
89 | asyncMiddleware(videoPlaylistsDeleteValidator), | ||
90 | asyncRetryTransactionMiddleware(removeVideoPlaylist) | ||
91 | ) | ||
92 | |||
93 | videoPlaylistRouter.get('/:playlistId/videos', | ||
94 | asyncMiddleware(videoPlaylistsGetValidator('summary')), | ||
95 | paginationValidator, | ||
96 | setDefaultPagination, | ||
97 | optionalAuthenticate, | ||
98 | asyncMiddleware(getVideoPlaylistVideos) | ||
99 | ) | ||
100 | |||
101 | videoPlaylistRouter.post('/:playlistId/videos', | ||
102 | authenticate, | ||
103 | asyncMiddleware(videoPlaylistsAddVideoValidator), | ||
104 | asyncRetryTransactionMiddleware(addVideoInPlaylist) | ||
105 | ) | ||
106 | |||
107 | videoPlaylistRouter.post('/:playlistId/videos/reorder', | ||
108 | authenticate, | ||
109 | asyncMiddleware(videoPlaylistsReorderVideosValidator), | ||
110 | asyncRetryTransactionMiddleware(reorderVideosPlaylist) | ||
111 | ) | ||
112 | |||
113 | videoPlaylistRouter.put('/:playlistId/videos/:playlistElementId', | ||
114 | authenticate, | ||
115 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | ||
116 | asyncRetryTransactionMiddleware(updateVideoPlaylistElement) | ||
117 | ) | ||
118 | |||
119 | videoPlaylistRouter.delete('/:playlistId/videos/:playlistElementId', | ||
120 | authenticate, | ||
121 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | ||
122 | asyncRetryTransactionMiddleware(removeVideoFromPlaylist) | ||
123 | ) | ||
124 | |||
125 | // --------------------------------------------------------------------------- | ||
126 | |||
127 | export { | ||
128 | videoPlaylistRouter | ||
129 | } | ||
130 | |||
131 | // --------------------------------------------------------------------------- | ||
132 | |||
133 | function listVideoPlaylistPrivacies (req: express.Request, res: express.Response) { | ||
134 | res.json(VIDEO_PLAYLIST_PRIVACIES) | ||
135 | } | ||
136 | |||
137 | async function listVideoPlaylists (req: express.Request, res: express.Response) { | ||
138 | const serverActor = await getServerActor() | ||
139 | const resultList = await VideoPlaylistModel.listForApi({ | ||
140 | followerActorId: serverActor.id, | ||
141 | start: req.query.start, | ||
142 | count: req.query.count, | ||
143 | sort: req.query.sort, | ||
144 | type: req.query.playlistType | ||
145 | }) | ||
146 | |||
147 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
148 | } | ||
149 | |||
150 | function getVideoPlaylist (req: express.Request, res: express.Response) { | ||
151 | const videoPlaylist = res.locals.videoPlaylistSummary | ||
152 | |||
153 | scheduleRefreshIfNeeded(videoPlaylist) | ||
154 | |||
155 | return res.json(videoPlaylist.toFormattedJSON()) | ||
156 | } | ||
157 | |||
158 | async function addVideoPlaylist (req: express.Request, res: express.Response) { | ||
159 | const videoPlaylistInfo: VideoPlaylistCreate = req.body | ||
160 | const user = res.locals.oauth.token.User | ||
161 | |||
162 | const videoPlaylist = new VideoPlaylistModel({ | ||
163 | name: videoPlaylistInfo.displayName, | ||
164 | description: videoPlaylistInfo.description, | ||
165 | privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, | ||
166 | ownerAccountId: user.Account.id | ||
167 | }) as MVideoPlaylistFull | ||
168 | |||
169 | videoPlaylist.url = getLocalVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object | ||
170 | |||
171 | if (videoPlaylistInfo.videoChannelId) { | ||
172 | const videoChannel = res.locals.videoChannel | ||
173 | |||
174 | videoPlaylist.videoChannelId = videoChannel.id | ||
175 | videoPlaylist.VideoChannel = videoChannel | ||
176 | } | ||
177 | |||
178 | const thumbnailField = req.files['thumbnailfile'] | ||
179 | const thumbnailModel = thumbnailField | ||
180 | ? await updateLocalPlaylistMiniatureFromExisting({ | ||
181 | inputPath: thumbnailField[0].path, | ||
182 | playlist: videoPlaylist, | ||
183 | automaticallyGenerated: false | ||
184 | }) | ||
185 | : undefined | ||
186 | |||
187 | const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => { | ||
188 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) as MVideoPlaylistFull | ||
189 | |||
190 | if (thumbnailModel) { | ||
191 | thumbnailModel.automaticallyGenerated = false | ||
192 | await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t) | ||
193 | } | ||
194 | |||
195 | // We need more attributes for the federation | ||
196 | videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t) | ||
197 | await sendCreateVideoPlaylist(videoPlaylistCreated, t) | ||
198 | |||
199 | return videoPlaylistCreated | ||
200 | }) | ||
201 | |||
202 | logger.info('Video playlist with uuid %s created.', videoPlaylist.uuid) | ||
203 | |||
204 | return res.json({ | ||
205 | videoPlaylist: { | ||
206 | id: videoPlaylistCreated.id, | ||
207 | shortUUID: uuidToShort(videoPlaylistCreated.uuid), | ||
208 | uuid: videoPlaylistCreated.uuid | ||
209 | } as VideoPlaylistCreateResult | ||
210 | }) | ||
211 | } | ||
212 | |||
213 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { | ||
214 | const videoPlaylistInstance = res.locals.videoPlaylistFull | ||
215 | const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate | ||
216 | |||
217 | const wasPrivatePlaylist = videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE | ||
218 | const wasNotPrivatePlaylist = videoPlaylistInstance.privacy !== VideoPlaylistPrivacy.PRIVATE | ||
219 | |||
220 | const thumbnailField = req.files['thumbnailfile'] | ||
221 | const thumbnailModel = thumbnailField | ||
222 | ? await updateLocalPlaylistMiniatureFromExisting({ | ||
223 | inputPath: thumbnailField[0].path, | ||
224 | playlist: videoPlaylistInstance, | ||
225 | automaticallyGenerated: false | ||
226 | }) | ||
227 | : undefined | ||
228 | |||
229 | try { | ||
230 | await sequelizeTypescript.transaction(async t => { | ||
231 | const sequelizeOptions = { | ||
232 | transaction: t | ||
233 | } | ||
234 | |||
235 | if (videoPlaylistInfoToUpdate.videoChannelId !== undefined) { | ||
236 | if (videoPlaylistInfoToUpdate.videoChannelId === null) { | ||
237 | videoPlaylistInstance.videoChannelId = null | ||
238 | } else { | ||
239 | const videoChannel = res.locals.videoChannel | ||
240 | |||
241 | videoPlaylistInstance.videoChannelId = videoChannel.id | ||
242 | videoPlaylistInstance.VideoChannel = videoChannel | ||
243 | } | ||
244 | } | ||
245 | |||
246 | if (videoPlaylistInfoToUpdate.displayName !== undefined) videoPlaylistInstance.name = videoPlaylistInfoToUpdate.displayName | ||
247 | if (videoPlaylistInfoToUpdate.description !== undefined) videoPlaylistInstance.description = videoPlaylistInfoToUpdate.description | ||
248 | |||
249 | if (videoPlaylistInfoToUpdate.privacy !== undefined) { | ||
250 | videoPlaylistInstance.privacy = forceNumber(videoPlaylistInfoToUpdate.privacy) | ||
251 | |||
252 | if (wasNotPrivatePlaylist === true && videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE) { | ||
253 | await sendDeleteVideoPlaylist(videoPlaylistInstance, t) | ||
254 | } | ||
255 | } | ||
256 | |||
257 | const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions) | ||
258 | |||
259 | if (thumbnailModel) { | ||
260 | thumbnailModel.automaticallyGenerated = false | ||
261 | await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t) | ||
262 | } | ||
263 | |||
264 | const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE | ||
265 | |||
266 | if (isNewPlaylist) { | ||
267 | await sendCreateVideoPlaylist(playlistUpdated, t) | ||
268 | } else { | ||
269 | await sendUpdateVideoPlaylist(playlistUpdated, t) | ||
270 | } | ||
271 | |||
272 | logger.info('Video playlist %s updated.', videoPlaylistInstance.uuid) | ||
273 | |||
274 | return playlistUpdated | ||
275 | }) | ||
276 | } catch (err) { | ||
277 | logger.debug('Cannot update the video playlist.', { err }) | ||
278 | |||
279 | // If the transaction is retried, sequelize will think the object has not changed | ||
280 | // So we need to restore the previous fields | ||
281 | await resetSequelizeInstance(videoPlaylistInstance) | ||
282 | |||
283 | throw err | ||
284 | } | ||
285 | |||
286 | return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() | ||
287 | } | ||
288 | |||
289 | async function removeVideoPlaylist (req: express.Request, res: express.Response) { | ||
290 | const videoPlaylistInstance = res.locals.videoPlaylistSummary | ||
291 | |||
292 | await sequelizeTypescript.transaction(async t => { | ||
293 | await videoPlaylistInstance.destroy({ transaction: t }) | ||
294 | |||
295 | await sendDeleteVideoPlaylist(videoPlaylistInstance, t) | ||
296 | |||
297 | logger.info('Video playlist %s deleted.', videoPlaylistInstance.uuid) | ||
298 | }) | ||
299 | |||
300 | return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() | ||
301 | } | ||
302 | |||
303 | async function addVideoInPlaylist (req: express.Request, res: express.Response) { | ||
304 | const body: VideoPlaylistElementCreate = req.body | ||
305 | const videoPlaylist = res.locals.videoPlaylistFull | ||
306 | const video = res.locals.onlyVideo | ||
307 | |||
308 | const playlistElement = await sequelizeTypescript.transaction(async t => { | ||
309 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) | ||
310 | |||
311 | const playlistElement = await VideoPlaylistElementModel.create({ | ||
312 | position, | ||
313 | startTimestamp: body.startTimestamp || null, | ||
314 | stopTimestamp: body.stopTimestamp || null, | ||
315 | videoPlaylistId: videoPlaylist.id, | ||
316 | videoId: video.id | ||
317 | }, { transaction: t }) | ||
318 | |||
319 | playlistElement.url = getLocalVideoPlaylistElementActivityPubUrl(videoPlaylist, playlistElement) | ||
320 | await playlistElement.save({ transaction: t }) | ||
321 | |||
322 | videoPlaylist.changed('updatedAt', true) | ||
323 | await videoPlaylist.save({ transaction: t }) | ||
324 | |||
325 | return playlistElement | ||
326 | }) | ||
327 | |||
328 | // If the user did not set a thumbnail, automatically take the video thumbnail | ||
329 | if (videoPlaylist.hasThumbnail() === false || (videoPlaylist.hasGeneratedThumbnail() && playlistElement.position === 1)) { | ||
330 | await generateThumbnailForPlaylist(videoPlaylist, video) | ||
331 | } | ||
332 | |||
333 | sendUpdateVideoPlaylist(videoPlaylist, undefined) | ||
334 | .catch(err => logger.error('Cannot send video playlist update.', { err })) | ||
335 | |||
336 | logger.info('Video added in playlist %s at position %d.', videoPlaylist.uuid, playlistElement.position) | ||
337 | |||
338 | Hooks.runAction('action:api.video-playlist-element.created', { playlistElement, req, res }) | ||
339 | |||
340 | return res.json({ | ||
341 | videoPlaylistElement: { | ||
342 | id: playlistElement.id | ||
343 | } as VideoPlaylistElementCreateResult | ||
344 | }) | ||
345 | } | ||
346 | |||
347 | async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { | ||
348 | const body: VideoPlaylistElementUpdate = req.body | ||
349 | const videoPlaylist = res.locals.videoPlaylistFull | ||
350 | const videoPlaylistElement = res.locals.videoPlaylistElement | ||
351 | |||
352 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { | ||
353 | if (body.startTimestamp !== undefined) videoPlaylistElement.startTimestamp = body.startTimestamp | ||
354 | if (body.stopTimestamp !== undefined) videoPlaylistElement.stopTimestamp = body.stopTimestamp | ||
355 | |||
356 | const element = await videoPlaylistElement.save({ transaction: t }) | ||
357 | |||
358 | videoPlaylist.changed('updatedAt', true) | ||
359 | await videoPlaylist.save({ transaction: t }) | ||
360 | |||
361 | await sendUpdateVideoPlaylist(videoPlaylist, t) | ||
362 | |||
363 | return element | ||
364 | }) | ||
365 | |||
366 | logger.info('Element of position %d of playlist %s updated.', playlistElement.position, videoPlaylist.uuid) | ||
367 | |||
368 | return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() | ||
369 | } | ||
370 | |||
371 | async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { | ||
372 | const videoPlaylistElement = res.locals.videoPlaylistElement | ||
373 | const videoPlaylist = res.locals.videoPlaylistFull | ||
374 | const positionToDelete = videoPlaylistElement.position | ||
375 | |||
376 | await sequelizeTypescript.transaction(async t => { | ||
377 | await videoPlaylistElement.destroy({ transaction: t }) | ||
378 | |||
379 | // Decrease position of the next elements | ||
380 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, positionToDelete, -1, t) | ||
381 | |||
382 | videoPlaylist.changed('updatedAt', true) | ||
383 | await videoPlaylist.save({ transaction: t }) | ||
384 | |||
385 | logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid) | ||
386 | }) | ||
387 | |||
388 | // Do we need to regenerate the default thumbnail? | ||
389 | if (positionToDelete === 1 && videoPlaylist.hasGeneratedThumbnail()) { | ||
390 | await regeneratePlaylistThumbnail(videoPlaylist) | ||
391 | } | ||
392 | |||
393 | sendUpdateVideoPlaylist(videoPlaylist, undefined) | ||
394 | .catch(err => logger.error('Cannot send video playlist update.', { err })) | ||
395 | |||
396 | return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() | ||
397 | } | ||
398 | |||
399 | async function reorderVideosPlaylist (req: express.Request, res: express.Response) { | ||
400 | const videoPlaylist = res.locals.videoPlaylistFull | ||
401 | const body: VideoPlaylistReorder = req.body | ||
402 | |||
403 | const start: number = body.startPosition | ||
404 | const insertAfter: number = body.insertAfterPosition | ||
405 | const reorderLength: number = body.reorderLength || 1 | ||
406 | |||
407 | if (start === insertAfter) { | ||
408 | return res.status(HttpStatusCode.NO_CONTENT_204).end() | ||
409 | } | ||
410 | |||
411 | // Example: if we reorder position 2 and insert after position 5 (so at position 6): # 1 2 3 4 5 6 7 8 9 | ||
412 | // * increase position when position > 5 # 1 2 3 4 5 7 8 9 10 | ||
413 | // * update position 2 -> position 6 # 1 3 4 5 6 7 8 9 10 | ||
414 | // * decrease position when position position > 2 # 1 2 3 4 5 6 7 8 9 | ||
415 | await sequelizeTypescript.transaction(async t => { | ||
416 | const newPosition = insertAfter + 1 | ||
417 | |||
418 | // Add space after the position when we want to insert our reordered elements (increase) | ||
419 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, newPosition, reorderLength, t) | ||
420 | |||
421 | let oldPosition = start | ||
422 | |||
423 | // We incremented the position of the elements we want to reorder | ||
424 | if (start >= newPosition) oldPosition += reorderLength | ||
425 | |||
426 | const endOldPosition = oldPosition + reorderLength - 1 | ||
427 | // Insert our reordered elements in their place (update) | ||
428 | await VideoPlaylistElementModel.reassignPositionOf({ | ||
429 | videoPlaylistId: videoPlaylist.id, | ||
430 | firstPosition: oldPosition, | ||
431 | endPosition: endOldPosition, | ||
432 | newPosition, | ||
433 | transaction: t | ||
434 | }) | ||
435 | |||
436 | // Decrease positions of elements after the old position of our ordered elements (decrease) | ||
437 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, -reorderLength, t) | ||
438 | |||
439 | videoPlaylist.changed('updatedAt', true) | ||
440 | await videoPlaylist.save({ transaction: t }) | ||
441 | |||
442 | await sendUpdateVideoPlaylist(videoPlaylist, t) | ||
443 | }) | ||
444 | |||
445 | // The first element changed | ||
446 | if ((start === 1 || insertAfter === 0) && videoPlaylist.hasGeneratedThumbnail()) { | ||
447 | await regeneratePlaylistThumbnail(videoPlaylist) | ||
448 | } | ||
449 | |||
450 | logger.info( | ||
451 | 'Reordered playlist %s (inserted after position %d elements %d - %d).', | ||
452 | videoPlaylist.uuid, insertAfter, start, start + reorderLength - 1 | ||
453 | ) | ||
454 | |||
455 | return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() | ||
456 | } | ||
457 | |||
458 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { | ||
459 | const videoPlaylistInstance = res.locals.videoPlaylistSummary | ||
460 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | ||
461 | const server = await getServerActor() | ||
462 | |||
463 | const apiOptions = await Hooks.wrapObject({ | ||
464 | start: req.query.start, | ||
465 | count: req.query.count, | ||
466 | videoPlaylistId: videoPlaylistInstance.id, | ||
467 | serverAccount: server.Account, | ||
468 | user | ||
469 | }, 'filter:api.video-playlist.videos.list.params') | ||
470 | |||
471 | const resultList = await Hooks.wrapPromiseFun( | ||
472 | VideoPlaylistElementModel.listForApi, | ||
473 | apiOptions, | ||
474 | 'filter:api.video-playlist.videos.list.result' | ||
475 | ) | ||
476 | |||
477 | const options = { accountId: user?.Account?.id } | ||
478 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) | ||
479 | } | ||
480 | |||
481 | async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbnail) { | ||
482 | await videoPlaylist.Thumbnail.destroy() | ||
483 | videoPlaylist.Thumbnail = null | ||
484 | |||
485 | const firstElement = await VideoPlaylistElementModel.loadFirstElementWithVideoThumbnail(videoPlaylist.id) | ||
486 | if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video) | ||
487 | } | ||
488 | |||
489 | async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) { | ||
490 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) | ||
491 | |||
492 | const videoMiniature = video.getMiniature() | ||
493 | if (!videoMiniature) { | ||
494 | logger.info('Cannot generate thumbnail for playlist %s because video %s does not have any.', videoPlaylist.url, video.url) | ||
495 | return | ||
496 | } | ||
497 | |||
498 | // Ensure the file is on disk | ||
499 | const videoMiniaturePermanentFileCache = new VideoMiniaturePermanentFileCache() | ||
500 | const inputPath = videoMiniature.isOwned() | ||
501 | ? videoMiniature.getPath() | ||
502 | : await videoMiniaturePermanentFileCache.downloadRemoteFile(videoMiniature) | ||
503 | |||
504 | const thumbnailModel = await updateLocalPlaylistMiniatureFromExisting({ | ||
505 | inputPath, | ||
506 | playlist: videoPlaylist, | ||
507 | automaticallyGenerated: true, | ||
508 | keepOriginal: true | ||
509 | }) | ||
510 | |||
511 | thumbnailModel.videoPlaylistId = videoPlaylist.id | ||
512 | |||
513 | videoPlaylist.Thumbnail = await thumbnailModel.save() | ||
514 | } | ||