diff options
Diffstat (limited to 'server/controllers')
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 | |||
4 | import { activityPubContextify } from '@server/lib/activitypub/context' | 4 | import { activityPubContextify } from '@server/lib/activitypub/context' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models' | 6 | import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models' |
7 | import { VideoCommentObject } from '@shared/models' | ||
7 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' | 8 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' |
8 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 9 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
9 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' | 10 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' |
@@ -33,7 +34,6 @@ import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from ' | |||
33 | import { AccountModel } from '../../models/account/account' | 34 | import { AccountModel } from '../../models/account/account' |
34 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | 35 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' |
35 | import { ActorFollowModel } from '../../models/actor/actor-follow' | 36 | import { ActorFollowModel } from '../../models/actor/actor-follow' |
36 | import { VideoCaptionModel } from '../../models/video/video-caption' | ||
37 | import { VideoCommentModel } from '../../models/video/video-comment' | 37 | import { VideoCommentModel } from '../../models/video/video-comment' |
38 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 38 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
39 | import { VideoShareModel } from '../../models/video/video-share' | 39 | import { 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' | |||
2 | import { pickCommonVideoQuery } from '@server/helpers/query' | 2 | import { pickCommonVideoQuery } from '@server/helpers/query' |
3 | import { ActorFollowModel } from '@server/models/actor/actor-follow' | 3 | import { ActorFollowModel } from '@server/models/actor/actor-follow' |
4 | import { getServerActor } from '@server/models/application/application' | 4 | import { getServerActor } from '@server/models/application/application' |
5 | import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' | ||
6 | import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync' | 5 | import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync' |
7 | import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 6 | import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
8 | import { getFormattedObjects } from '../../helpers/utils' | 7 | import { getFormattedObjects } from '../../helpers/utils' |
@@ -36,6 +35,7 @@ import { | |||
36 | import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists' | 35 | import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists' |
37 | import { AccountModel } from '../../models/account/account' | 36 | import { AccountModel } from '../../models/account/account' |
38 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | 37 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' |
38 | import { guessAdditionalAttributesFromQuery } from '../../models/video/formatter' | ||
39 | import { VideoModel } from '../../models/video/video' | 39 | import { VideoModel } from '../../models/video/video' |
40 | import { VideoChannelModel } from '../../models/video/video-channel' | 40 | import { VideoChannelModel } from '../../models/video/video-channel' |
41 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 41 | import { 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 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | 3 | import { logger } from '@server/helpers/logger' | |
4 | import { HttpStatusCode } from '../../../shared/models' | 4 | import { HttpStatusCode } from '../../../shared/models' |
5 | import { badRequest } from '../../helpers/express-utils' | ||
6 | import { abuseRouter } from './abuse' | 5 | import { abuseRouter } from './abuse' |
7 | import { accountsRouter } from './accounts' | 6 | import { accountsRouter } from './accounts' |
8 | import { blocklistRouter } from './blocklist' | 7 | import { blocklistRouter } from './blocklist' |
@@ -64,3 +63,11 @@ export { apiRouter } | |||
64 | function pong (req: express.Request, res: express.Response) { | 63 | function 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 | |||
67 | function 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 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
3 | import { proxifyHLS, proxifyWebTorrentFile } from '@server/lib/object-storage' | 3 | import { proxifyHLS, proxifyWebVideoFile } from '@server/lib/object-storage' |
4 | import { VideoPathManager } from '@server/lib/video-path-manager' | 4 | import { VideoPathManager } from '@server/lib/video-path-manager' |
5 | import { getStudioTaskFilePath } from '@server/lib/video-studio' | 5 | import { getStudioTaskFilePath } from '@server/lib/video-studio' |
6 | import { apiRateLimiter, asyncMiddleware } from '@server/middlewares' | 6 | import { 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' | |||
8 | import { Hooks } from '@server/lib/plugins/hooks' | 8 | import { Hooks } from '@server/lib/plugins/hooks' |
9 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' | 9 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' |
10 | import { getServerActor } from '@server/models/application/application' | 10 | import { getServerActor } from '@server/models/application/application' |
11 | import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' | ||
12 | import { HttpStatusCode, ResultList, Video } from '@shared/models' | 11 | import { HttpStatusCode, ResultList, Video } from '@shared/models' |
13 | import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search' | 12 | import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search' |
14 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' | 13 | import { 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' |
27 | import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter' | ||
28 | import { VideoModel } from '../../../models/video/video' | 28 | import { VideoModel } from '../../../models/video/video' |
29 | import { MVideoAccountLightBlacklistAllFiles } from '../../../types/models' | 29 | import { MVideoAccountLightBlacklistAllFiles } from '../../../types/models' |
30 | import { searchLocalUrl } from './shared' | 30 | import { 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' | |||
3 | import { handlesToNameAndHost } from '@server/helpers/actors' | 3 | import { handlesToNameAndHost } from '@server/helpers/actors' |
4 | import { pickCommonVideoQuery } from '@server/helpers/query' | 4 | import { pickCommonVideoQuery } from '@server/helpers/query' |
5 | import { sendUndoFollow } from '@server/lib/activitypub/send' | 5 | import { sendUndoFollow } from '@server/lib/activitypub/send' |
6 | import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' | 6 | import { Hooks } from '@server/lib/plugins/hooks' |
7 | import { VideoChannelModel } from '@server/models/video/video-channel' | 7 | import { VideoChannelModel } from '@server/models/video/video-channel' |
8 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 8 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
9 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' | 9 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' |
@@ -29,8 +29,8 @@ import { | |||
29 | videosSortValidator | 29 | videosSortValidator |
30 | } from '../../../middlewares/validators' | 30 | } from '../../../middlewares/validators' |
31 | import { ActorFollowModel } from '../../../models/actor/actor-follow' | 31 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
32 | import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter' | ||
32 | import { VideoModel } from '../../../models/video/video' | 33 | import { VideoModel } from '../../../models/video/video' |
33 | import { Hooks } from '@server/lib/plugins/hooks' | ||
34 | 34 | ||
35 | const mySubscriptionsRouter = express.Router() | 35 | const 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' | |||
4 | import { Hooks } from '@server/lib/plugins/hooks' | 4 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { ActorFollowModel } from '@server/models/actor/actor-follow' | 5 | import { ActorFollowModel } from '@server/models/actor/actor-follow' |
6 | import { getServerActor } from '@server/models/application/application' | 6 | import { getServerActor } from '@server/models/application/application' |
7 | import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' | ||
8 | import { MChannelBannerAccountDefault } from '@server/types/models' | 7 | import { MChannelBannerAccountDefault } from '@server/types/models' |
9 | import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate, VideosImportInChannelCreate } from '@shared/models' | 8 | import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate, VideosImportInChannelCreate } from '@shared/models' |
10 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' | 9 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' |
@@ -48,6 +47,7 @@ import { | |||
48 | import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image' | 47 | import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image' |
49 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' | 48 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' |
50 | import { AccountModel } from '../../models/account/account' | 49 | import { AccountModel } from '../../models/account/account' |
50 | import { guessAdditionalAttributesFromQuery } from '../../models/video/formatter' | ||
51 | import { VideoModel } from '../../models/video/video' | 51 | import { VideoModel } from '../../models/video/video' |
52 | import { VideoChannelModel } from '../../models/video/video-channel' | 52 | import { VideoChannelModel } from '../../models/video/video-channel' |
53 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 53 | import { 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 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { join } from 'path' | ||
3 | import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists' | 2 | import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists' |
3 | import { VideoMiniaturePermanentFileCache } from '@server/lib/files-cache' | ||
4 | import { Hooks } from '@server/lib/plugins/hooks' | 4 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models' | 6 | import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models' |
@@ -18,12 +18,11 @@ import { resetSequelizeInstance } from '../../helpers/database-utils' | |||
18 | import { createReqFiles } from '../../helpers/express-utils' | 18 | import { createReqFiles } from '../../helpers/express-utils' |
19 | import { logger } from '../../helpers/logger' | 19 | import { logger } from '../../helpers/logger' |
20 | import { getFormattedObjects } from '../../helpers/utils' | 20 | import { getFormattedObjects } from '../../helpers/utils' |
21 | import { CONFIG } from '../../initializers/config' | ||
22 | import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants' | 21 | import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants' |
23 | import { sequelizeTypescript } from '../../initializers/database' | 22 | import { sequelizeTypescript } from '../../initializers/database' |
24 | import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send' | 23 | import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send' |
25 | import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' | 24 | import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' |
26 | import { updatePlaylistMiniatureFromExisting } from '../../lib/thumbnail' | 25 | import { updateLocalPlaylistMiniatureFromExisting } from '../../lib/thumbnail' |
27 | import { | 26 | import { |
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' | |||
2 | import toInt from 'validator/lib/toInt' | 2 | import toInt from 'validator/lib/toInt' |
3 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 3 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
4 | import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' | 4 | import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' |
5 | import { removeAllWebTorrentFiles, removeHLSFile, removeHLSPlaylist, removeWebTorrentFile } from '@server/lib/video-file' | 5 | import { updatePlaylistAfterFileChange } from '@server/lib/hls' |
6 | import { removeAllWebVideoFiles, removeHLSFile, removeHLSPlaylist, removeWebVideoFile } from '@server/lib/video-file' | ||
6 | import { VideoFileModel } from '@server/models/video/video-file' | 7 | import { VideoFileModel } from '@server/models/video/video-file' |
7 | import { HttpStatusCode, UserRight } from '@shared/models' | 8 | import { HttpStatusCode, UserRight } from '@shared/models' |
8 | import { | 9 | import { |
@@ -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' |
19 | import { updatePlaylistAfterFileChange } from '@server/lib/hls' | ||
20 | 20 | ||
21 | const lTags = loggerTagsFactory('api', 'video') | 21 | const lTags = loggerTagsFactory('api', 'video') |
22 | const filesRouter = express.Router() | 22 | const filesRouter = express.Router() |
@@ -40,17 +40,19 @@ filesRouter.delete('/:id/hls/:videoFileId', | |||
40 | asyncMiddleware(removeHLSFileController) | 40 | asyncMiddleware(removeHLSFileController) |
41 | ) | 41 | ) |
42 | 42 | ||
43 | filesRouter.delete('/:id/webtorrent', | 43 | filesRouter.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 | ) |
49 | filesRouter.delete('/:id/webtorrent/:videoFileId', | 50 | filesRouter.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 | ||
99 | async function removeAllWebTorrentFilesController (req: express.Request, res: express.Response) { | 101 | async 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 | ||
110 | async function removeWebTorrentFileController (req: express.Request, res: express.Response) { | 112 | async 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' | |||
14 | import { CONFIG } from '../../../initializers/config' | 14 | import { CONFIG } from '../../../initializers/config' |
15 | import { MIMETYPES } from '../../../initializers/constants' | 15 | import { MIMETYPES } from '../../../initializers/constants' |
16 | import { JobQueue } from '../../../lib/job-queue/job-queue' | 16 | import { JobQueue } from '../../../lib/job-queue/job-queue' |
17 | import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' | 17 | import { updateLocalVideoMiniatureFromExisting } from '../../../lib/thumbnail' |
18 | import { | 18 | import { |
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' | |||
3 | import { doJSONRequest } from '@server/helpers/requests' | 3 | import { doJSONRequest } from '@server/helpers/requests' |
4 | import { openapiOperationDoc } from '@server/middlewares/doc' | 4 | import { openapiOperationDoc } from '@server/middlewares/doc' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' | ||
7 | import { MVideoAccountLight } from '@server/types/models' | 6 | import { MVideoAccountLight } from '@server/types/models' |
8 | import { HttpStatusCode } from '../../../../shared/models' | 7 | import { HttpStatusCode } from '../../../../shared/models' |
9 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 8 | import { 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' |
33 | import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter' | ||
34 | import { VideoModel } from '../../../models/video/video' | 34 | import { VideoModel } from '../../../models/video/video' |
35 | import { blacklistRouter } from './blacklist' | 35 | import { blacklistRouter } from './blacklist' |
36 | import { videoCaptionsRouter } from './captions' | 36 | import { videoCaptionsRouter } from './captions' |
@@ -41,12 +41,14 @@ import { liveRouter } from './live' | |||
41 | import { ownershipVideoRouter } from './ownership' | 41 | import { ownershipVideoRouter } from './ownership' |
42 | import { rateVideoRouter } from './rate' | 42 | import { rateVideoRouter } from './rate' |
43 | import { statsRouter } from './stats' | 43 | import { statsRouter } from './stats' |
44 | import { storyboardRouter } from './storyboard' | ||
44 | import { studioRouter } from './studio' | 45 | import { studioRouter } from './studio' |
45 | import { tokenRouter } from './token' | 46 | import { tokenRouter } from './token' |
46 | import { transcodingRouter } from './transcoding' | 47 | import { transcodingRouter } from './transcoding' |
47 | import { updateRouter } from './update' | 48 | import { updateRouter } from './update' |
48 | import { uploadRouter } from './upload' | 49 | import { uploadRouter } from './upload' |
49 | import { viewRouter } from './view' | 50 | import { viewRouter } from './view' |
51 | import { videoPasswordRouter } from './passwords' | ||
50 | 52 | ||
51 | const auditLogger = auditLoggerFactory('videos') | 53 | const auditLogger = auditLoggerFactory('videos') |
52 | const videosRouter = express.Router() | 54 | const videosRouter = express.Router() |
@@ -68,6 +70,8 @@ videosRouter.use('/', updateRouter) | |||
68 | videosRouter.use('/', filesRouter) | 70 | videosRouter.use('/', filesRouter) |
69 | videosRouter.use('/', transcodingRouter) | 71 | videosRouter.use('/', transcodingRouter) |
70 | videosRouter.use('/', tokenRouter) | 72 | videosRouter.use('/', tokenRouter) |
73 | videosRouter.use('/', videoPasswordRouter) | ||
74 | videosRouter.use('/', storyboardRouter) | ||
71 | 75 | ||
72 | videosRouter.get('/categories', | 76 | videosRouter.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' | |||
18 | import { VideoLiveSessionModel } from '@server/models/video/video-live-session' | 18 | import { VideoLiveSessionModel } from '@server/models/video/video-live-session' |
19 | import { MVideoDetails, MVideoFullLight, MVideoLive } from '@server/types/models' | 19 | import { MVideoDetails, MVideoFullLight, MVideoLive } from '@server/types/models' |
20 | import { buildUUID, uuidToShort } from '@shared/extra-utils' | 20 | import { buildUUID, uuidToShort } from '@shared/extra-utils' |
21 | import { HttpStatusCode, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, UserRight, VideoState } from '@shared/models' | 21 | import { HttpStatusCode, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, UserRight, VideoPrivacy, VideoState } from '@shared/models' |
22 | import { logger } from '../../../helpers/logger' | 22 | import { logger } from '../../../helpers/logger' |
23 | import { sequelizeTypescript } from '../../../initializers/database' | 23 | import { sequelizeTypescript } from '../../../initializers/database' |
24 | import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' | 24 | import { updateLocalVideoMiniatureFromExisting } from '../../../lib/thumbnail' |
25 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, optionalAuthenticate } from '../../../middlewares' | 25 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, optionalAuthenticate } from '../../../middlewares' |
26 | import { VideoModel } from '../../../models/video/video' | 26 | import { VideoModel } from '../../../models/video/video' |
27 | import { VideoLiveReplaySettingModel } from '@server/models/video/video-live-replay-setting' | 27 | import { VideoLiveReplaySettingModel } from '@server/models/video/video-live-replay-setting' |
28 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
28 | 29 | ||
29 | const liveRouter = express.Router() | 30 | const 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 @@ | |||
1 | import express from 'express' | ||
2 | |||
3 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | ||
4 | import { getFormattedObjects } from '../../../helpers/utils' | ||
5 | import { | ||
6 | asyncMiddleware, | ||
7 | asyncRetryTransactionMiddleware, | ||
8 | authenticate, | ||
9 | setDefaultPagination, | ||
10 | setDefaultSort | ||
11 | } from '../../../middlewares' | ||
12 | import { | ||
13 | listVideoPasswordValidator, | ||
14 | paginationValidator, | ||
15 | removeVideoPasswordValidator, | ||
16 | updateVideoPasswordListValidator, | ||
17 | videoPasswordsSortValidator | ||
18 | } from '../../../middlewares/validators' | ||
19 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
20 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | ||
21 | import { Transaction } from 'sequelize' | ||
22 | import { getVideoWithAttributes } from '@server/helpers/video' | ||
23 | |||
24 | const lTags = loggerTagsFactory('api', 'video') | ||
25 | const videoPasswordRouter = express.Router() | ||
26 | |||
27 | videoPasswordRouter.get('/:videoId/passwords', | ||
28 | authenticate, | ||
29 | paginationValidator, | ||
30 | videoPasswordsSortValidator, | ||
31 | setDefaultSort, | ||
32 | setDefaultPagination, | ||
33 | asyncMiddleware(listVideoPasswordValidator), | ||
34 | asyncMiddleware(listVideoPasswords) | ||
35 | ) | ||
36 | |||
37 | videoPasswordRouter.put('/:videoId/passwords', | ||
38 | authenticate, | ||
39 | asyncMiddleware(updateVideoPasswordListValidator), | ||
40 | asyncMiddleware(updateVideoPasswordList) | ||
41 | ) | ||
42 | |||
43 | videoPasswordRouter.delete('/:videoId/passwords/:passwordId', | ||
44 | authenticate, | ||
45 | asyncMiddleware(removeVideoPasswordValidator), | ||
46 | asyncRetryTransactionMiddleware(removeVideoPassword) | ||
47 | ) | ||
48 | |||
49 | // --------------------------------------------------------------------------- | ||
50 | |||
51 | export { | ||
52 | videoPasswordRouter | ||
53 | } | ||
54 | |||
55 | // --------------------------------------------------------------------------- | ||
56 | |||
57 | async 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 | |||
70 | async 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 | |||
91 | async 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 @@ | |||
1 | import express from 'express' | ||
2 | import { getVideoWithAttributes } from '@server/helpers/video' | ||
3 | import { StoryboardModel } from '@server/models/video/storyboard' | ||
4 | import { asyncMiddleware, videosGetValidator } from '../../../middlewares' | ||
5 | |||
6 | const storyboardRouter = express.Router() | ||
7 | |||
8 | storyboardRouter.get('/:id/storyboards', | ||
9 | asyncMiddleware(videosGetValidator), | ||
10 | asyncMiddleware(listStoryboards) | ||
11 | ) | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | export { | ||
16 | storyboardRouter | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | async 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 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { VideoTokensManager } from '@server/lib/video-tokens-manager' | 2 | import { VideoTokensManager } from '@server/lib/video-tokens-manager' |
3 | import { VideoToken } from '@shared/models' | 3 | import { VideoPrivacy, VideoToken } from '@shared/models' |
4 | import { asyncMiddleware, authenticate, videosCustomGetValidator } from '../../../middlewares' | 4 | import { asyncMiddleware, optionalAuthenticate, videoFileTokenValidator, videosCustomGetValidator } from '../../../middlewares' |
5 | 5 | ||
6 | const tokenRouter = express.Router() | 6 | const tokenRouter = express.Router() |
7 | 7 | ||
8 | tokenRouter.post('/:id/token', | 8 | tokenRouter.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 { | |||
22 | function generateToken (req: express.Request, res: express.Response) { | 23 | function 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' | |||
2 | import { Transaction } from 'sequelize/types' | 2 | import { Transaction } from 'sequelize/types' |
3 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' | 3 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' |
4 | import { addVideoJobsAfterUpdate, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | 4 | import { addVideoJobsAfterUpdate, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' |
5 | import { VideoPathManager } from '@server/lib/video-path-manager' | ||
6 | import { setVideoPrivacy } from '@server/lib/video-privacy' | 5 | import { setVideoPrivacy } from '@server/lib/video-privacy' |
7 | import { openapiOperationDoc } from '@server/middlewares/doc' | 6 | import { openapiOperationDoc } from '@server/middlewares/doc' |
8 | import { FilteredModelAttributes } from '@server/types' | 7 | import { FilteredModelAttributes } from '@server/types' |
9 | import { MVideoFullLight } from '@server/types/models' | 8 | import { MVideoFullLight } from '@server/types/models' |
10 | import { forceNumber } from '@shared/core-utils' | 9 | import { forceNumber } from '@shared/core-utils' |
11 | import { HttpStatusCode, VideoUpdate } from '@shared/models' | 10 | import { HttpStatusCode, VideoPrivacy, VideoUpdate } from '@shared/models' |
12 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 11 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
13 | import { resetSequelizeInstance } from '../../../helpers/database-utils' | 12 | import { resetSequelizeInstance } from '../../../helpers/database-utils' |
14 | import { createReqFiles } from '../../../helpers/express-utils' | 13 | import { createReqFiles } from '../../../helpers/express-utils' |
@@ -20,6 +19,9 @@ import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | |||
20 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares' | 19 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares' |
21 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | 20 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' |
22 | import { VideoModel } from '../../../models/video/video' | 21 | import { VideoModel } from '../../../models/video/video' |
22 | import { VideoPathManager } from '@server/lib/video-path-manager' | ||
23 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
24 | import { exists } from '@server/helpers/custom-validators/misc' | ||
23 | 25 | ||
24 | const lTags = loggerTagsFactory('api', 'video') | 26 | const lTags = loggerTagsFactory('api', 'video') |
25 | const auditLogger = auditLoggerFactory('videos') | 27 | const 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' | |||
14 | import { VideoSourceModel } from '@server/models/video/video-source' | 14 | import { VideoSourceModel } from '@server/models/video/video-source' |
15 | import { MUserId, MVideoFile, MVideoFullLight } from '@server/types/models' | 15 | import { MUserId, MVideoFile, MVideoFullLight } from '@server/types/models' |
16 | import { uuidToShort } from '@shared/extra-utils' | 16 | import { uuidToShort } from '@shared/extra-utils' |
17 | import { HttpStatusCode, VideoCreate, VideoState } from '@shared/models' | 17 | import { HttpStatusCode, VideoCreate, VideoPrivacy, VideoState } from '@shared/models' |
18 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 18 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
19 | import { createReqFiles } from '../../../helpers/express-utils' | 19 | import { createReqFiles } from '../../../helpers/express-utils' |
20 | import { logger, loggerTagsFactory } from '../../../helpers/logger' | 20 | import { logger, loggerTagsFactory } from '../../../helpers/logger' |
21 | import { MIMETYPES } from '../../../initializers/constants' | 21 | import { MIMETYPES } from '../../../initializers/constants' |
22 | import { sequelizeTypescript } from '../../../initializers/database' | 22 | import { sequelizeTypescript } from '../../../initializers/database' |
23 | import { Hooks } from '../../../lib/plugins/hooks' | 23 | import { Hooks } from '../../../lib/plugins/hooks' |
24 | import { generateVideoMiniature } from '../../../lib/thumbnail' | 24 | import { generateLocalVideoMiniature } from '../../../lib/thumbnail' |
25 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | 25 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' |
26 | import { | 26 | import { |
27 | asyncMiddleware, | 27 | asyncMiddleware, |
@@ -33,6 +33,7 @@ import { | |||
33 | } from '../../../middlewares' | 33 | } from '../../../middlewares' |
34 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | 34 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' |
35 | import { VideoModel } from '../../../models/video/video' | 35 | import { VideoModel } from '../../../models/video/video' |
36 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
36 | 37 | ||
37 | const lTags = loggerTagsFactory('api', 'video') | 38 | const lTags = loggerTagsFactory('api', 'video') |
38 | const auditLogger = auditLoggerFactory('videos') | 39 | const 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 | ||
68 | uploadRouter.delete('/upload-resumable', | 69 | uploadRouter.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 | ||
74 | uploadRouter.put('/upload-resumable', | 75 | uploadRouter.put('/upload-resumable', |
@@ -110,7 +111,7 @@ async function addVideoLegacy (req: express.Request, res: express.Response) { | |||
110 | async function addVideoResumable (req: express.Request, res: express.Response) { | 111 | async 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 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { logger } from '@server/helpers/logger' | 3 | import { logger } from '@server/helpers/logger' |
4 | import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' | 4 | import { VideoTorrentsSimpleFileCache } from '@server/lib/files-cache' |
5 | import { generateHLSFilePresignedUrl, generateWebVideoPresignedUrl } from '@server/lib/object-storage' | ||
5 | import { Hooks } from '@server/lib/plugins/hooks' | 6 | import { Hooks } from '@server/lib/plugins/hooks' |
6 | import { VideoPathManager } from '@server/lib/video-path-manager' | 7 | import { VideoPathManager } from '@server/lib/video-path-manager' |
7 | import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' | 8 | import { MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' |
8 | import { addQueryParams, forceNumber } from '@shared/core-utils' | 9 | import { forceNumber } from '@shared/core-utils' |
9 | import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models' | 10 | import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models' |
10 | import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants' | 11 | import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants' |
11 | import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares' | 12 | import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares' |
@@ -42,7 +43,7 @@ export { | |||
42 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
43 | 44 | ||
44 | async function downloadTorrent (req: express.Request, res: express.Response) { | 45 | async 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 | ||
195 | function redirectToObjectStorage (options: { | 196 | async 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' | |||
2 | import { CONFIG } from '@server/initializers/config' | 2 | import { CONFIG } from '@server/initializers/config' |
3 | import { WEBSERVER } from '@server/initializers/constants' | 3 | import { WEBSERVER } from '@server/initializers/constants' |
4 | import { getServerActor } from '@server/models/application/application' | 4 | import { getServerActor } from '@server/models/application/application' |
5 | import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils' | 5 | import { getCategoryLabel } from '@server/models/video/formatter' |
6 | import { DisplayOnlyForFollowerOptions } from '@server/models/video/sql/video' | 6 | import { DisplayOnlyForFollowerOptions } from '@server/models/video/sql/video' |
7 | import { VideoModel } from '@server/models/video/video' | 7 | import { VideoModel } from '@server/models/video/video' |
8 | import { MThumbnail, MUserDefault } from '@server/types/models' | 8 | import { 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 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' | 3 | import { CONFIG } from '@server/initializers/config' |
4 | import { MActorImage } from '@server/types/models' | ||
5 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' | 4 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' |
6 | import { logger } from '../helpers/logger' | 5 | import { FILES_CACHE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' |
7 | import { ACTOR_IMAGES_SIZE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' | 6 | import { |
8 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' | 7 | AvatarPermanentFileCache, |
9 | import { actorImagePathUnsafeCache, downloadActorImageFromWorker } from '../lib/local-actor' | 8 | VideoCaptionsSimpleFileCache, |
9 | VideoMiniaturePermanentFileCache, | ||
10 | VideoPreviewsSimpleFileCache, | ||
11 | VideoStoryboardsSimpleFileCache, | ||
12 | VideoTorrentsSimpleFileCache | ||
13 | } from '../lib/files-cache' | ||
10 | import { asyncMiddleware, handleStaticError } from '../middlewares' | 14 | import { asyncMiddleware, handleStaticError } from '../middlewares' |
11 | import { ActorImageModel } from '../models/actor/actor-image' | 15 | |
16 | // --------------------------------------------------------------------------- | ||
17 | // Cache initializations | ||
18 | // --------------------------------------------------------------------------- | ||
19 | |||
20 | VideoPreviewsSimpleFileCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE) | ||
21 | VideoCaptionsSimpleFileCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE) | ||
22 | VideoTorrentsSimpleFileCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE) | ||
23 | VideoStoryboardsSimpleFileCache.Instance.init(CONFIG.CACHE.STORYBOARDS.SIZE, FILES_CACHE.STORYBOARDS.MAX_AGE) | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
12 | 26 | ||
13 | const lazyStaticRouter = express.Router() | 27 | const lazyStaticRouter = express.Router() |
14 | 28 | ||
@@ -27,12 +41,24 @@ lazyStaticRouter.use( | |||
27 | ) | 41 | ) |
28 | 42 | ||
29 | lazyStaticRouter.use( | 43 | lazyStaticRouter.use( |
44 | LAZY_STATIC_PATHS.THUMBNAILS + ':filename', | ||
45 | asyncMiddleware(getThumbnail), | ||
46 | handleStaticError | ||
47 | ) | ||
48 | |||
49 | lazyStaticRouter.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 | ||
35 | lazyStaticRouter.use( | 55 | lazyStaticRouter.use( |
56 | LAZY_STATIC_PATHS.STORYBOARDS + ':filename', | ||
57 | asyncMiddleware(getStoryboard), | ||
58 | handleStaticError | ||
59 | ) | ||
60 | |||
61 | lazyStaticRouter.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 | // --------------------------------------------------------------------------- |
82 | const avatarPermanentFileCache = new AvatarPermanentFileCache() | ||
56 | 83 | ||
57 | async function getActorImage (req: express.Request, res: express.Response, next: express.NextFunction) { | 84 | function 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 | // --------------------------------------------------------------------------- |
91 | const videoMiniaturePermanentFileCache = new VideoMiniaturePermanentFileCache() | ||
101 | 92 | ||
102 | image.onDisk = false | 93 | function 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 | ||
111 | function getActorImageSize (image: MActorImage): { width: number, height: number } { | 99 | // --------------------------------------------------------------------------- |
112 | if (image.width && image.height) { | 100 | |
113 | return { | 101 | async 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 | ||
122 | async function getPreview (req: express.Request, res: express.Response) { | 108 | async 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 | ||
129 | async function getVideoCaption (req: express.Request, res: express.Response) { | 115 | async 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 | ||
136 | async function getTorrent (req: express.Request, res: express.Response) { | 122 | async 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 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants' | 3 | import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants' |
4 | import { proxifyHLS, proxifyWebTorrentFile } from '@server/lib/object-storage' | 4 | import { proxifyHLS, proxifyWebVideoFile } from '@server/lib/object-storage' |
5 | import { | 5 | import { |
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 | ||
16 | objectStorageProxyRouter.use(cors()) | 16 | objectStorageProxyRouter.use(cors()) |
17 | 17 | ||
18 | objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEBSEED + ':filename', | 18 | objectStorageProxyRouter.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 | ||
25 | objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename', | 26 | objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename', |
@@ -35,10 +36,10 @@ export { | |||
35 | objectStorageProxyRouter | 36 | objectStorageProxyRouter |
36 | } | 37 | } |
37 | 38 | ||
38 | function proxifyWebTorrentController (req: express.Request, res: express.Response) { | 39 | function 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 | ||
44 | function proxifyHLSController (req: express.Request, res: express.Response) { | 45 | function 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' | |||
6 | import { | 6 | import { |
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() | |||
21 | staticRouter.use(cors()) | 21 | staticRouter.use(cors()) |
22 | 22 | ||
23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
24 | // WebTorrent/Classic videos | 24 | // Web videos/Classic videos |
25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
26 | 26 | ||
27 | const privateWebTorrentStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true | 27 | const privateWebVideoStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true |
28 | ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles) ] | 28 | ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessVideoPrivateWebVideoFiles) ] |
29 | : [] | 29 | : [] |
30 | 30 | ||
31 | staticRouter.use( | 31 | staticRouter.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 | ) |
37 | staticRouter.use( | 37 | staticRouter.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 |
76 | const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR | 76 | const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR |
77 | staticRouter.use( | 77 | staticRouter.use( |
78 | STATIC_PATHS.THUMBNAILS, | 78 | STATIC_PATHS.THUMBNAILS, |