diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/lazy-static.ts | 19 | ||||
-rw-r--r-- | server/controllers/static.ts | 2 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/lib/activitypub/videos/shared/abstract-builder.ts | 25 | ||||
-rw-r--r-- | server/lib/activitypub/videos/shared/creator.ts | 81 | ||||
-rw-r--r-- | server/lib/activitypub/videos/updater.ts | 11 | ||||
-rw-r--r-- | server/lib/files-cache/avatar-permanent-file-cache.ts | 4 | ||||
-rw-r--r-- | server/lib/files-cache/index.ts | 1 | ||||
-rw-r--r-- | server/lib/files-cache/video-miniature-permanent-file-cache.ts | 28 | ||||
-rw-r--r-- | server/lib/thumbnail.ts | 70 | ||||
-rw-r--r-- | server/lib/video-pre-import.ts | 5 | ||||
-rw-r--r-- | server/models/video/thumbnail.ts | 8 | ||||
-rw-r--r-- | server/models/video/video-playlist.ts | 6 | ||||
-rw-r--r-- | server/tests/api/videos/video-imports.ts | 2 |
14 files changed, 151 insertions, 113 deletions
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts index 8e18b0642..dad30365c 100644 --- a/server/controllers/lazy-static.ts +++ b/server/controllers/lazy-static.ts | |||
@@ -6,6 +6,7 @@ import { FILES_CACHE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/ | |||
6 | import { | 6 | import { |
7 | AvatarPermanentFileCache, | 7 | AvatarPermanentFileCache, |
8 | VideoCaptionsSimpleFileCache, | 8 | VideoCaptionsSimpleFileCache, |
9 | VideoMiniaturePermanentFileCache, | ||
9 | VideoPreviewsSimpleFileCache, | 10 | VideoPreviewsSimpleFileCache, |
10 | VideoStoryboardsSimpleFileCache, | 11 | VideoStoryboardsSimpleFileCache, |
11 | VideoTorrentsSimpleFileCache | 12 | VideoTorrentsSimpleFileCache |
@@ -40,6 +41,12 @@ lazyStaticRouter.use( | |||
40 | ) | 41 | ) |
41 | 42 | ||
42 | lazyStaticRouter.use( | 43 | lazyStaticRouter.use( |
44 | LAZY_STATIC_PATHS.THUMBNAILS + ':filename', | ||
45 | asyncMiddleware(getThumbnail), | ||
46 | handleStaticError | ||
47 | ) | ||
48 | |||
49 | lazyStaticRouter.use( | ||
43 | LAZY_STATIC_PATHS.PREVIEWS + ':filename', | 50 | LAZY_STATIC_PATHS.PREVIEWS + ':filename', |
44 | asyncMiddleware(getPreview), | 51 | asyncMiddleware(getPreview), |
45 | handleStaticError | 52 | handleStaticError |
@@ -72,7 +79,6 @@ export { | |||
72 | } | 79 | } |
73 | 80 | ||
74 | // --------------------------------------------------------------------------- | 81 | // --------------------------------------------------------------------------- |
75 | |||
76 | const avatarPermanentFileCache = new AvatarPermanentFileCache() | 82 | const avatarPermanentFileCache = new AvatarPermanentFileCache() |
77 | 83 | ||
78 | function getActorImage (req: express.Request, res: express.Response, next: express.NextFunction) { | 84 | function getActorImage (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -81,6 +87,17 @@ function getActorImage (req: express.Request, res: express.Response, next: expre | |||
81 | return avatarPermanentFileCache.lazyServe({ filename, res, next }) | 87 | return avatarPermanentFileCache.lazyServe({ filename, res, next }) |
82 | } | 88 | } |
83 | 89 | ||
90 | // --------------------------------------------------------------------------- | ||
91 | const videoMiniaturePermanentFileCache = new VideoMiniaturePermanentFileCache() | ||
92 | |||
93 | function getThumbnail (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
94 | const filename = req.params.filename | ||
95 | |||
96 | return videoMiniaturePermanentFileCache.lazyServe({ filename, res, next }) | ||
97 | } | ||
98 | |||
99 | // --------------------------------------------------------------------------- | ||
100 | |||
84 | async function getPreview (req: express.Request, res: express.Response) { | 101 | async function getPreview (req: express.Request, res: express.Response) { |
85 | const result = await VideoPreviewsSimpleFileCache.Instance.getFilePath(req.params.filename) | 102 | const result = await VideoPreviewsSimpleFileCache.Instance.getFilePath(req.params.filename) |
86 | if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end() | 103 | if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end() |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index 9baff94c0..bbd0dd011 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -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, |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 511aa91cc..ced18eef0 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -747,6 +747,7 @@ const NSFW_POLICY_TYPES: { [ id: string ]: NSFWPolicyType } = { | |||
747 | 747 | ||
748 | // Express static paths (router) | 748 | // Express static paths (router) |
749 | const STATIC_PATHS = { | 749 | const STATIC_PATHS = { |
750 | // TODO: deprecated in v6, to remove | ||
750 | THUMBNAILS: '/static/thumbnails/', | 751 | THUMBNAILS: '/static/thumbnails/', |
751 | 752 | ||
752 | WEBSEED: '/static/webseed/', | 753 | WEBSEED: '/static/webseed/', |
@@ -765,6 +766,7 @@ const STATIC_DOWNLOAD_PATHS = { | |||
765 | HLS_VIDEOS: '/download/streaming-playlists/hls/videos/' | 766 | HLS_VIDEOS: '/download/streaming-playlists/hls/videos/' |
766 | } | 767 | } |
767 | const LAZY_STATIC_PATHS = { | 768 | const LAZY_STATIC_PATHS = { |
769 | THUMBNAILS: '/lazy-static/thumbnails/', | ||
768 | BANNERS: '/lazy-static/banners/', | 770 | BANNERS: '/lazy-static/banners/', |
769 | AVATARS: '/lazy-static/avatars/', | 771 | AVATARS: '/lazy-static/avatars/', |
770 | PREVIEWS: '/lazy-static/previews/', | 772 | PREVIEWS: '/lazy-static/previews/', |
diff --git a/server/lib/activitypub/videos/shared/abstract-builder.ts b/server/lib/activitypub/videos/shared/abstract-builder.ts index e50bf29dc..4f74316d3 100644 --- a/server/lib/activitypub/videos/shared/abstract-builder.ts +++ b/server/lib/activitypub/videos/shared/abstract-builder.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { CreationAttributes, Transaction } from 'sequelize/types' | 1 | import { CreationAttributes, Transaction } from 'sequelize/types' |
2 | import { deleteAllModels, filterNonExistingModels } from '@server/helpers/database-utils' | 2 | import { deleteAllModels, filterNonExistingModels } from '@server/helpers/database-utils' |
3 | import { logger, LoggerTagsFn } from '@server/helpers/logger' | 3 | import { logger, LoggerTagsFn } from '@server/helpers/logger' |
4 | import { updateRemoteThumbnail, updateVideoMiniatureFromUrl } from '@server/lib/thumbnail' | 4 | import { updateRemoteThumbnail } from '@server/lib/thumbnail' |
5 | import { setVideoTags } from '@server/lib/video' | 5 | import { setVideoTags } from '@server/lib/video' |
6 | import { StoryboardModel } from '@server/models/video/storyboard' | 6 | import { StoryboardModel } from '@server/models/video/storyboard' |
7 | import { VideoCaptionModel } from '@server/models/video/video-caption' | 7 | import { VideoCaptionModel } from '@server/models/video/video-caption' |
@@ -11,7 +11,6 @@ import { VideoStreamingPlaylistModel } from '@server/models/video/video-streamin | |||
11 | import { | 11 | import { |
12 | MStreamingPlaylistFiles, | 12 | MStreamingPlaylistFiles, |
13 | MStreamingPlaylistFilesVideo, | 13 | MStreamingPlaylistFilesVideo, |
14 | MThumbnail, | ||
15 | MVideoCaption, | 14 | MVideoCaption, |
16 | MVideoFile, | 15 | MVideoFile, |
17 | MVideoFullLight, | 16 | MVideoFullLight, |
@@ -42,16 +41,22 @@ export abstract class APVideoAbstractBuilder { | |||
42 | return getOrCreateAPActor(channel.id, 'all') | 41 | return getOrCreateAPActor(channel.id, 'all') |
43 | } | 42 | } |
44 | 43 | ||
45 | protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> { | 44 | protected async setThumbnail (video: MVideoThumbnail, t?: Transaction) { |
46 | return updateVideoMiniatureFromUrl({ | 45 | const miniatureIcon = getThumbnailFromIcons(this.videoObject) |
47 | downloadUrl: getThumbnailFromIcons(this.videoObject).url, | 46 | if (!miniatureIcon) { |
48 | video, | 47 | logger.warn('Cannot find thumbnail in video object', { object: this.videoObject }) |
49 | type: ThumbnailType.MINIATURE | ||
50 | }).catch(err => { | ||
51 | logger.warn('Cannot generate thumbnail of %s.', this.videoObject.id, { err, ...this.lTags() }) | ||
52 | |||
53 | return undefined | 48 | return undefined |
49 | } | ||
50 | |||
51 | const miniatureModel = updateRemoteThumbnail({ | ||
52 | fileUrl: miniatureIcon.url, | ||
53 | video, | ||
54 | type: ThumbnailType.MINIATURE, | ||
55 | size: miniatureIcon, | ||
56 | onDisk: false // Lazy download remote thumbnails | ||
54 | }) | 57 | }) |
58 | |||
59 | await video.addAndSaveThumbnail(miniatureModel, t) | ||
55 | } | 60 | } |
56 | 61 | ||
57 | protected async setPreview (video: MVideoFullLight, t?: Transaction) { | 62 | protected async setPreview (video: MVideoFullLight, t?: Transaction) { |
diff --git a/server/lib/activitypub/videos/shared/creator.ts b/server/lib/activitypub/videos/shared/creator.ts index e6d7bc23c..3d646ef66 100644 --- a/server/lib/activitypub/videos/shared/creator.ts +++ b/server/lib/activitypub/videos/shared/creator.ts | |||
@@ -4,7 +4,7 @@ import { sequelizeTypescript } from '@server/initializers/database' | |||
4 | import { Hooks } from '@server/lib/plugins/hooks' | 4 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' | 5 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' |
6 | import { VideoModel } from '@server/models/video/video' | 6 | import { VideoModel } from '@server/models/video/video' |
7 | import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models' | 7 | import { MVideoFullLight, MVideoThumbnail } from '@server/types/models' |
8 | import { VideoObject } from '@shared/models' | 8 | import { VideoObject } from '@shared/models' |
9 | import { APVideoAbstractBuilder } from './abstract-builder' | 9 | import { APVideoAbstractBuilder } from './abstract-builder' |
10 | import { getVideoAttributesFromObject } from './object-to-model-attributes' | 10 | import { getVideoAttributesFromObject } from './object-to-model-attributes' |
@@ -27,65 +27,38 @@ export class APVideoCreator extends APVideoAbstractBuilder { | |||
27 | const videoData = getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to) | 27 | const videoData = getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to) |
28 | const video = VideoModel.build({ ...videoData, likes: 0, dislikes: 0 }) as MVideoThumbnail | 28 | const video = VideoModel.build({ ...videoData, likes: 0, dislikes: 0 }) as MVideoThumbnail |
29 | 29 | ||
30 | const promiseThumbnail = this.tryToGenerateThumbnail(video) | ||
31 | |||
32 | let thumbnailModel: MThumbnail | ||
33 | if (waitThumbnail === true) { | ||
34 | thumbnailModel = await promiseThumbnail | ||
35 | } | ||
36 | |||
37 | const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { | 30 | const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { |
38 | try { | 31 | const videoCreated = await video.save({ transaction: t }) as MVideoFullLight |
39 | const videoCreated = await video.save({ transaction: t }) as MVideoFullLight | 32 | videoCreated.VideoChannel = channel |
40 | videoCreated.VideoChannel = channel | 33 | |
41 | 34 | await this.setThumbnail(videoCreated, t) | |
42 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 35 | await this.setPreview(videoCreated, t) |
43 | 36 | await this.setWebTorrentFiles(videoCreated, t) | |
44 | await this.setPreview(videoCreated, t) | 37 | await this.setStreamingPlaylists(videoCreated, t) |
45 | await this.setWebTorrentFiles(videoCreated, t) | 38 | await this.setTags(videoCreated, t) |
46 | await this.setStreamingPlaylists(videoCreated, t) | 39 | await this.setTrackers(videoCreated, t) |
47 | await this.setTags(videoCreated, t) | 40 | await this.insertOrReplaceCaptions(videoCreated, t) |
48 | await this.setTrackers(videoCreated, t) | 41 | await this.insertOrReplaceLive(videoCreated, t) |
49 | await this.insertOrReplaceCaptions(videoCreated, t) | 42 | await this.insertOrReplaceStoryboard(videoCreated, t) |
50 | await this.insertOrReplaceLive(videoCreated, t) | 43 | |
51 | await this.insertOrReplaceStoryboard(videoCreated, t) | 44 | // We added a video in this channel, set it as updated |
52 | 45 | await channel.setAsUpdated(t) | |
53 | // We added a video in this channel, set it as updated | 46 | |
54 | await channel.setAsUpdated(t) | 47 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ |
55 | 48 | video: videoCreated, | |
56 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ | 49 | user: undefined, |
57 | video: videoCreated, | 50 | isRemote: true, |
58 | user: undefined, | 51 | isNew: true, |
59 | isRemote: true, | 52 | transaction: t |
60 | isNew: true, | 53 | }) |
61 | transaction: t | ||
62 | }) | ||
63 | |||
64 | logger.info('Remote video with uuid %s inserted.', this.videoObject.uuid, this.lTags()) | ||
65 | 54 | ||
66 | Hooks.runAction('action:activity-pub.remote-video.created', { video: videoCreated, videoAPObject: this.videoObject }) | 55 | logger.info('Remote video with uuid %s inserted.', this.videoObject.uuid, this.lTags()) |
67 | 56 | ||
68 | return { autoBlacklisted, videoCreated } | 57 | Hooks.runAction('action:activity-pub.remote-video.created', { video: videoCreated, videoAPObject: this.videoObject }) |
69 | } catch (err) { | ||
70 | // FIXME: Use rollback hook when https://github.com/sequelize/sequelize/pull/13038 is released | ||
71 | if (thumbnailModel) await thumbnailModel.removeThumbnail() | ||
72 | 58 | ||
73 | throw err | 59 | return { autoBlacklisted, videoCreated } |
74 | } | ||
75 | }) | 60 | }) |
76 | 61 | ||
77 | if (waitThumbnail === false) { | ||
78 | // Error is already caught above | ||
79 | // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
80 | promiseThumbnail.then(thumbnailModel => { | ||
81 | if (!thumbnailModel) return | ||
82 | |||
83 | thumbnailModel = videoCreated.id | ||
84 | |||
85 | return thumbnailModel.save() | ||
86 | }) | ||
87 | } | ||
88 | |||
89 | return { autoBlacklisted, videoCreated } | 62 | return { autoBlacklisted, videoCreated } |
90 | } | 63 | } |
91 | } | 64 | } |
diff --git a/server/lib/activitypub/videos/updater.ts b/server/lib/activitypub/videos/updater.ts index 3a0886523..c98bce662 100644 --- a/server/lib/activitypub/videos/updater.ts +++ b/server/lib/activitypub/videos/updater.ts | |||
@@ -41,7 +41,7 @@ export class APVideoUpdater extends APVideoAbstractBuilder { | |||
41 | try { | 41 | try { |
42 | const channelActor = await this.getOrCreateVideoChannelFromVideoObject() | 42 | const channelActor = await this.getOrCreateVideoChannelFromVideoObject() |
43 | 43 | ||
44 | const thumbnailModel = await this.tryToGenerateThumbnail(this.video) | 44 | const thumbnailModel = await this.setThumbnail(this.video) |
45 | 45 | ||
46 | this.checkChannelUpdateOrThrow(channelActor) | 46 | this.checkChannelUpdateOrThrow(channelActor) |
47 | 47 | ||
@@ -58,8 +58,13 @@ export class APVideoUpdater extends APVideoAbstractBuilder { | |||
58 | runInReadCommittedTransaction(t => this.setTags(videoUpdated, t)), | 58 | runInReadCommittedTransaction(t => this.setTags(videoUpdated, t)), |
59 | runInReadCommittedTransaction(t => this.setTrackers(videoUpdated, t)), | 59 | runInReadCommittedTransaction(t => this.setTrackers(videoUpdated, t)), |
60 | runInReadCommittedTransaction(t => this.setStoryboard(videoUpdated, t)), | 60 | runInReadCommittedTransaction(t => this.setStoryboard(videoUpdated, t)), |
61 | this.setOrDeleteLive(videoUpdated), | 61 | runInReadCommittedTransaction(t => { |
62 | this.setPreview(videoUpdated) | 62 | return Promise.all([ |
63 | this.setPreview(videoUpdated, t), | ||
64 | this.setThumbnail(videoUpdated, t) | ||
65 | ]) | ||
66 | }), | ||
67 | this.setOrDeleteLive(videoUpdated) | ||
63 | ]) | 68 | ]) |
64 | 69 | ||
65 | await runInReadCommittedTransaction(t => this.setCaptions(videoUpdated, t)) | 70 | await runInReadCommittedTransaction(t => this.setCaptions(videoUpdated, t)) |
diff --git a/server/lib/files-cache/avatar-permanent-file-cache.ts b/server/lib/files-cache/avatar-permanent-file-cache.ts index 89228c5a5..1d77c5bc1 100644 --- a/server/lib/files-cache/avatar-permanent-file-cache.ts +++ b/server/lib/files-cache/avatar-permanent-file-cache.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { CONFIG } from '@server/initializers/config' | ||
1 | import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants' | 2 | import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants' |
2 | import { ActorImageModel } from '@server/models/actor/actor-image' | 3 | import { ActorImageModel } from '@server/models/actor/actor-image' |
3 | import { MActorImage } from '@server/types/models' | 4 | import { MActorImage } from '@server/types/models' |
4 | import { AbstractPermanentFileCache } from './shared' | 5 | import { AbstractPermanentFileCache } from './shared' |
5 | import { CONFIG } from '@server/initializers/config' | ||
6 | 6 | ||
7 | export class AvatarPermanentFileCache extends AbstractPermanentFileCache<ActorImageModel> { | 7 | export class AvatarPermanentFileCache extends AbstractPermanentFileCache<MActorImage> { |
8 | 8 | ||
9 | constructor () { | 9 | constructor () { |
10 | super(CONFIG.STORAGE.ACTOR_IMAGES) | 10 | super(CONFIG.STORAGE.ACTOR_IMAGES) |
diff --git a/server/lib/files-cache/index.ts b/server/lib/files-cache/index.ts index cc11d5385..5630a9b80 100644 --- a/server/lib/files-cache/index.ts +++ b/server/lib/files-cache/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export * from './avatar-permanent-file-cache' | 1 | export * from './avatar-permanent-file-cache' |
2 | export * from './video-miniature-permanent-file-cache' | ||
2 | export * from './video-captions-simple-file-cache' | 3 | export * from './video-captions-simple-file-cache' |
3 | export * from './video-previews-simple-file-cache' | 4 | export * from './video-previews-simple-file-cache' |
4 | export * from './video-storyboards-simple-file-cache' | 5 | export * from './video-storyboards-simple-file-cache' |
diff --git a/server/lib/files-cache/video-miniature-permanent-file-cache.ts b/server/lib/files-cache/video-miniature-permanent-file-cache.ts new file mode 100644 index 000000000..35d9466f7 --- /dev/null +++ b/server/lib/files-cache/video-miniature-permanent-file-cache.ts | |||
@@ -0,0 +1,28 @@ | |||
1 | import { CONFIG } from '@server/initializers/config' | ||
2 | import { THUMBNAILS_SIZE } from '@server/initializers/constants' | ||
3 | import { ThumbnailModel } from '@server/models/video/thumbnail' | ||
4 | import { MThumbnail } from '@server/types/models' | ||
5 | import { ThumbnailType } from '@shared/models' | ||
6 | import { AbstractPermanentFileCache } from './shared' | ||
7 | |||
8 | export class VideoMiniaturePermanentFileCache extends AbstractPermanentFileCache<MThumbnail> { | ||
9 | |||
10 | constructor () { | ||
11 | super(CONFIG.STORAGE.THUMBNAILS_DIR) | ||
12 | } | ||
13 | |||
14 | protected loadModel (filename: string) { | ||
15 | return ThumbnailModel.loadByFilename(filename, ThumbnailType.MINIATURE) | ||
16 | } | ||
17 | |||
18 | protected getImageSize (image: MThumbnail): { width: number, height: number } { | ||
19 | if (image.width && image.height) { | ||
20 | return { | ||
21 | height: image.height, | ||
22 | width: image.width | ||
23 | } | ||
24 | } | ||
25 | |||
26 | return THUMBNAILS_SIZE | ||
27 | } | ||
28 | } | ||
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index e792567ff..90f5dc2c8 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -60,38 +60,6 @@ function updatePlaylistMiniatureFromUrl (options: { | |||
60 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl, onDisk: true }) | 60 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl, onDisk: true }) |
61 | } | 61 | } |
62 | 62 | ||
63 | function updateVideoMiniatureFromUrl (options: { | ||
64 | downloadUrl: string | ||
65 | video: MVideoThumbnail | ||
66 | type: ThumbnailType | ||
67 | size?: ImageSize | ||
68 | }) { | ||
69 | const { downloadUrl, video, type, size } = options | ||
70 | const { filename: updatedFilename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
71 | |||
72 | // Only save the file URL if it is a remote video | ||
73 | const fileUrl = video.isOwned() | ||
74 | ? null | ||
75 | : downloadUrl | ||
76 | |||
77 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, downloadUrl, video) | ||
78 | |||
79 | // Do not change the thumbnail filename if the file did not change | ||
80 | const filename = thumbnailUrlChanged | ||
81 | ? updatedFilename | ||
82 | : existingThumbnail.filename | ||
83 | |||
84 | const thumbnailCreator = () => { | ||
85 | if (thumbnailUrlChanged) { | ||
86 | return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } }) | ||
87 | } | ||
88 | |||
89 | return Promise.resolve() | ||
90 | } | ||
91 | |||
92 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl, onDisk: true }) | ||
93 | } | ||
94 | |||
95 | function updateLocalVideoMiniatureFromExisting (options: { | 63 | function updateLocalVideoMiniatureFromExisting (options: { |
96 | inputPath: string | 64 | inputPath: string |
97 | video: MVideoThumbnail | 65 | video: MVideoThumbnail |
@@ -157,6 +125,40 @@ function generateLocalVideoMiniature (options: { | |||
157 | }) | 125 | }) |
158 | } | 126 | } |
159 | 127 | ||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | function updateVideoMiniatureFromUrl (options: { | ||
131 | downloadUrl: string | ||
132 | video: MVideoThumbnail | ||
133 | type: ThumbnailType | ||
134 | size?: ImageSize | ||
135 | }) { | ||
136 | const { downloadUrl, video, type, size } = options | ||
137 | const { filename: updatedFilename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
138 | |||
139 | // Only save the file URL if it is a remote video | ||
140 | const fileUrl = video.isOwned() | ||
141 | ? null | ||
142 | : downloadUrl | ||
143 | |||
144 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, downloadUrl, video) | ||
145 | |||
146 | // Do not change the thumbnail filename if the file did not change | ||
147 | const filename = thumbnailUrlChanged | ||
148 | ? updatedFilename | ||
149 | : existingThumbnail.filename | ||
150 | |||
151 | const thumbnailCreator = () => { | ||
152 | if (thumbnailUrlChanged) { | ||
153 | return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } }) | ||
154 | } | ||
155 | |||
156 | return Promise.resolve() | ||
157 | } | ||
158 | |||
159 | return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl, onDisk: true }) | ||
160 | } | ||
161 | |||
160 | function updateRemoteThumbnail (options: { | 162 | function updateRemoteThumbnail (options: { |
161 | fileUrl: string | 163 | fileUrl: string |
162 | video: MVideoThumbnail | 164 | video: MVideoThumbnail |
@@ -167,12 +169,10 @@ function updateRemoteThumbnail (options: { | |||
167 | const { fileUrl, video, type, size, onDisk } = options | 169 | const { fileUrl, video, type, size, onDisk } = options |
168 | const { filename: generatedFilename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 170 | const { filename: generatedFilename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
169 | 171 | ||
170 | const thumbnailUrlChanged = hasThumbnailUrlChanged(existingThumbnail, fileUrl, video) | ||
171 | |||
172 | const thumbnail = existingThumbnail || new ThumbnailModel() | 172 | const thumbnail = existingThumbnail || new ThumbnailModel() |
173 | 173 | ||
174 | // Do not change the thumbnail filename if the file did not change | 174 | // Do not change the thumbnail filename if the file did not change |
175 | if (thumbnailUrlChanged) { | 175 | if (hasThumbnailUrlChanged(existingThumbnail, fileUrl, video)) { |
176 | thumbnail.filename = generatedFilename | 176 | thumbnail.filename = generatedFilename |
177 | } | 177 | } |
178 | 178 | ||
diff --git a/server/lib/video-pre-import.ts b/server/lib/video-pre-import.ts index ef9c38731..1471d4091 100644 --- a/server/lib/video-pre-import.ts +++ b/server/lib/video-pre-import.ts | |||
@@ -262,13 +262,16 @@ async function forgeThumbnail ({ inputPath, video, downloadUrl, type }: { | |||
262 | type, | 262 | type, |
263 | automaticallyGenerated: false | 263 | automaticallyGenerated: false |
264 | }) | 264 | }) |
265 | } else if (downloadUrl) { | 265 | } |
266 | |||
267 | if (downloadUrl) { | ||
266 | try { | 268 | try { |
267 | return await updateVideoMiniatureFromUrl({ downloadUrl, video, type }) | 269 | return await updateVideoMiniatureFromUrl({ downloadUrl, video, type }) |
268 | } catch (err) { | 270 | } catch (err) { |
269 | logger.warn('Cannot process thumbnail %s from youtube-dl.', downloadUrl, { err }) | 271 | logger.warn('Cannot process thumbnail %s from youtube-dl.', downloadUrl, { err }) |
270 | } | 272 | } |
271 | } | 273 | } |
274 | |||
272 | return null | 275 | return null |
273 | } | 276 | } |
274 | 277 | ||
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index 2a1f6a7b4..1722acdb4 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -21,7 +21,7 @@ import { AttributesOnly } from '@shared/typescript-utils' | |||
21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
22 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
23 | import { CONFIG } from '../../initializers/config' | 23 | import { CONFIG } from '../../initializers/config' |
24 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, STATIC_PATHS, WEBSERVER } from '../../initializers/constants' | 24 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, WEBSERVER } from '../../initializers/constants' |
25 | import { VideoModel } from './video' | 25 | import { VideoModel } from './video' |
26 | import { VideoPlaylistModel } from './video-playlist' | 26 | import { VideoPlaylistModel } from './video-playlist' |
27 | 27 | ||
@@ -110,7 +110,7 @@ export class ThumbnailModel extends Model<Partial<AttributesOnly<ThumbnailModel> | |||
110 | [ThumbnailType.MINIATURE]: { | 110 | [ThumbnailType.MINIATURE]: { |
111 | label: 'miniature', | 111 | label: 'miniature', |
112 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, | 112 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, |
113 | staticPath: STATIC_PATHS.THUMBNAILS | 113 | staticPath: LAZY_STATIC_PATHS.THUMBNAILS |
114 | }, | 114 | }, |
115 | [ThumbnailType.PREVIEW]: { | 115 | [ThumbnailType.PREVIEW]: { |
116 | label: 'preview', | 116 | label: 'preview', |
@@ -201,4 +201,8 @@ export class ThumbnailModel extends Model<Partial<AttributesOnly<ThumbnailModel> | |||
201 | 201 | ||
202 | this.previousThumbnailFilename = undefined | 202 | this.previousThumbnailFilename = undefined |
203 | } | 203 | } |
204 | |||
205 | isOwned () { | ||
206 | return !this.fileUrl | ||
207 | } | ||
204 | } | 208 | } |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index faf4bea78..15999d409 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -32,7 +32,7 @@ import { | |||
32 | import { | 32 | import { |
33 | ACTIVITY_PUB, | 33 | ACTIVITY_PUB, |
34 | CONSTRAINTS_FIELDS, | 34 | CONSTRAINTS_FIELDS, |
35 | STATIC_PATHS, | 35 | LAZY_STATIC_PATHS, |
36 | THUMBNAILS_SIZE, | 36 | THUMBNAILS_SIZE, |
37 | VIDEO_PLAYLIST_PRIVACIES, | 37 | VIDEO_PLAYLIST_PRIVACIES, |
38 | VIDEO_PLAYLIST_TYPES, | 38 | VIDEO_PLAYLIST_TYPES, |
@@ -592,13 +592,13 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli | |||
592 | getThumbnailUrl () { | 592 | getThumbnailUrl () { |
593 | if (!this.hasThumbnail()) return null | 593 | if (!this.hasThumbnail()) return null |
594 | 594 | ||
595 | return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename | 595 | return WEBSERVER.URL + LAZY_STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename |
596 | } | 596 | } |
597 | 597 | ||
598 | getThumbnailStaticPath () { | 598 | getThumbnailStaticPath () { |
599 | if (!this.hasThumbnail()) return null | 599 | if (!this.hasThumbnail()) return null |
600 | 600 | ||
601 | return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) | 601 | return join(LAZY_STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) |
602 | } | 602 | } |
603 | 603 | ||
604 | getWatchStaticPath () { | 604 | getWatchStaticPath () { |
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts index a684a55a0..4f3149d52 100644 --- a/server/tests/api/videos/video-imports.ts +++ b/server/tests/api/videos/video-imports.ts | |||
@@ -119,7 +119,7 @@ describe('Test video imports', function () { | |||
119 | expect(video.name).to.equal('small video - youtube') | 119 | expect(video.name).to.equal('small video - youtube') |
120 | 120 | ||
121 | { | 121 | { |
122 | expect(video.thumbnailPath).to.match(new RegExp(`^/static/thumbnails/.+.jpg$`)) | 122 | expect(video.thumbnailPath).to.match(new RegExp(`^/lazy-static/thumbnails/.+.jpg$`)) |
123 | expect(video.previewPath).to.match(new RegExp(`^/lazy-static/previews/.+.jpg$`)) | 123 | expect(video.previewPath).to.match(new RegExp(`^/lazy-static/previews/.+.jpg$`)) |
124 | 124 | ||
125 | const suffix = mode === 'yt-dlp' | 125 | const suffix = mode === 'yt-dlp' |