aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/activitypub/client.ts13
-rw-r--r--server/controllers/activitypub/outbox.ts1
-rw-r--r--server/controllers/api/accounts.ts2
-rw-r--r--server/controllers/api/config.ts7
-rw-r--r--server/controllers/api/index.ts11
-rw-r--r--server/controllers/api/runners/jobs-files.ts4
-rw-r--r--server/controllers/api/search/search-videos.ts2
-rw-r--r--server/controllers/api/users/me.ts9
-rw-r--r--server/controllers/api/users/my-subscriptions.ts4
-rw-r--r--server/controllers/api/video-channel.ts2
-rw-r--r--server/controllers/api/video-playlist.ts18
-rw-r--r--server/controllers/api/videos/files.ts34
-rw-r--r--server/controllers/api/videos/import.ts7
-rw-r--r--server/controllers/api/videos/index.ts6
-rw-r--r--server/controllers/api/videos/live.ts11
-rw-r--r--server/controllers/api/videos/passwords.ts105
-rw-r--r--server/controllers/api/videos/storyboard.ts29
-rw-r--r--server/controllers/api/videos/token.ts16
-rw-r--r--server/controllers/api/videos/update.ts16
-rw-r--r--server/controllers/api/videos/upload.ts26
-rw-r--r--server/controllers/download.ts43
-rw-r--r--server/controllers/feeds/shared/video-feed-utils.ts2
-rw-r--r--server/controllers/lazy-static.ts118
-rw-r--r--server/controllers/misc.ts4
-rw-r--r--server/controllers/object-storage-proxy.ts15
-rw-r--r--server/controllers/static.ts16
26 files changed, 346 insertions, 175 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 750e3091c..c47c61f52 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -4,6 +4,7 @@ import { activityPubCollectionPagination } from '@server/lib/activitypub/collect
4import { activityPubContextify } from '@server/lib/activitypub/context' 4import { activityPubContextify } from '@server/lib/activitypub/context'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models' 6import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models'
7import { VideoCommentObject } from '@shared/models'
7import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' 8import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos'
8import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 9import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
9import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' 10import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants'
@@ -33,7 +34,6 @@ import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '
33import { AccountModel } from '../../models/account/account' 34import { AccountModel } from '../../models/account/account'
34import { AccountVideoRateModel } from '../../models/account/account-video-rate' 35import { AccountVideoRateModel } from '../../models/account/account-video-rate'
35import { ActorFollowModel } from '../../models/actor/actor-follow' 36import { ActorFollowModel } from '../../models/actor/actor-follow'
36import { VideoCaptionModel } from '../../models/video/video-caption'
37import { VideoCommentModel } from '../../models/video/video-comment' 37import { VideoCommentModel } from '../../models/video/video-comment'
38import { VideoPlaylistModel } from '../../models/video/video-playlist' 38import { VideoPlaylistModel } from '../../models/video/video-playlist'
39import { VideoShareModel } from '../../models/video/video-share' 39import { VideoShareModel } from '../../models/video/video-share'
@@ -242,14 +242,13 @@ async function videoController (req: express.Request, res: express.Response) {
242 if (redirectIfNotOwned(video.url, res)) return 242 if (redirectIfNotOwned(video.url, res)) return
243 243
244 // We need captions to render AP object 244 // We need captions to render AP object
245 const captions = await VideoCaptionModel.listVideoCaptions(video.id) 245 const videoAP = await video.lightAPToFullAP(undefined)
246 const videoWithCaptions = Object.assign(video, { VideoCaptions: captions })
247 246
248 const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC) 247 const audience = getAudience(videoAP.VideoChannel.Account.Actor, videoAP.privacy === VideoPrivacy.PUBLIC)
249 const videoObject = audiencify(await videoWithCaptions.toActivityPubObject(), audience) 248 const videoObject = audiencify(await videoAP.toActivityPubObject(), audience)
250 249
251 if (req.path.endsWith('/activity')) { 250 if (req.path.endsWith('/activity')) {
252 const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience) 251 const data = buildCreateActivity(videoAP.url, video.VideoChannel.Account.Actor, videoObject, audience)
253 return activityPubResponse(activityPubContextify(data, 'Video'), res) 252 return activityPubResponse(activityPubContextify(data, 'Video'), res)
254 } 253 }
255 254
@@ -355,7 +354,7 @@ async function videoCommentController (req: express.Request, res: express.Respon
355 videoCommentObject = audiencify(videoCommentObject, audience) 354 videoCommentObject = audiencify(videoCommentObject, audience)
356 355
357 if (req.path.endsWith('/activity')) { 356 if (req.path.endsWith('/activity')) {
358 const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience) 357 const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject as VideoCommentObject, audience)
359 return activityPubResponse(activityPubContextify(data, 'Comment'), res) 358 return activityPubResponse(activityPubContextify(data, 'Comment'), res)
360 } 359 }
361 } 360 }
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index 681a5660c..4175cf276 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -63,6 +63,7 @@ async function buildActivities (actor: MActorLight, start: number, count: number
63 63
64 activities.push(announceActivity) 64 activities.push(announceActivity)
65 } else { 65 } else {
66 // FIXME: only use the video URL to reduce load. Breaks compat with PeerTube < 6.0.0
66 const videoObject = await video.toActivityPubObject() 67 const videoObject = await video.toActivityPubObject()
67 const createActivity = buildCreateActivity(video.url, byActor, videoObject, createActivityAudience) 68 const createActivity = buildCreateActivity(video.url, byActor, videoObject, createActivityAudience)
68 69
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 96f36bf6f..49cd7559a 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -2,7 +2,6 @@ import express from 'express'
2import { pickCommonVideoQuery } from '@server/helpers/query' 2import { pickCommonVideoQuery } from '@server/helpers/query'
3import { ActorFollowModel } from '@server/models/actor/actor-follow' 3import { ActorFollowModel } from '@server/models/actor/actor-follow'
4import { getServerActor } from '@server/models/application/application' 4import { getServerActor } from '@server/models/application/application'
5import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
6import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync' 5import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync'
7import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 6import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
8import { getFormattedObjects } from '../../helpers/utils' 7import { getFormattedObjects } from '../../helpers/utils'
@@ -36,6 +35,7 @@ import {
36import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists' 35import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists'
37import { AccountModel } from '../../models/account/account' 36import { AccountModel } from '../../models/account/account'
38import { AccountVideoRateModel } from '../../models/account/account-video-rate' 37import { AccountVideoRateModel } from '../../models/account/account-video-rate'
38import { guessAdditionalAttributesFromQuery } from '../../models/video/formatter'
39import { VideoModel } from '../../models/video/video' 39import { VideoModel } from '../../models/video/video'
40import { VideoChannelModel } from '../../models/video/video-channel' 40import { VideoChannelModel } from '../../models/video/video-channel'
41import { VideoPlaylistModel } from '../../models/video/video-playlist' 41import { VideoPlaylistModel } from '../../models/video/video-playlist'
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 228eae109..0980ec10a 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -190,6 +190,9 @@ function customConfig (): CustomConfig {
190 }, 190 },
191 torrents: { 191 torrents: {
192 size: CONFIG.CACHE.TORRENTS.SIZE 192 size: CONFIG.CACHE.TORRENTS.SIZE
193 },
194 storyboards: {
195 size: CONFIG.CACHE.STORYBOARDS.SIZE
193 } 196 }
194 }, 197 },
195 signup: { 198 signup: {
@@ -239,8 +242,8 @@ function customConfig (): CustomConfig {
239 '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p'] 242 '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
240 }, 243 },
241 alwaysTranscodeOriginalResolution: CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION, 244 alwaysTranscodeOriginalResolution: CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION,
242 webtorrent: { 245 webVideos: {
243 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED 246 enabled: CONFIG.TRANSCODING.WEB_VIDEOS.ENABLED
244 }, 247 },
245 hls: { 248 hls: {
246 enabled: CONFIG.TRANSCODING.HLS.ENABLED 249 enabled: CONFIG.TRANSCODING.HLS.ENABLED
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 31f1a56f9..38bd135d0 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -1,8 +1,7 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3 3import { logger } from '@server/helpers/logger'
4import { HttpStatusCode } from '../../../shared/models' 4import { HttpStatusCode } from '../../../shared/models'
5import { badRequest } from '../../helpers/express-utils'
6import { abuseRouter } from './abuse' 5import { abuseRouter } from './abuse'
7import { accountsRouter } from './accounts' 6import { accountsRouter } from './accounts'
8import { blocklistRouter } from './blocklist' 7import { blocklistRouter } from './blocklist'
@@ -64,3 +63,11 @@ export { apiRouter }
64function pong (req: express.Request, res: express.Response) { 63function pong (req: express.Request, res: express.Response) {
65 return res.send('pong').status(HttpStatusCode.OK_200).end() 64 return res.send('pong').status(HttpStatusCode.OK_200).end()
66} 65}
66
67function badRequest (req: express.Request, res: express.Response) {
68 logger.debug(`API express handler not found: bad PeerTube request for ${req.method} - ${req.originalUrl}`)
69
70 return res.type('json')
71 .status(HttpStatusCode.BAD_REQUEST_400)
72 .end()
73}
diff --git a/server/controllers/api/runners/jobs-files.ts b/server/controllers/api/runners/jobs-files.ts
index 4e69fb902..cb4eff570 100644
--- a/server/controllers/api/runners/jobs-files.ts
+++ b/server/controllers/api/runners/jobs-files.ts
@@ -1,6 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { logger, loggerTagsFactory } from '@server/helpers/logger' 2import { logger, loggerTagsFactory } from '@server/helpers/logger'
3import { proxifyHLS, proxifyWebTorrentFile } from '@server/lib/object-storage' 3import { proxifyHLS, proxifyWebVideoFile } from '@server/lib/object-storage'
4import { VideoPathManager } from '@server/lib/video-path-manager' 4import { VideoPathManager } from '@server/lib/video-path-manager'
5import { getStudioTaskFilePath } from '@server/lib/video-studio' 5import { getStudioTaskFilePath } from '@server/lib/video-studio'
6import { apiRateLimiter, asyncMiddleware } from '@server/middlewares' 6import { apiRateLimiter, asyncMiddleware } from '@server/middlewares'
@@ -70,7 +70,7 @@ async function getMaxQualityVideoFile (req: express.Request, res: express.Respon
70 } 70 }
71 71
72 // Web video 72 // Web video
73 return proxifyWebTorrentFile({ 73 return proxifyWebVideoFile({
74 req, 74 req,
75 res, 75 res,
76 filename: file.filename 76 filename: file.filename
diff --git a/server/controllers/api/search/search-videos.ts b/server/controllers/api/search/search-videos.ts
index 1d7a7b7bc..034a63ace 100644
--- a/server/controllers/api/search/search-videos.ts
+++ b/server/controllers/api/search/search-videos.ts
@@ -8,7 +8,6 @@ import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
8import { Hooks } from '@server/lib/plugins/hooks' 8import { Hooks } from '@server/lib/plugins/hooks'
9import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' 9import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
10import { getServerActor } from '@server/models/application/application' 10import { getServerActor } from '@server/models/application/application'
11import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
12import { HttpStatusCode, ResultList, Video } from '@shared/models' 11import { HttpStatusCode, ResultList, Video } from '@shared/models'
13import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search' 12import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search'
14import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' 13import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
@@ -25,6 +24,7 @@ import {
25 videosSearchSortValidator, 24 videosSearchSortValidator,
26 videosSearchValidator 25 videosSearchValidator
27} from '../../../middlewares' 26} from '../../../middlewares'
27import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter'
28import { VideoModel } from '../../../models/video/video' 28import { VideoModel } from '../../../models/video/video'
29import { MVideoAccountLightBlacklistAllFiles } from '../../../types/models' 29import { MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
30import { searchLocalUrl } from './shared' 30import { searchLocalUrl } from './shared'
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 218091d91..4753308e8 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -213,19 +213,14 @@ async function updateMe (req: express.Request, res: express.Response) {
213 'noInstanceConfigWarningModal', 213 'noInstanceConfigWarningModal',
214 'noAccountSetupWarningModal', 214 'noAccountSetupWarningModal',
215 'noWelcomeModal', 215 'noWelcomeModal',
216 'emailPublic' 216 'emailPublic',
217 'p2pEnabled'
217 ] 218 ]
218 219
219 for (const key of keysToUpdate) { 220 for (const key of keysToUpdate) {
220 if (body[key] !== undefined) user.set(key, body[key]) 221 if (body[key] !== undefined) user.set(key, body[key])
221 } 222 }
222 223
223 if (body.p2pEnabled !== undefined) {
224 user.set('p2pEnabled', body.p2pEnabled)
225 } else if (body.webTorrentEnabled !== undefined) { // FIXME: deprecated in 4.1
226 user.set('p2pEnabled', body.webTorrentEnabled)
227 }
228
229 if (body.email !== undefined) { 224 if (body.email !== undefined) {
230 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { 225 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
231 user.pendingEmail = body.email 226 user.pendingEmail = body.email
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 6e2aa3711..c4360f59d 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -3,7 +3,7 @@ import express from 'express'
3import { handlesToNameAndHost } from '@server/helpers/actors' 3import { handlesToNameAndHost } from '@server/helpers/actors'
4import { pickCommonVideoQuery } from '@server/helpers/query' 4import { pickCommonVideoQuery } from '@server/helpers/query'
5import { sendUndoFollow } from '@server/lib/activitypub/send' 5import { sendUndoFollow } from '@server/lib/activitypub/send'
6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' 6import { Hooks } from '@server/lib/plugins/hooks'
7import { VideoChannelModel } from '@server/models/video/video-channel' 7import { VideoChannelModel } from '@server/models/video/video-channel'
8import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 8import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
9import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' 9import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
@@ -29,8 +29,8 @@ import {
29 videosSortValidator 29 videosSortValidator
30} from '../../../middlewares/validators' 30} from '../../../middlewares/validators'
31import { ActorFollowModel } from '../../../models/actor/actor-follow' 31import { ActorFollowModel } from '../../../models/actor/actor-follow'
32import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter'
32import { VideoModel } from '../../../models/video/video' 33import { VideoModel } from '../../../models/video/video'
33import { Hooks } from '@server/lib/plugins/hooks'
34 34
35const mySubscriptionsRouter = express.Router() 35const mySubscriptionsRouter = express.Router()
36 36
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index cdafa31dc..3d7ef31ee 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -4,7 +4,6 @@ import { getBiggestActorImage } from '@server/lib/actor-image'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { ActorFollowModel } from '@server/models/actor/actor-follow' 5import { ActorFollowModel } from '@server/models/actor/actor-follow'
6import { getServerActor } from '@server/models/application/application' 6import { getServerActor } from '@server/models/application/application'
7import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
8import { MChannelBannerAccountDefault } from '@server/types/models' 7import { MChannelBannerAccountDefault } from '@server/types/models'
9import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate, VideosImportInChannelCreate } from '@shared/models' 8import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate, VideosImportInChannelCreate } from '@shared/models'
10import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 9import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
@@ -48,6 +47,7 @@ import {
48import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image' 47import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image'
49import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' 48import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
50import { AccountModel } from '../../models/account/account' 49import { AccountModel } from '../../models/account/account'
50import { guessAdditionalAttributesFromQuery } from '../../models/video/formatter'
51import { VideoModel } from '../../models/video/video' 51import { VideoModel } from '../../models/video/video'
52import { VideoChannelModel } from '../../models/video/video-channel' 52import { VideoChannelModel } from '../../models/video/video-channel'
53import { VideoPlaylistModel } from '../../models/video/video-playlist' 53import { VideoPlaylistModel } from '../../models/video/video-playlist'
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index fe00034ed..73362e1e3 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -1,6 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { join } from 'path'
3import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists' 2import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists'
3import { VideoMiniaturePermanentFileCache } from '@server/lib/files-cache'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models' 6import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models'
@@ -18,12 +18,11 @@ import { resetSequelizeInstance } from '../../helpers/database-utils'
18import { createReqFiles } from '../../helpers/express-utils' 18import { createReqFiles } from '../../helpers/express-utils'
19import { logger } from '../../helpers/logger' 19import { logger } from '../../helpers/logger'
20import { getFormattedObjects } from '../../helpers/utils' 20import { getFormattedObjects } from '../../helpers/utils'
21import { CONFIG } from '../../initializers/config'
22import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants' 21import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants'
23import { sequelizeTypescript } from '../../initializers/database' 22import { sequelizeTypescript } from '../../initializers/database'
24import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send' 23import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send'
25import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' 24import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url'
26import { updatePlaylistMiniatureFromExisting } from '../../lib/thumbnail' 25import { updateLocalPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
27import { 26import {
28 apiRateLimiter, 27 apiRateLimiter,
29 asyncMiddleware, 28 asyncMiddleware,
@@ -178,7 +177,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
178 177
179 const thumbnailField = req.files['thumbnailfile'] 178 const thumbnailField = req.files['thumbnailfile']
180 const thumbnailModel = thumbnailField 179 const thumbnailModel = thumbnailField
181 ? await updatePlaylistMiniatureFromExisting({ 180 ? await updateLocalPlaylistMiniatureFromExisting({
182 inputPath: thumbnailField[0].path, 181 inputPath: thumbnailField[0].path,
183 playlist: videoPlaylist, 182 playlist: videoPlaylist,
184 automaticallyGenerated: false 183 automaticallyGenerated: false
@@ -220,7 +219,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
220 219
221 const thumbnailField = req.files['thumbnailfile'] 220 const thumbnailField = req.files['thumbnailfile']
222 const thumbnailModel = thumbnailField 221 const thumbnailModel = thumbnailField
223 ? await updatePlaylistMiniatureFromExisting({ 222 ? await updateLocalPlaylistMiniatureFromExisting({
224 inputPath: thumbnailField[0].path, 223 inputPath: thumbnailField[0].path,
225 playlist: videoPlaylistInstance, 224 playlist: videoPlaylistInstance,
226 automaticallyGenerated: false 225 automaticallyGenerated: false
@@ -496,8 +495,13 @@ async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbn
496 return 495 return
497 } 496 }
498 497
499 const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoMiniature.filename) 498 // Ensure the file is on disk
500 const thumbnailModel = await updatePlaylistMiniatureFromExisting({ 499 const videoMiniaturePermanentFileCache = new VideoMiniaturePermanentFileCache()
500 const inputPath = videoMiniature.isOwned()
501 ? videoMiniature.getPath()
502 : await videoMiniaturePermanentFileCache.downloadRemoteFile(videoMiniature)
503
504 const thumbnailModel = await updateLocalPlaylistMiniatureFromExisting({
501 inputPath, 505 inputPath,
502 playlist: videoPlaylist, 506 playlist: videoPlaylist,
503 automaticallyGenerated: true, 507 automaticallyGenerated: true,
diff --git a/server/controllers/api/videos/files.ts b/server/controllers/api/videos/files.ts
index 6d9c0b843..67b60ff63 100644
--- a/server/controllers/api/videos/files.ts
+++ b/server/controllers/api/videos/files.ts
@@ -2,7 +2,8 @@ import express from 'express'
2import toInt from 'validator/lib/toInt' 2import toInt from 'validator/lib/toInt'
3import { logger, loggerTagsFactory } from '@server/helpers/logger' 3import { logger, loggerTagsFactory } from '@server/helpers/logger'
4import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' 4import { federateVideoIfNeeded } from '@server/lib/activitypub/videos'
5import { removeAllWebTorrentFiles, removeHLSFile, removeHLSPlaylist, removeWebTorrentFile } from '@server/lib/video-file' 5import { updatePlaylistAfterFileChange } from '@server/lib/hls'
6import { removeAllWebVideoFiles, removeHLSFile, removeHLSPlaylist, removeWebVideoFile } from '@server/lib/video-file'
6import { VideoFileModel } from '@server/models/video/video-file' 7import { VideoFileModel } from '@server/models/video/video-file'
7import { HttpStatusCode, UserRight } from '@shared/models' 8import { HttpStatusCode, UserRight } from '@shared/models'
8import { 9import {
@@ -12,11 +13,10 @@ import {
12 videoFileMetadataGetValidator, 13 videoFileMetadataGetValidator,
13 videoFilesDeleteHLSFileValidator, 14 videoFilesDeleteHLSFileValidator,
14 videoFilesDeleteHLSValidator, 15 videoFilesDeleteHLSValidator,
15 videoFilesDeleteWebTorrentFileValidator, 16 videoFilesDeleteWebVideoFileValidator,
16 videoFilesDeleteWebTorrentValidator, 17 videoFilesDeleteWebVideoValidator,
17 videosGetValidator 18 videosGetValidator
18} from '../../../middlewares' 19} from '../../../middlewares'
19import { updatePlaylistAfterFileChange } from '@server/lib/hls'
20 20
21const lTags = loggerTagsFactory('api', 'video') 21const lTags = loggerTagsFactory('api', 'video')
22const filesRouter = express.Router() 22const filesRouter = express.Router()
@@ -40,17 +40,19 @@ filesRouter.delete('/:id/hls/:videoFileId',
40 asyncMiddleware(removeHLSFileController) 40 asyncMiddleware(removeHLSFileController)
41) 41)
42 42
43filesRouter.delete('/:id/webtorrent', 43filesRouter.delete(
44 [ '/:id/webtorrent', '/:id/web-videos' ], // TODO: remove webtorrent in V7
44 authenticate, 45 authenticate,
45 ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES), 46 ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES),
46 asyncMiddleware(videoFilesDeleteWebTorrentValidator), 47 asyncMiddleware(videoFilesDeleteWebVideoValidator),
47 asyncMiddleware(removeAllWebTorrentFilesController) 48 asyncMiddleware(removeAllWebVideoFilesController)
48) 49)
49filesRouter.delete('/:id/webtorrent/:videoFileId', 50filesRouter.delete(
51 [ '/:id/webtorrent/:videoFileId', '/:id/web-videos/:videoFileId' ], // TODO: remove webtorrent in V7
50 authenticate, 52 authenticate,
51 ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES), 53 ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES),
52 asyncMiddleware(videoFilesDeleteWebTorrentFileValidator), 54 asyncMiddleware(videoFilesDeleteWebVideoFileValidator),
53 asyncMiddleware(removeWebTorrentFileController) 55 asyncMiddleware(removeWebVideoFileController)
54) 56)
55 57
56// --------------------------------------------------------------------------- 58// ---------------------------------------------------------------------------
@@ -96,24 +98,24 @@ async function removeHLSFileController (req: express.Request, res: express.Respo
96 98
97// --------------------------------------------------------------------------- 99// ---------------------------------------------------------------------------
98 100
99async function removeAllWebTorrentFilesController (req: express.Request, res: express.Response) { 101async function removeAllWebVideoFilesController (req: express.Request, res: express.Response) {
100 const video = res.locals.videoAll 102 const video = res.locals.videoAll
101 103
102 logger.info('Deleting WebTorrent files of %s.', video.url, lTags(video.uuid)) 104 logger.info('Deleting Web Video files of %s.', video.url, lTags(video.uuid))
103 105
104 await removeAllWebTorrentFiles(video) 106 await removeAllWebVideoFiles(video)
105 await federateVideoIfNeeded(video, false, undefined) 107 await federateVideoIfNeeded(video, false, undefined)
106 108
107 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 109 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
108} 110}
109 111
110async function removeWebTorrentFileController (req: express.Request, res: express.Response) { 112async function removeWebVideoFileController (req: express.Request, res: express.Response) {
111 const video = res.locals.videoAll 113 const video = res.locals.videoAll
112 114
113 const videoFileId = +req.params.videoFileId 115 const videoFileId = +req.params.videoFileId
114 logger.info('Deleting WebTorrent file %d of %s.', videoFileId, video.url, lTags(video.uuid)) 116 logger.info('Deleting Web Video file %d of %s.', videoFileId, video.url, lTags(video.uuid))
115 117
116 await removeWebTorrentFile(video, videoFileId) 118 await removeWebVideoFile(video, videoFileId)
117 await federateVideoIfNeeded(video, false, undefined) 119 await federateVideoIfNeeded(video, false, undefined)
118 120
119 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 121 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 6a50aaf4e..defe9efd4 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -14,7 +14,7 @@ import { getSecureTorrentName } from '../../../helpers/utils'
14import { CONFIG } from '../../../initializers/config' 14import { CONFIG } from '../../../initializers/config'
15import { MIMETYPES } from '../../../initializers/constants' 15import { MIMETYPES } from '../../../initializers/constants'
16import { JobQueue } from '../../../lib/job-queue/job-queue' 16import { JobQueue } from '../../../lib/job-queue/job-queue'
17import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' 17import { updateLocalVideoMiniatureFromExisting } from '../../../lib/thumbnail'
18import { 18import {
19 asyncMiddleware, 19 asyncMiddleware,
20 asyncRetryTransactionMiddleware, 20 asyncRetryTransactionMiddleware,
@@ -120,6 +120,7 @@ async function handleTorrentImport (req: express.Request, res: express.Response,
120 videoChannel: res.locals.videoChannel, 120 videoChannel: res.locals.videoChannel,
121 tags: body.tags || undefined, 121 tags: body.tags || undefined,
122 user, 122 user,
123 videoPasswords: body.videoPasswords,
123 videoImportAttributes: { 124 videoImportAttributes: {
124 magnetUri, 125 magnetUri,
125 torrentName, 126 torrentName,
@@ -192,7 +193,7 @@ async function processThumbnail (req: express.Request, video: MVideoThumbnail) {
192 if (thumbnailField) { 193 if (thumbnailField) {
193 const thumbnailPhysicalFile = thumbnailField[0] 194 const thumbnailPhysicalFile = thumbnailField[0]
194 195
195 return updateVideoMiniatureFromExisting({ 196 return updateLocalVideoMiniatureFromExisting({
196 inputPath: thumbnailPhysicalFile.path, 197 inputPath: thumbnailPhysicalFile.path,
197 video, 198 video,
198 type: ThumbnailType.MINIATURE, 199 type: ThumbnailType.MINIATURE,
@@ -208,7 +209,7 @@ async function processPreview (req: express.Request, video: MVideoThumbnail): Pr
208 if (previewField) { 209 if (previewField) {
209 const previewPhysicalFile = previewField[0] 210 const previewPhysicalFile = previewField[0]
210 211
211 return updateVideoMiniatureFromExisting({ 212 return updateLocalVideoMiniatureFromExisting({
212 inputPath: previewPhysicalFile.path, 213 inputPath: previewPhysicalFile.path,
213 video, 214 video,
214 type: ThumbnailType.PREVIEW, 215 type: ThumbnailType.PREVIEW,
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index a34325e79..520d8cbbb 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -3,7 +3,6 @@ import { pickCommonVideoQuery } from '@server/helpers/query'
3import { doJSONRequest } from '@server/helpers/requests' 3import { doJSONRequest } from '@server/helpers/requests'
4import { openapiOperationDoc } from '@server/middlewares/doc' 4import { openapiOperationDoc } from '@server/middlewares/doc'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
7import { MVideoAccountLight } from '@server/types/models' 6import { MVideoAccountLight } from '@server/types/models'
8import { HttpStatusCode } from '../../../../shared/models' 7import { HttpStatusCode } from '../../../../shared/models'
9import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 8import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
@@ -31,6 +30,7 @@ import {
31 videosRemoveValidator, 30 videosRemoveValidator,
32 videosSortValidator 31 videosSortValidator
33} from '../../../middlewares' 32} from '../../../middlewares'
33import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter'
34import { VideoModel } from '../../../models/video/video' 34import { VideoModel } from '../../../models/video/video'
35import { blacklistRouter } from './blacklist' 35import { blacklistRouter } from './blacklist'
36import { videoCaptionsRouter } from './captions' 36import { videoCaptionsRouter } from './captions'
@@ -41,12 +41,14 @@ import { liveRouter } from './live'
41import { ownershipVideoRouter } from './ownership' 41import { ownershipVideoRouter } from './ownership'
42import { rateVideoRouter } from './rate' 42import { rateVideoRouter } from './rate'
43import { statsRouter } from './stats' 43import { statsRouter } from './stats'
44import { storyboardRouter } from './storyboard'
44import { studioRouter } from './studio' 45import { studioRouter } from './studio'
45import { tokenRouter } from './token' 46import { tokenRouter } from './token'
46import { transcodingRouter } from './transcoding' 47import { transcodingRouter } from './transcoding'
47import { updateRouter } from './update' 48import { updateRouter } from './update'
48import { uploadRouter } from './upload' 49import { uploadRouter } from './upload'
49import { viewRouter } from './view' 50import { viewRouter } from './view'
51import { videoPasswordRouter } from './passwords'
50 52
51const auditLogger = auditLoggerFactory('videos') 53const auditLogger = auditLoggerFactory('videos')
52const videosRouter = express.Router() 54const videosRouter = express.Router()
@@ -68,6 +70,8 @@ videosRouter.use('/', updateRouter)
68videosRouter.use('/', filesRouter) 70videosRouter.use('/', filesRouter)
69videosRouter.use('/', transcodingRouter) 71videosRouter.use('/', transcodingRouter)
70videosRouter.use('/', tokenRouter) 72videosRouter.use('/', tokenRouter)
73videosRouter.use('/', videoPasswordRouter)
74videosRouter.use('/', storyboardRouter)
71 75
72videosRouter.get('/categories', 76videosRouter.get('/categories',
73 openapiOperationDoc({ operationId: 'getCategories' }), 77 openapiOperationDoc({ operationId: 'getCategories' }),
diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts
index de047d4ec..e19e8c652 100644
--- a/server/controllers/api/videos/live.ts
+++ b/server/controllers/api/videos/live.ts
@@ -18,13 +18,14 @@ import { VideoLiveModel } from '@server/models/video/video-live'
18import { VideoLiveSessionModel } from '@server/models/video/video-live-session' 18import { VideoLiveSessionModel } from '@server/models/video/video-live-session'
19import { MVideoDetails, MVideoFullLight, MVideoLive } from '@server/types/models' 19import { MVideoDetails, MVideoFullLight, MVideoLive } from '@server/types/models'
20import { buildUUID, uuidToShort } from '@shared/extra-utils' 20import { buildUUID, uuidToShort } from '@shared/extra-utils'
21import { HttpStatusCode, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, UserRight, VideoState } from '@shared/models' 21import { HttpStatusCode, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, UserRight, VideoPrivacy, VideoState } from '@shared/models'
22import { logger } from '../../../helpers/logger' 22import { logger } from '../../../helpers/logger'
23import { sequelizeTypescript } from '../../../initializers/database' 23import { sequelizeTypescript } from '../../../initializers/database'
24import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' 24import { updateLocalVideoMiniatureFromExisting } from '../../../lib/thumbnail'
25import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, optionalAuthenticate } from '../../../middlewares' 25import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, optionalAuthenticate } from '../../../middlewares'
26import { VideoModel } from '../../../models/video/video' 26import { VideoModel } from '../../../models/video/video'
27import { VideoLiveReplaySettingModel } from '@server/models/video/video-live-replay-setting' 27import { VideoLiveReplaySettingModel } from '@server/models/video/video-live-replay-setting'
28import { VideoPasswordModel } from '@server/models/video/video-password'
28 29
29const liveRouter = express.Router() 30const liveRouter = express.Router()
30 31
@@ -165,7 +166,7 @@ async function addLiveVideo (req: express.Request, res: express.Response) {
165 video, 166 video,
166 files: req.files, 167 files: req.files,
167 fallback: type => { 168 fallback: type => {
168 return updateVideoMiniatureFromExisting({ 169 return updateLocalVideoMiniatureFromExisting({
169 inputPath: ASSETS_PATH.DEFAULT_LIVE_BACKGROUND, 170 inputPath: ASSETS_PATH.DEFAULT_LIVE_BACKGROUND,
170 video, 171 video,
171 type, 172 type,
@@ -202,6 +203,10 @@ async function addLiveVideo (req: express.Request, res: express.Response) {
202 203
203 await federateVideoIfNeeded(videoCreated, true, t) 204 await federateVideoIfNeeded(videoCreated, true, t)
204 205
206 if (videoInfo.privacy === VideoPrivacy.PASSWORD_PROTECTED) {
207 await VideoPasswordModel.addPasswords(videoInfo.videoPasswords, video.id, t)
208 }
209
205 logger.info('Video live %s with uuid %s created.', videoInfo.name, videoCreated.uuid) 210 logger.info('Video live %s with uuid %s created.', videoInfo.name, videoCreated.uuid)
206 211
207 return { videoCreated } 212 return { videoCreated }
diff --git a/server/controllers/api/videos/passwords.ts b/server/controllers/api/videos/passwords.ts
new file mode 100644
index 000000000..d11cf5bcc
--- /dev/null
+++ b/server/controllers/api/videos/passwords.ts
@@ -0,0 +1,105 @@
1import express from 'express'
2
3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
4import { getFormattedObjects } from '../../../helpers/utils'
5import {
6 asyncMiddleware,
7 asyncRetryTransactionMiddleware,
8 authenticate,
9 setDefaultPagination,
10 setDefaultSort
11} from '../../../middlewares'
12import {
13 listVideoPasswordValidator,
14 paginationValidator,
15 removeVideoPasswordValidator,
16 updateVideoPasswordListValidator,
17 videoPasswordsSortValidator
18} from '../../../middlewares/validators'
19import { VideoPasswordModel } from '@server/models/video/video-password'
20import { logger, loggerTagsFactory } from '@server/helpers/logger'
21import { Transaction } from 'sequelize'
22import { getVideoWithAttributes } from '@server/helpers/video'
23
24const lTags = loggerTagsFactory('api', 'video')
25const videoPasswordRouter = express.Router()
26
27videoPasswordRouter.get('/:videoId/passwords',
28 authenticate,
29 paginationValidator,
30 videoPasswordsSortValidator,
31 setDefaultSort,
32 setDefaultPagination,
33 asyncMiddleware(listVideoPasswordValidator),
34 asyncMiddleware(listVideoPasswords)
35)
36
37videoPasswordRouter.put('/:videoId/passwords',
38 authenticate,
39 asyncMiddleware(updateVideoPasswordListValidator),
40 asyncMiddleware(updateVideoPasswordList)
41)
42
43videoPasswordRouter.delete('/:videoId/passwords/:passwordId',
44 authenticate,
45 asyncMiddleware(removeVideoPasswordValidator),
46 asyncRetryTransactionMiddleware(removeVideoPassword)
47)
48
49// ---------------------------------------------------------------------------
50
51export {
52 videoPasswordRouter
53}
54
55// ---------------------------------------------------------------------------
56
57async function listVideoPasswords (req: express.Request, res: express.Response) {
58 const options = {
59 videoId: res.locals.videoAll.id,
60 start: req.query.start,
61 count: req.query.count,
62 sort: req.query.sort
63 }
64
65 const resultList = await VideoPasswordModel.listPasswords(options)
66
67 return res.json(getFormattedObjects(resultList.data, resultList.total))
68}
69
70async function updateVideoPasswordList (req: express.Request, res: express.Response) {
71 const videoInstance = getVideoWithAttributes(res)
72 const videoId = videoInstance.id
73
74 const passwordArray = req.body.passwords as string[]
75
76 await VideoPasswordModel.sequelize.transaction(async (t: Transaction) => {
77 await VideoPasswordModel.deleteAllPasswords(videoId, t)
78 await VideoPasswordModel.addPasswords(passwordArray, videoId, t)
79 })
80
81 logger.info(
82 `Video passwords for video with name %s and uuid %s have been updated`,
83 videoInstance.name,
84 videoInstance.uuid,
85 lTags(videoInstance.uuid)
86 )
87
88 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
89}
90
91async function removeVideoPassword (req: express.Request, res: express.Response) {
92 const videoInstance = getVideoWithAttributes(res)
93 const password = res.locals.videoPassword
94
95 await VideoPasswordModel.deletePassword(password.id)
96 logger.info(
97 'Password with id %d of video named %s and uuid %s has been deleted.',
98 password.id,
99 videoInstance.name,
100 videoInstance.uuid,
101 lTags(videoInstance.uuid)
102 )
103
104 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
105}
diff --git a/server/controllers/api/videos/storyboard.ts b/server/controllers/api/videos/storyboard.ts
new file mode 100644
index 000000000..47a22011d
--- /dev/null
+++ b/server/controllers/api/videos/storyboard.ts
@@ -0,0 +1,29 @@
1import express from 'express'
2import { getVideoWithAttributes } from '@server/helpers/video'
3import { StoryboardModel } from '@server/models/video/storyboard'
4import { asyncMiddleware, videosGetValidator } from '../../../middlewares'
5
6const storyboardRouter = express.Router()
7
8storyboardRouter.get('/:id/storyboards',
9 asyncMiddleware(videosGetValidator),
10 asyncMiddleware(listStoryboards)
11)
12
13// ---------------------------------------------------------------------------
14
15export {
16 storyboardRouter
17}
18
19// ---------------------------------------------------------------------------
20
21async function listStoryboards (req: express.Request, res: express.Response) {
22 const video = getVideoWithAttributes(res)
23
24 const storyboards = await StoryboardModel.listStoryboardsOf(video)
25
26 return res.json({
27 storyboards: storyboards.map(s => s.toFormattedJSON())
28 })
29}
diff --git a/server/controllers/api/videos/token.ts b/server/controllers/api/videos/token.ts
index 22387c3e8..e961ffd9e 100644
--- a/server/controllers/api/videos/token.ts
+++ b/server/controllers/api/videos/token.ts
@@ -1,13 +1,14 @@
1import express from 'express' 1import express from 'express'
2import { VideoTokensManager } from '@server/lib/video-tokens-manager' 2import { VideoTokensManager } from '@server/lib/video-tokens-manager'
3import { VideoToken } from '@shared/models' 3import { VideoPrivacy, VideoToken } from '@shared/models'
4import { asyncMiddleware, authenticate, videosCustomGetValidator } from '../../../middlewares' 4import { asyncMiddleware, optionalAuthenticate, videoFileTokenValidator, videosCustomGetValidator } from '../../../middlewares'
5 5
6const tokenRouter = express.Router() 6const tokenRouter = express.Router()
7 7
8tokenRouter.post('/:id/token', 8tokenRouter.post('/:id/token',
9 authenticate, 9 optionalAuthenticate,
10 asyncMiddleware(videosCustomGetValidator('only-video')), 10 asyncMiddleware(videosCustomGetValidator('only-video')),
11 videoFileTokenValidator,
11 generateToken 12 generateToken
12) 13)
13 14
@@ -22,12 +23,11 @@ export {
22function generateToken (req: express.Request, res: express.Response) { 23function generateToken (req: express.Request, res: express.Response) {
23 const video = res.locals.onlyVideo 24 const video = res.locals.onlyVideo
24 25
25 const { token, expires } = VideoTokensManager.Instance.create({ videoUUID: video.uuid, user: res.locals.oauth.token.User }) 26 const files = video.privacy === VideoPrivacy.PASSWORD_PROTECTED
27 ? VideoTokensManager.Instance.createForPasswordProtectedVideo({ videoUUID: video.uuid })
28 : VideoTokensManager.Instance.createForAuthUser({ videoUUID: video.uuid, user: res.locals.oauth.token.User })
26 29
27 return res.json({ 30 return res.json({
28 files: { 31 files
29 token,
30 expires
31 }
32 } as VideoToken) 32 } as VideoToken)
33} 33}
diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts
index ddab428d4..28ec2cf37 100644
--- a/server/controllers/api/videos/update.ts
+++ b/server/controllers/api/videos/update.ts
@@ -2,13 +2,12 @@ import express from 'express'
2import { Transaction } from 'sequelize/types' 2import { Transaction } from 'sequelize/types'
3import { changeVideoChannelShare } from '@server/lib/activitypub/share' 3import { changeVideoChannelShare } from '@server/lib/activitypub/share'
4import { addVideoJobsAfterUpdate, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' 4import { addVideoJobsAfterUpdate, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
5import { VideoPathManager } from '@server/lib/video-path-manager'
6import { setVideoPrivacy } from '@server/lib/video-privacy' 5import { setVideoPrivacy } from '@server/lib/video-privacy'
7import { openapiOperationDoc } from '@server/middlewares/doc' 6import { openapiOperationDoc } from '@server/middlewares/doc'
8import { FilteredModelAttributes } from '@server/types' 7import { FilteredModelAttributes } from '@server/types'
9import { MVideoFullLight } from '@server/types/models' 8import { MVideoFullLight } from '@server/types/models'
10import { forceNumber } from '@shared/core-utils' 9import { forceNumber } from '@shared/core-utils'
11import { HttpStatusCode, VideoUpdate } from '@shared/models' 10import { HttpStatusCode, VideoPrivacy, VideoUpdate } from '@shared/models'
12import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 11import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
13import { resetSequelizeInstance } from '../../../helpers/database-utils' 12import { resetSequelizeInstance } from '../../../helpers/database-utils'
14import { createReqFiles } from '../../../helpers/express-utils' 13import { createReqFiles } from '../../../helpers/express-utils'
@@ -20,6 +19,9 @@ import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
20import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares' 19import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares'
21import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 20import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
22import { VideoModel } from '../../../models/video/video' 21import { VideoModel } from '../../../models/video/video'
22import { VideoPathManager } from '@server/lib/video-path-manager'
23import { VideoPasswordModel } from '@server/models/video/video-password'
24import { exists } from '@server/helpers/custom-validators/misc'
23 25
24const lTags = loggerTagsFactory('api', 'video') 26const lTags = loggerTagsFactory('api', 'video')
25const auditLogger = auditLoggerFactory('videos') 27const auditLogger = auditLoggerFactory('videos')
@@ -176,6 +178,16 @@ async function updateVideoPrivacy (options: {
176 const newPrivacy = forceNumber(videoInfoToUpdate.privacy) 178 const newPrivacy = forceNumber(videoInfoToUpdate.privacy)
177 setVideoPrivacy(videoInstance, newPrivacy) 179 setVideoPrivacy(videoInstance, newPrivacy)
178 180
181 // Delete passwords if video is not anymore password protected
182 if (videoInstance.privacy === VideoPrivacy.PASSWORD_PROTECTED && newPrivacy !== VideoPrivacy.PASSWORD_PROTECTED) {
183 await VideoPasswordModel.deleteAllPasswords(videoInstance.id, transaction)
184 }
185
186 if (newPrivacy === VideoPrivacy.PASSWORD_PROTECTED && exists(videoInfoToUpdate.videoPasswords)) {
187 await VideoPasswordModel.deleteAllPasswords(videoInstance.id, transaction)
188 await VideoPasswordModel.addPasswords(videoInfoToUpdate.videoPasswords, videoInstance.id, transaction)
189 }
190
179 // Unfederate the video if the new privacy is not compatible with federation 191 // Unfederate the video if the new privacy is not compatible with federation
180 if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) { 192 if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) {
181 await VideoModel.sendDelete(videoInstance, { transaction }) 193 await VideoModel.sendDelete(videoInstance, { transaction })
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
index 885ac8b81..27fef0b1a 100644
--- a/server/controllers/api/videos/upload.ts
+++ b/server/controllers/api/videos/upload.ts
@@ -14,14 +14,14 @@ import { openapiOperationDoc } from '@server/middlewares/doc'
14import { VideoSourceModel } from '@server/models/video/video-source' 14import { VideoSourceModel } from '@server/models/video/video-source'
15import { MUserId, MVideoFile, MVideoFullLight } from '@server/types/models' 15import { MUserId, MVideoFile, MVideoFullLight } from '@server/types/models'
16import { uuidToShort } from '@shared/extra-utils' 16import { uuidToShort } from '@shared/extra-utils'
17import { HttpStatusCode, VideoCreate, VideoState } from '@shared/models' 17import { HttpStatusCode, VideoCreate, VideoPrivacy, VideoState } from '@shared/models'
18import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 18import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
19import { createReqFiles } from '../../../helpers/express-utils' 19import { createReqFiles } from '../../../helpers/express-utils'
20import { logger, loggerTagsFactory } from '../../../helpers/logger' 20import { logger, loggerTagsFactory } from '../../../helpers/logger'
21import { MIMETYPES } from '../../../initializers/constants' 21import { MIMETYPES } from '../../../initializers/constants'
22import { sequelizeTypescript } from '../../../initializers/database' 22import { sequelizeTypescript } from '../../../initializers/database'
23import { Hooks } from '../../../lib/plugins/hooks' 23import { Hooks } from '../../../lib/plugins/hooks'
24import { generateVideoMiniature } from '../../../lib/thumbnail' 24import { generateLocalVideoMiniature } from '../../../lib/thumbnail'
25import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' 25import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
26import { 26import {
27 asyncMiddleware, 27 asyncMiddleware,
@@ -33,6 +33,7 @@ import {
33} from '../../../middlewares' 33} from '../../../middlewares'
34import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 34import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
35import { VideoModel } from '../../../models/video/video' 35import { VideoModel } from '../../../models/video/video'
36import { VideoPasswordModel } from '@server/models/video/video-password'
36 37
37const lTags = loggerTagsFactory('api', 'video') 38const lTags = loggerTagsFactory('api', 'video')
38const auditLogger = auditLoggerFactory('videos') 39const auditLogger = auditLoggerFactory('videos')
@@ -62,13 +63,13 @@ uploadRouter.post('/upload-resumable',
62 authenticate, 63 authenticate,
63 reqVideoFileAddResumable, 64 reqVideoFileAddResumable,
64 asyncMiddleware(videosAddResumableInitValidator), 65 asyncMiddleware(videosAddResumableInitValidator),
65 uploadx.upload 66 (req, res) => uploadx.upload(req, res) // Prevent next() call, explicitely tell to uploadx it's the end
66) 67)
67 68
68uploadRouter.delete('/upload-resumable', 69uploadRouter.delete('/upload-resumable',
69 authenticate, 70 authenticate,
70 asyncMiddleware(deleteUploadResumableCache), 71 asyncMiddleware(deleteUploadResumableCache),
71 uploadx.upload 72 (req, res) => uploadx.upload(req, res) // Prevent next() call, explicitely tell to uploadx it's the end
72) 73)
73 74
74uploadRouter.put('/upload-resumable', 75uploadRouter.put('/upload-resumable',
@@ -110,7 +111,7 @@ async function addVideoLegacy (req: express.Request, res: express.Response) {
110async function addVideoResumable (req: express.Request, res: express.Response) { 111async function addVideoResumable (req: express.Request, res: express.Response) {
111 const videoPhysicalFile = res.locals.videoFileResumable 112 const videoPhysicalFile = res.locals.videoFileResumable
112 const videoInfo = videoPhysicalFile.metadata 113 const videoInfo = videoPhysicalFile.metadata
113 const files = { previewfile: videoInfo.previewfile } 114 const files = { previewfile: videoInfo.previewfile, thumbnailfile: videoInfo.thumbnailfile }
114 115
115 const response = await addVideo({ req, res, videoPhysicalFile, videoInfo, files }) 116 const response = await addVideo({ req, res, videoPhysicalFile, videoInfo, files })
116 await Redis.Instance.setUploadSession(req.query.upload_id, response) 117 await Redis.Instance.setUploadSession(req.query.upload_id, response)
@@ -152,7 +153,7 @@ async function addVideo (options: {
152 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ 153 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
153 video, 154 video,
154 files, 155 files,
155 fallback: type => generateVideoMiniature({ video, videoFile, type }) 156 fallback: type => generateLocalVideoMiniature({ video, videoFile, type })
156 }) 157 })
157 158
158 const { videoCreated } = await sequelizeTypescript.transaction(async t => { 159 const { videoCreated } = await sequelizeTypescript.transaction(async t => {
@@ -195,6 +196,10 @@ async function addVideo (options: {
195 transaction: t 196 transaction: t
196 }) 197 })
197 198
199 if (videoInfo.privacy === VideoPrivacy.PASSWORD_PROTECTED) {
200 await VideoPasswordModel.addPasswords(videoInfo.videoPasswords, video.id, t)
201 }
202
198 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) 203 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
199 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid)) 204 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid))
200 205
@@ -230,6 +235,15 @@ async function addVideoJobsAfterUpload (video: MVideoFullLight, videoFile: MVide
230 }, 235 },
231 236
232 { 237 {
238 type: 'generate-video-storyboard' as 'generate-video-storyboard',
239 payload: {
240 videoUUID: video.uuid,
241 // No need to federate, we process these jobs sequentially
242 federate: false
243 }
244 },
245
246 {
233 type: 'notify', 247 type: 'notify',
234 payload: { 248 payload: {
235 action: 'new-video', 249 action: 'new-video',
diff --git a/server/controllers/download.ts b/server/controllers/download.ts
index d675a2d6c..4b94e34bd 100644
--- a/server/controllers/download.ts
+++ b/server/controllers/download.ts
@@ -1,11 +1,12 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' 4import { VideoTorrentsSimpleFileCache } from '@server/lib/files-cache'
5import { generateHLSFilePresignedUrl, generateWebVideoPresignedUrl } from '@server/lib/object-storage'
5import { Hooks } from '@server/lib/plugins/hooks' 6import { Hooks } from '@server/lib/plugins/hooks'
6import { VideoPathManager } from '@server/lib/video-path-manager' 7import { VideoPathManager } from '@server/lib/video-path-manager'
7import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 8import { MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
8import { addQueryParams, forceNumber } from '@shared/core-utils' 9import { forceNumber } from '@shared/core-utils'
9import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models' 10import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models'
10import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants' 11import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants'
11import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares' 12import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares'
@@ -42,7 +43,7 @@ export {
42// --------------------------------------------------------------------------- 43// ---------------------------------------------------------------------------
43 44
44async function downloadTorrent (req: express.Request, res: express.Response) { 45async function downloadTorrent (req: express.Request, res: express.Response) {
45 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename) 46 const result = await VideoTorrentsSimpleFileCache.Instance.getFilePath(req.params.filename)
46 if (!result) { 47 if (!result) {
47 return res.fail({ 48 return res.fail({
48 status: HttpStatusCode.NOT_FOUND_404, 49 status: HttpStatusCode.NOT_FOUND_404,
@@ -94,16 +95,16 @@ async function downloadVideoFile (req: express.Request, res: express.Response) {
94 95
95 if (!checkAllowResult(res, allowParameters, allowedResult)) return 96 if (!checkAllowResult(res, allowParameters, allowedResult)) return
96 97
98 // Express uses basename on filename parameter
99 const videoName = video.name.replace(/[/\\]/g, '_')
100 const downloadFilename = `${videoName}-${videoFile.resolution}p${videoFile.extname}`
101
97 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) { 102 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
98 return redirectToObjectStorage({ req, res, video, file: videoFile }) 103 return redirectToObjectStorage({ req, res, video, file: videoFile, downloadFilename })
99 } 104 }
100 105
101 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), path => { 106 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), path => {
102 // Express uses basename on filename parameter 107 return res.download(path, downloadFilename)
103 const videoName = video.name.replace(/[/\\]/g, '_')
104 const filename = `${videoName}-${videoFile.resolution}p${videoFile.extname}`
105
106 return res.download(path, filename)
107 }) 108 })
108} 109}
109 110
@@ -136,14 +137,14 @@ async function downloadHLSVideoFile (req: express.Request, res: express.Response
136 137
137 if (!checkAllowResult(res, allowParameters, allowedResult)) return 138 if (!checkAllowResult(res, allowParameters, allowedResult)) return
138 139
140 const downloadFilename = `${video.name}-${videoFile.resolution}p-${streamingPlaylist.getStringType()}${videoFile.extname}`
141
139 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) { 142 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
140 return redirectToObjectStorage({ req, res, video, file: videoFile }) 143 return redirectToObjectStorage({ req, res, video, streamingPlaylist, file: videoFile, downloadFilename })
141 } 144 }
142 145
143 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(streamingPlaylist), path => { 146 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(streamingPlaylist), path => {
144 const filename = `${video.name}-${videoFile.resolution}p-${streamingPlaylist.getStringType()}${videoFile.extname}` 147 return res.download(path, downloadFilename)
145
146 return res.download(path, filename)
147 }) 148 })
148} 149}
149 150
@@ -192,19 +193,21 @@ function checkAllowResult (res: express.Response, allowParameters: any, result?:
192 return true 193 return true
193} 194}
194 195
195function redirectToObjectStorage (options: { 196async function redirectToObjectStorage (options: {
196 req: express.Request 197 req: express.Request
197 res: express.Response 198 res: express.Response
198 video: MVideo 199 video: MVideo
199 file: MVideoFile 200 file: MVideoFile
201 streamingPlaylist?: MStreamingPlaylistVideo
202 downloadFilename: string
200}) { 203}) {
201 const { req, res, video, file } = options 204 const { res, video, streamingPlaylist, file, downloadFilename } = options
202 205
203 const baseUrl = file.getObjectStorageUrl(video) 206 const url = streamingPlaylist
207 ? await generateHLSFilePresignedUrl({ streamingPlaylist, file, downloadFilename })
208 : await generateWebVideoPresignedUrl({ file, downloadFilename })
204 209
205 const url = video.hasPrivateStaticPath() && req.query.videoFileToken 210 logger.debug('Generating pre-signed URL %s for video %s', url, video.uuid)
206 ? addQueryParams(baseUrl, { videoFileToken: req.query.videoFileToken })
207 : baseUrl
208 211
209 return res.redirect(url) 212 return res.redirect(url)
210} 213}
diff --git a/server/controllers/feeds/shared/video-feed-utils.ts b/server/controllers/feeds/shared/video-feed-utils.ts
index 3175cea59..b154e04fa 100644
--- a/server/controllers/feeds/shared/video-feed-utils.ts
+++ b/server/controllers/feeds/shared/video-feed-utils.ts
@@ -2,7 +2,7 @@ import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown'
2import { CONFIG } from '@server/initializers/config' 2import { CONFIG } from '@server/initializers/config'
3import { WEBSERVER } from '@server/initializers/constants' 3import { WEBSERVER } from '@server/initializers/constants'
4import { getServerActor } from '@server/models/application/application' 4import { getServerActor } from '@server/models/application/application'
5import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils' 5import { getCategoryLabel } from '@server/models/video/formatter'
6import { DisplayOnlyForFollowerOptions } from '@server/models/video/sql/video' 6import { DisplayOnlyForFollowerOptions } from '@server/models/video/sql/video'
7import { VideoModel } from '@server/models/video/video' 7import { VideoModel } from '@server/models/video/video'
8import { MThumbnail, MUserDefault } from '@server/types/models' 8import { MThumbnail, MUserDefault } from '@server/types/models'
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts
index b082e41f6..dad30365c 100644
--- a/server/controllers/lazy-static.ts
+++ b/server/controllers/lazy-static.ts
@@ -1,14 +1,28 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' 3import { CONFIG } from '@server/initializers/config'
4import { MActorImage } from '@server/types/models'
5import { HttpStatusCode } from '../../shared/models/http/http-error-codes' 4import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
6import { logger } from '../helpers/logger' 5import { FILES_CACHE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants'
7import { ACTOR_IMAGES_SIZE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' 6import {
8import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' 7 AvatarPermanentFileCache,
9import { actorImagePathUnsafeCache, downloadActorImageFromWorker } from '../lib/local-actor' 8 VideoCaptionsSimpleFileCache,
9 VideoMiniaturePermanentFileCache,
10 VideoPreviewsSimpleFileCache,
11 VideoStoryboardsSimpleFileCache,
12 VideoTorrentsSimpleFileCache
13} from '../lib/files-cache'
10import { asyncMiddleware, handleStaticError } from '../middlewares' 14import { asyncMiddleware, handleStaticError } from '../middlewares'
11import { ActorImageModel } from '../models/actor/actor-image' 15
16// ---------------------------------------------------------------------------
17// Cache initializations
18// ---------------------------------------------------------------------------
19
20VideoPreviewsSimpleFileCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE)
21VideoCaptionsSimpleFileCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE)
22VideoTorrentsSimpleFileCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
23VideoStoryboardsSimpleFileCache.Instance.init(CONFIG.CACHE.STORYBOARDS.SIZE, FILES_CACHE.STORYBOARDS.MAX_AGE)
24
25// ---------------------------------------------------------------------------
12 26
13const lazyStaticRouter = express.Router() 27const lazyStaticRouter = express.Router()
14 28
@@ -27,12 +41,24 @@ lazyStaticRouter.use(
27) 41)
28 42
29lazyStaticRouter.use( 43lazyStaticRouter.use(
44 LAZY_STATIC_PATHS.THUMBNAILS + ':filename',
45 asyncMiddleware(getThumbnail),
46 handleStaticError
47)
48
49lazyStaticRouter.use(
30 LAZY_STATIC_PATHS.PREVIEWS + ':filename', 50 LAZY_STATIC_PATHS.PREVIEWS + ':filename',
31 asyncMiddleware(getPreview), 51 asyncMiddleware(getPreview),
32 handleStaticError 52 handleStaticError
33) 53)
34 54
35lazyStaticRouter.use( 55lazyStaticRouter.use(
56 LAZY_STATIC_PATHS.STORYBOARDS + ':filename',
57 asyncMiddleware(getStoryboard),
58 handleStaticError
59)
60
61lazyStaticRouter.use(
36 LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':filename', 62 LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':filename',
37 asyncMiddleware(getVideoCaption), 63 asyncMiddleware(getVideoCaption),
38 handleStaticError 64 handleStaticError
@@ -53,88 +79,48 @@ export {
53} 79}
54 80
55// --------------------------------------------------------------------------- 81// ---------------------------------------------------------------------------
82const avatarPermanentFileCache = new AvatarPermanentFileCache()
56 83
57async function getActorImage (req: express.Request, res: express.Response, next: express.NextFunction) { 84function getActorImage (req: express.Request, res: express.Response, next: express.NextFunction) {
58 const filename = req.params.filename 85 const filename = req.params.filename
59 86
60 if (actorImagePathUnsafeCache.has(filename)) { 87 return avatarPermanentFileCache.lazyServe({ filename, res, next })
61 return res.sendFile(actorImagePathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER }) 88}
62 }
63
64 const image = await ActorImageModel.loadByName(filename)
65 if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end()
66
67 if (image.onDisk === false) {
68 if (!image.fileUrl) return res.status(HttpStatusCode.NOT_FOUND_404).end()
69
70 logger.info('Lazy serve remote actor image %s.', image.fileUrl)
71
72 try {
73 await downloadActorImageFromWorker({
74 filename: image.filename,
75 fileUrl: image.fileUrl,
76 size: getActorImageSize(image),
77 type: image.type
78 })
79 } catch (err) {
80 logger.warn('Cannot process remote actor image %s.', image.fileUrl, { err })
81 return res.status(HttpStatusCode.NOT_FOUND_404).end()
82 }
83
84 image.onDisk = true
85 image.save()
86 .catch(err => logger.error('Cannot save new actor image disk state.', { err }))
87 }
88
89 const path = image.getPath()
90
91 actorImagePathUnsafeCache.set(filename, path)
92
93 return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }, (err: any) => {
94 if (!err) return
95
96 // It seems this actor image is not on the disk anymore
97 if (err.status === HttpStatusCode.NOT_FOUND_404 && !image.isOwned()) {
98 logger.error('Cannot lazy serve actor image %s.', filename, { err })
99 89
100 actorImagePathUnsafeCache.delete(filename) 90// ---------------------------------------------------------------------------
91const videoMiniaturePermanentFileCache = new VideoMiniaturePermanentFileCache()
101 92
102 image.onDisk = false 93function getThumbnail (req: express.Request, res: express.Response, next: express.NextFunction) {
103 image.save() 94 const filename = req.params.filename
104 .catch(err => logger.error('Cannot save new actor image disk state.', { err }))
105 }
106 95
107 return next(err) 96 return videoMiniaturePermanentFileCache.lazyServe({ filename, res, next })
108 })
109} 97}
110 98
111function getActorImageSize (image: MActorImage): { width: number, height: number } { 99// ---------------------------------------------------------------------------
112 if (image.width && image.height) { 100
113 return { 101async function getPreview (req: express.Request, res: express.Response) {
114 height: image.height, 102 const result = await VideoPreviewsSimpleFileCache.Instance.getFilePath(req.params.filename)
115 width: image.width 103 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
116 }
117 }
118 104
119 return ACTOR_IMAGES_SIZE[image.type][0] 105 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
120} 106}
121 107
122async function getPreview (req: express.Request, res: express.Response) { 108async function getStoryboard (req: express.Request, res: express.Response) {
123 const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename) 109 const result = await VideoStoryboardsSimpleFileCache.Instance.getFilePath(req.params.filename)
124 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end() 110 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
125 111
126 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) 112 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
127} 113}
128 114
129async function getVideoCaption (req: express.Request, res: express.Response) { 115async function getVideoCaption (req: express.Request, res: express.Response) {
130 const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename) 116 const result = await VideoCaptionsSimpleFileCache.Instance.getFilePath(req.params.filename)
131 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end() 117 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
132 118
133 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) 119 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
134} 120}
135 121
136async function getTorrent (req: express.Request, res: express.Response) { 122async function getTorrent (req: express.Request, res: express.Response) {
137 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename) 123 const result = await VideoTorrentsSimpleFileCache.Instance.getFilePath(req.params.filename)
138 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end() 124 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
139 125
140 // Torrents still use the old naming convention (video uuid + .torrent) 126 // Torrents still use the old naming convention (video uuid + .torrent)
diff --git a/server/controllers/misc.ts b/server/controllers/misc.ts
index 4c8af2adc..163352ac5 100644
--- a/server/controllers/misc.ts
+++ b/server/controllers/misc.ts
@@ -120,8 +120,8 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
120 hls: { 120 hls: {
121 enabled: CONFIG.TRANSCODING.HLS.ENABLED 121 enabled: CONFIG.TRANSCODING.HLS.ENABLED
122 }, 122 },
123 webtorrent: { 123 web_videos: {
124 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED 124 enabled: CONFIG.TRANSCODING.WEB_VIDEOS.ENABLED
125 }, 125 },
126 enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('vod') 126 enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('vod')
127 }, 127 },
diff --git a/server/controllers/object-storage-proxy.ts b/server/controllers/object-storage-proxy.ts
index 8e2cc4af9..d0c59bf93 100644
--- a/server/controllers/object-storage-proxy.ts
+++ b/server/controllers/object-storage-proxy.ts
@@ -1,11 +1,11 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants' 3import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants'
4import { proxifyHLS, proxifyWebTorrentFile } from '@server/lib/object-storage' 4import { proxifyHLS, proxifyWebVideoFile } from '@server/lib/object-storage'
5import { 5import {
6 asyncMiddleware, 6 asyncMiddleware,
7 ensureCanAccessPrivateVideoHLSFiles, 7 ensureCanAccessPrivateVideoHLSFiles,
8 ensureCanAccessVideoPrivateWebTorrentFiles, 8 ensureCanAccessVideoPrivateWebVideoFiles,
9 ensurePrivateObjectStorageProxyIsEnabled, 9 ensurePrivateObjectStorageProxyIsEnabled,
10 optionalAuthenticate 10 optionalAuthenticate
11} from '@server/middlewares' 11} from '@server/middlewares'
@@ -15,11 +15,12 @@ const objectStorageProxyRouter = express.Router()
15 15
16objectStorageProxyRouter.use(cors()) 16objectStorageProxyRouter.use(cors())
17 17
18objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEBSEED + ':filename', 18objectStorageProxyRouter.get(
19 [ OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEB_VIDEOS + ':filename', OBJECT_STORAGE_PROXY_PATHS.LEGACY_PRIVATE_WEB_VIDEOS + ':filename' ],
19 ensurePrivateObjectStorageProxyIsEnabled, 20 ensurePrivateObjectStorageProxyIsEnabled,
20 optionalAuthenticate, 21 optionalAuthenticate,
21 asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles), 22 asyncMiddleware(ensureCanAccessVideoPrivateWebVideoFiles),
22 asyncMiddleware(proxifyWebTorrentController) 23 asyncMiddleware(proxifyWebVideoController)
23) 24)
24 25
25objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename', 26objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename',
@@ -35,10 +36,10 @@ export {
35 objectStorageProxyRouter 36 objectStorageProxyRouter
36} 37}
37 38
38function proxifyWebTorrentController (req: express.Request, res: express.Response) { 39function proxifyWebVideoController (req: express.Request, res: express.Response) {
39 const filename = req.params.filename 40 const filename = req.params.filename
40 41
41 return proxifyWebTorrentFile({ req, res, filename }) 42 return proxifyWebVideoFile({ req, res, filename })
42} 43}
43 44
44function proxifyHLSController (req: express.Request, res: express.Response) { 45function proxifyHLSController (req: express.Request, res: express.Response) {
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 9baff94c0..97caa8292 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -6,7 +6,7 @@ import { injectQueryToPlaylistUrls } from '@server/lib/hls'
6import { 6import {
7 asyncMiddleware, 7 asyncMiddleware,
8 ensureCanAccessPrivateVideoHLSFiles, 8 ensureCanAccessPrivateVideoHLSFiles,
9 ensureCanAccessVideoPrivateWebTorrentFiles, 9 ensureCanAccessVideoPrivateWebVideoFiles,
10 handleStaticError, 10 handleStaticError,
11 optionalAuthenticate 11 optionalAuthenticate
12} from '@server/middlewares' 12} from '@server/middlewares'
@@ -21,21 +21,21 @@ const staticRouter = express.Router()
21staticRouter.use(cors()) 21staticRouter.use(cors())
22 22
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24// WebTorrent/Classic videos 24// Web videos/Classic videos
25// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
26 26
27const privateWebTorrentStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true 27const privateWebVideoStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true
28 ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles) ] 28 ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessVideoPrivateWebVideoFiles) ]
29 : [] 29 : []
30 30
31staticRouter.use( 31staticRouter.use(
32 STATIC_PATHS.PRIVATE_WEBSEED, 32 [ STATIC_PATHS.PRIVATE_WEB_VIDEOS, STATIC_PATHS.LEGACY_PRIVATE_WEB_VIDEOS ],
33 ...privateWebTorrentStaticMiddlewares, 33 ...privateWebVideoStaticMiddlewares,
34 express.static(DIRECTORIES.VIDEOS.PRIVATE, { fallthrough: false }), 34 express.static(DIRECTORIES.VIDEOS.PRIVATE, { fallthrough: false }),
35 handleStaticError 35 handleStaticError
36) 36)
37staticRouter.use( 37staticRouter.use(
38 STATIC_PATHS.WEBSEED, 38 [ STATIC_PATHS.WEB_VIDEOS, STATIC_PATHS.LEGACY_WEB_VIDEOS ],
39 express.static(DIRECTORIES.VIDEOS.PUBLIC, { fallthrough: false }), 39 express.static(DIRECTORIES.VIDEOS.PUBLIC, { fallthrough: false }),
40 handleStaticError 40 handleStaticError
41) 41)
@@ -72,7 +72,7 @@ staticRouter.use(
72 handleStaticError 72 handleStaticError
73) 73)
74 74
75// Thumbnails path for express 75// FIXME: deprecated in v6, to remove
76const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR 76const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR
77staticRouter.use( 77staticRouter.use(
78 STATIC_PATHS.THUMBNAILS, 78 STATIC_PATHS.THUMBNAILS,