diff options
-rwxr-xr-x | scripts/prune-storage.ts | 9 | ||||
-rw-r--r-- | server/controllers/lazy-static.ts | 4 | ||||
-rw-r--r-- | server/controllers/static.ts | 29 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/initializers/migrations/0575-duplicate-thumbnail.ts | 24 | ||||
-rw-r--r-- | server/lib/activitypub/videos.ts | 18 | ||||
-rw-r--r-- | server/lib/files-cache/videos-preview-cache.ts | 15 | ||||
-rw-r--r-- | server/lib/thumbnail.ts | 18 | ||||
-rw-r--r-- | server/models/video/thumbnail.ts | 31 | ||||
-rw-r--r-- | server/models/video/video.ts | 5 | ||||
-rw-r--r-- | server/types/models/video/thumbnail.ts | 12 |
11 files changed, 114 insertions, 53 deletions
diff --git a/scripts/prune-storage.ts b/scripts/prune-storage.ts index 1def1d792..788d97997 100755 --- a/scripts/prune-storage.ts +++ b/scripts/prune-storage.ts | |||
@@ -13,6 +13,7 @@ import { getUUIDFromFilename } from '../server/helpers/utils' | |||
13 | import { ThumbnailModel } from '../server/models/video/thumbnail' | 13 | import { ThumbnailModel } from '../server/models/video/thumbnail' |
14 | import { AvatarModel } from '../server/models/avatar/avatar' | 14 | import { AvatarModel } from '../server/models/avatar/avatar' |
15 | import { uniq, values } from 'lodash' | 15 | import { uniq, values } from 'lodash' |
16 | import { ThumbnailType } from '@shared/models' | ||
16 | 17 | ||
17 | run() | 18 | run() |
18 | .then(() => process.exit(0)) | 19 | .then(() => process.exit(0)) |
@@ -39,8 +40,8 @@ async function run () { | |||
39 | 40 | ||
40 | await pruneDirectory(CONFIG.STORAGE.REDUNDANCY_DIR, doesRedundancyExist), | 41 | await pruneDirectory(CONFIG.STORAGE.REDUNDANCY_DIR, doesRedundancyExist), |
41 | 42 | ||
42 | await pruneDirectory(CONFIG.STORAGE.PREVIEWS_DIR, doesThumbnailExist(true)), | 43 | await pruneDirectory(CONFIG.STORAGE.PREVIEWS_DIR, doesThumbnailExist(true, ThumbnailType.PREVIEW)), |
43 | await pruneDirectory(CONFIG.STORAGE.THUMBNAILS_DIR, doesThumbnailExist(false)), | 44 | await pruneDirectory(CONFIG.STORAGE.THUMBNAILS_DIR, doesThumbnailExist(false, ThumbnailType.MINIATURE)), |
44 | 45 | ||
45 | await pruneDirectory(CONFIG.STORAGE.AVATARS_DIR, doesAvatarExist) | 46 | await pruneDirectory(CONFIG.STORAGE.AVATARS_DIR, doesAvatarExist) |
46 | ) | 47 | ) |
@@ -92,9 +93,9 @@ function doesVideoExist (keepOnlyOwned: boolean) { | |||
92 | } | 93 | } |
93 | } | 94 | } |
94 | 95 | ||
95 | function doesThumbnailExist (keepOnlyOwned: boolean) { | 96 | function doesThumbnailExist (keepOnlyOwned: boolean, type: ThumbnailType) { |
96 | return async (file: string) => { | 97 | return async (file: string) => { |
97 | const thumbnail = await ThumbnailModel.loadByName(file) | 98 | const thumbnail = await ThumbnailModel.loadWithVideoByName(file, type) |
98 | if (!thumbnail) return false | 99 | if (!thumbnail) return false |
99 | 100 | ||
100 | if (keepOnlyOwned) { | 101 | if (keepOnlyOwned) { |
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts index 5c6369c9e..847d24fd4 100644 --- a/server/controllers/lazy-static.ts +++ b/server/controllers/lazy-static.ts | |||
@@ -18,7 +18,7 @@ lazyStaticRouter.use( | |||
18 | ) | 18 | ) |
19 | 19 | ||
20 | lazyStaticRouter.use( | 20 | lazyStaticRouter.use( |
21 | LAZY_STATIC_PATHS.PREVIEWS + ':uuid.jpg', | 21 | LAZY_STATIC_PATHS.PREVIEWS + ':filename', |
22 | asyncMiddleware(getPreview) | 22 | asyncMiddleware(getPreview) |
23 | ) | 23 | ) |
24 | 24 | ||
@@ -71,7 +71,7 @@ async function getAvatar (req: express.Request, res: express.Response) { | |||
71 | } | 71 | } |
72 | 72 | ||
73 | async function getPreview (req: express.Request, res: express.Response) { | 73 | async function getPreview (req: express.Request, res: express.Response) { |
74 | const result = await VideosPreviewCache.Instance.getFilePath(req.params.uuid) | 74 | const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename) |
75 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 75 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) |
76 | 76 | ||
77 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) | 77 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index a645c496b..2064857eb 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -1,5 +1,15 @@ | |||
1 | import * as cors from 'cors' | 1 | import * as cors from 'cors' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { join } from 'path' | ||
4 | import { getRegisteredPlugins, getRegisteredThemes } from '@server/controllers/api/config' | ||
5 | import { serveIndexHTML } from '@server/lib/client-html' | ||
6 | import { getTorrentFilePath, getVideoFilePath } from '@server/lib/video-paths' | ||
7 | import { MVideoFile, MVideoFullLight } from '@server/types/models' | ||
8 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
9 | import { VideoStreamingPlaylistType } from '@shared/models/videos/video-streaming-playlist.type' | ||
10 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' | ||
11 | import { root } from '../helpers/core-utils' | ||
12 | import { CONFIG, isEmailEnabled } from '../initializers/config' | ||
3 | import { | 13 | import { |
4 | CONSTRAINTS_FIELDS, | 14 | CONSTRAINTS_FIELDS, |
5 | DEFAULT_THEME_NAME, | 15 | DEFAULT_THEME_NAME, |
@@ -11,24 +21,13 @@ import { | |||
11 | STATIC_PATHS, | 21 | STATIC_PATHS, |
12 | WEBSERVER | 22 | WEBSERVER |
13 | } from '../initializers/constants' | 23 | } from '../initializers/constants' |
14 | import { cacheRoute } from '../middlewares/cache' | 24 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' |
25 | import { getEnabledResolutions } from '../lib/video-transcoding' | ||
15 | import { asyncMiddleware, videosDownloadValidator } from '../middlewares' | 26 | import { asyncMiddleware, videosDownloadValidator } from '../middlewares' |
16 | import { VideoModel } from '../models/video/video' | 27 | import { cacheRoute } from '../middlewares/cache' |
17 | import { UserModel } from '../models/account/user' | 28 | import { UserModel } from '../models/account/user' |
29 | import { VideoModel } from '../models/video/video' | ||
18 | import { VideoCommentModel } from '../models/video/video-comment' | 30 | import { VideoCommentModel } from '../models/video/video-comment' |
19 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' | ||
20 | import { join } from 'path' | ||
21 | import { root } from '../helpers/core-utils' | ||
22 | import { getEnabledResolutions } from '../lib/video-transcoding' | ||
23 | import { CONFIG, isEmailEnabled } from '../initializers/config' | ||
24 | import { getPreview, getVideoCaption } from './lazy-static' | ||
25 | import { VideoStreamingPlaylistType } from '@shared/models/videos/video-streaming-playlist.type' | ||
26 | import { MVideoFile, MVideoFullLight } from '@server/types/models' | ||
27 | import { getTorrentFilePath, getVideoFilePath } from '@server/lib/video-paths' | ||
28 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' | ||
29 | import { getRegisteredPlugins, getRegisteredThemes } from '@server/controllers/api/config' | ||
30 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
31 | import { serveIndexHTML } from '@server/lib/client-html' | ||
32 | 31 | ||
33 | const staticRouter = express.Router() | 32 | const staticRouter = express.Router() |
34 | 33 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 7beaca238..a9f7a8e58 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
24 | 24 | ||
25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
26 | 26 | ||
27 | const LAST_MIGRATION_VERSION = 570 | 27 | const LAST_MIGRATION_VERSION = 575 |
28 | 28 | ||
29 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
30 | 30 | ||
diff --git a/server/initializers/migrations/0575-duplicate-thumbnail.ts b/server/initializers/migrations/0575-duplicate-thumbnail.ts new file mode 100644 index 000000000..4dbbe71d4 --- /dev/null +++ b/server/initializers/migrations/0575-duplicate-thumbnail.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction | ||
5 | queryInterface: Sequelize.QueryInterface | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const query = 'DELETE FROM "thumbnail" s1 ' + | ||
11 | 'USING (SELECT MIN(id) as id, "filename", "type" FROM "thumbnail" GROUP BY "filename", "type" HAVING COUNT(*) > 1) s2 ' + | ||
12 | 'WHERE s1."filename" = s2."filename" AND s1."type" = s2."type" AND s1.id <> s2.id' | ||
13 | await utils.sequelize.query(query) | ||
14 | } | ||
15 | } | ||
16 | |||
17 | function down (options) { | ||
18 | throw new Error('Not implemented.') | ||
19 | } | ||
20 | |||
21 | export { | ||
22 | up, | ||
23 | down | ||
24 | } | ||
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 8545e5bad..b5a199e67 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -5,6 +5,7 @@ import { join } from 'path' | |||
5 | import * as request from 'request' | 5 | import * as request from 'request' |
6 | import * as sequelize from 'sequelize' | 6 | import * as sequelize from 'sequelize' |
7 | import { VideoLiveModel } from '@server/models/video/video-live' | 7 | import { VideoLiveModel } from '@server/models/video/video-live' |
8 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
8 | import { | 9 | import { |
9 | ActivityHashTagObject, | 10 | ActivityHashTagObject, |
10 | ActivityMagnetUrlObject, | 11 | ActivityMagnetUrlObject, |
@@ -15,7 +16,7 @@ import { | |||
15 | ActivityUrlObject, | 16 | ActivityUrlObject, |
16 | ActivityVideoUrlObject | 17 | ActivityVideoUrlObject |
17 | } from '../../../shared/index' | 18 | } from '../../../shared/index' |
18 | import { VideoObject } from '../../../shared/models/activitypub/objects' | 19 | import { ActivityIconObject, VideoObject } from '../../../shared/models/activitypub/objects' |
19 | import { VideoPrivacy } from '../../../shared/models/videos' | 20 | import { VideoPrivacy } from '../../../shared/models/videos' |
20 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
21 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 22 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
@@ -76,7 +77,6 @@ import { sendCreateVideo, sendUpdateVideo } from './send' | |||
76 | import { addVideoShares, shareVideoByServerAndChannel } from './share' | 77 | import { addVideoShares, shareVideoByServerAndChannel } from './share' |
77 | import { addVideoComments } from './video-comments' | 78 | import { addVideoComments } from './video-comments' |
78 | import { createRates } from './video-rates' | 79 | import { createRates } from './video-rates' |
79 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
80 | 80 | ||
81 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 81 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
82 | const video = videoArg as MVideoAP | 82 | const video = videoArg as MVideoAP |
@@ -360,7 +360,7 @@ async function updateVideoFromAP (options: { | |||
360 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) | 360 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) |
361 | 361 | ||
362 | if (videoUpdated.getPreview()) { | 362 | if (videoUpdated.getPreview()) { |
363 | const previewUrl = videoUpdated.getPreview().getFileUrl(videoUpdated) | 363 | const previewUrl = getPreviewUrl(getPreviewFromIcons(videoObject), video) |
364 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 364 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
365 | await videoUpdated.addAndSaveThumbnail(previewModel, t) | 365 | await videoUpdated.addAndSaveThumbnail(previewModel, t) |
366 | } | 366 | } |
@@ -597,9 +597,7 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi | |||
597 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 597 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
598 | 598 | ||
599 | const previewIcon = getPreviewFromIcons(videoObject) | 599 | const previewIcon = getPreviewFromIcons(videoObject) |
600 | const previewUrl = previewIcon | 600 | const previewUrl = getPreviewUrl(previewIcon, videoCreated) |
601 | ? previewIcon.url | ||
602 | : buildRemoteVideoBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | ||
603 | const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 601 | const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
604 | 602 | ||
605 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) | 603 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) |
@@ -822,7 +820,11 @@ function getThumbnailFromIcons (videoObject: VideoObject) { | |||
822 | function getPreviewFromIcons (videoObject: VideoObject) { | 820 | function getPreviewFromIcons (videoObject: VideoObject) { |
823 | const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth) | 821 | const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth) |
824 | 822 | ||
825 | // FIXME: don't put a fallback here for compatibility with PeerTube <2.2 | ||
826 | |||
827 | return maxBy(validIcons, 'width') | 823 | return maxBy(validIcons, 'width') |
828 | } | 824 | } |
825 | |||
826 | function getPreviewUrl (previewIcon: ActivityIconObject, video: MVideoAccountLight) { | ||
827 | return previewIcon | ||
828 | ? previewIcon.url | ||
829 | : buildRemoteVideoBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | ||
830 | } | ||
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index d0d4fc5b5..51146d718 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts | |||
@@ -3,6 +3,9 @@ import { FILES_CACHE } from '../../initializers/constants' | |||
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
5 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | 5 | import { doRequestAndSaveToFile } from '@server/helpers/requests' |
6 | import { ThumbnailModel } from '@server/models/video/thumbnail' | ||
7 | import { ThumbnailType } from '@shared/models' | ||
8 | import { logger } from '@server/helpers/logger' | ||
6 | 9 | ||
7 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | 10 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { |
8 | 11 | ||
@@ -16,13 +19,13 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
16 | return this.instance || (this.instance = new this()) | 19 | return this.instance || (this.instance = new this()) |
17 | } | 20 | } |
18 | 21 | ||
19 | async getFilePathImpl (videoUUID: string) { | 22 | async getFilePathImpl (filename: string) { |
20 | const video = await VideoModel.loadByUUID(videoUUID) | 23 | const thumbnail = await ThumbnailModel.loadWithVideoByName(filename, ThumbnailType.PREVIEW) |
21 | if (!video) return undefined | 24 | if (!thumbnail) return undefined |
22 | 25 | ||
23 | if (video.isOwned()) return { isOwned: true, path: video.getPreview().getPath() } | 26 | if (thumbnail.Video.isOwned()) return { isOwned: true, path: thumbnail.getPath() } |
24 | 27 | ||
25 | return this.loadRemoteFile(videoUUID) | 28 | return this.loadRemoteFile(thumbnail.Video.uuid) |
26 | } | 29 | } |
27 | 30 | ||
28 | protected async loadRemoteFile (key: string) { | 31 | protected async loadRemoteFile (key: string) { |
@@ -37,6 +40,8 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
37 | const remoteUrl = preview.getFileUrl(video) | 40 | const remoteUrl = preview.getFileUrl(video) |
38 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) | 41 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) |
39 | 42 | ||
43 | logger.debug('Fetched remote preview %s to %s.', remoteUrl, destPath) | ||
44 | |||
40 | return { isOwned: false, path: destPath } | 45 | return { isOwned: false, path: destPath } |
41 | } | 46 | } |
42 | } | 47 | } |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index dc86423f8..740b83acb 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -27,18 +27,28 @@ function createPlaylistMiniatureFromExisting ( | |||
27 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) | 27 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) |
28 | } | 28 | } |
29 | 29 | ||
30 | function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) { | 30 | function createPlaylistMiniatureFromUrl (downloadUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) { |
31 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | 31 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) |
32 | const type = ThumbnailType.MINIATURE | 32 | const type = ThumbnailType.MINIATURE |
33 | 33 | ||
34 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) | 34 | // Only save the file URL if it is a remote playlist |
35 | const fileUrl = playlist.isOwned() | ||
36 | ? null | ||
37 | : downloadUrl | ||
38 | |||
39 | const thumbnailCreator = () => downloadImage(downloadUrl, basePath, filename, { width, height }) | ||
35 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | 40 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
36 | } | 41 | } |
37 | 42 | ||
38 | function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { | 43 | function createVideoMiniatureFromUrl (downloadUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { |
39 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 44 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
40 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) | ||
41 | 45 | ||
46 | // Only save the file URL if it is a remote video | ||
47 | const fileUrl = video.isOwned() | ||
48 | ? null | ||
49 | : downloadUrl | ||
50 | |||
51 | const thumbnailCreator = () => downloadImage(downloadUrl, basePath, filename, { width, height }) | ||
42 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | 52 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
43 | } | 53 | } |
44 | 54 | ||
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index 6878a3155..3cad6c668 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { remove } from 'fs-extra' | ||
1 | import { join } from 'path' | 2 | import { join } from 'path' |
2 | import { | 3 | import { |
3 | AfterDestroy, | 4 | AfterDestroy, |
@@ -12,15 +13,14 @@ import { | |||
12 | Table, | 13 | Table, |
13 | UpdatedAt | 14 | UpdatedAt |
14 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
15 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, STATIC_PATHS, WEBSERVER } from '../../initializers/constants' | 16 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' |
17 | import { MThumbnailVideo, MVideoAccountLight } from '@server/types/models' | ||
18 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
16 | import { logger } from '../../helpers/logger' | 19 | import { logger } from '../../helpers/logger' |
17 | import { remove } from 'fs-extra' | ||
18 | import { CONFIG } from '../../initializers/config' | 20 | import { CONFIG } from '../../initializers/config' |
21 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, STATIC_PATHS, WEBSERVER } from '../../initializers/constants' | ||
19 | import { VideoModel } from './video' | 22 | import { VideoModel } from './video' |
20 | import { VideoPlaylistModel } from './video-playlist' | 23 | import { VideoPlaylistModel } from './video-playlist' |
21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
22 | import { MVideoAccountLight } from '@server/types/models' | ||
23 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | ||
24 | 24 | ||
25 | @Table({ | 25 | @Table({ |
26 | tableName: 'thumbnail', | 26 | tableName: 'thumbnail', |
@@ -31,6 +31,10 @@ import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | |||
31 | { | 31 | { |
32 | fields: [ 'videoPlaylistId' ], | 32 | fields: [ 'videoPlaylistId' ], |
33 | unique: true | 33 | unique: true |
34 | }, | ||
35 | { | ||
36 | fields: [ 'filename', 'type' ], | ||
37 | unique: true | ||
34 | } | 38 | } |
35 | ] | 39 | ] |
36 | }) | 40 | }) |
@@ -114,20 +118,23 @@ export class ThumbnailModel extends Model { | |||
114 | .catch(err => logger.error('Cannot remove thumbnail file %s.', instance.filename, err)) | 118 | .catch(err => logger.error('Cannot remove thumbnail file %s.', instance.filename, err)) |
115 | } | 119 | } |
116 | 120 | ||
117 | static loadByName (filename: string) { | 121 | static loadWithVideoByName (filename: string, thumbnailType: ThumbnailType): Promise<MThumbnailVideo> { |
118 | const query = { | 122 | const query = { |
119 | where: { | 123 | where: { |
120 | filename | 124 | filename, |
121 | } | 125 | type: thumbnailType |
126 | }, | ||
127 | include: [ | ||
128 | { | ||
129 | model: VideoModel.unscoped(), | ||
130 | required: true | ||
131 | } | ||
132 | ] | ||
122 | } | 133 | } |
123 | 134 | ||
124 | return ThumbnailModel.findOne(query) | 135 | return ThumbnailModel.findOne(query) |
125 | } | 136 | } |
126 | 137 | ||
127 | static generateDefaultPreviewName (videoUUID: string) { | ||
128 | return videoUUID + '.jpg' | ||
129 | } | ||
130 | |||
131 | getFileUrl (video: MVideoAccountLight) { | 138 | getFileUrl (video: MVideoAccountLight) { |
132 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename | 139 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename |
133 | 140 | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 14e80a3ba..3321deed3 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -130,6 +130,7 @@ import { VideoShareModel } from './video-share' | |||
130 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 130 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
131 | import { VideoTagModel } from './video-tag' | 131 | import { VideoTagModel } from './video-tag' |
132 | import { VideoViewModel } from './video-view' | 132 | import { VideoViewModel } from './video-view' |
133 | import { v4 as uuidv4 } from 'uuid' | ||
133 | 134 | ||
134 | export enum ScopeNames { | 135 | export enum ScopeNames { |
135 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 136 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
@@ -1827,7 +1828,7 @@ export class VideoModel extends Model { | |||
1827 | } | 1828 | } |
1828 | 1829 | ||
1829 | generateThumbnailName () { | 1830 | generateThumbnailName () { |
1830 | return this.uuid + '.jpg' | 1831 | return uuidv4() + '.jpg' |
1831 | } | 1832 | } |
1832 | 1833 | ||
1833 | getMiniature () { | 1834 | getMiniature () { |
@@ -1837,7 +1838,7 @@ export class VideoModel extends Model { | |||
1837 | } | 1838 | } |
1838 | 1839 | ||
1839 | generatePreviewName () { | 1840 | generatePreviewName () { |
1840 | return this.uuid + '.jpg' | 1841 | return uuidv4() + '.jpg' |
1841 | } | 1842 | } |
1842 | 1843 | ||
1843 | hasPreview () { | 1844 | hasPreview () { |
diff --git a/server/types/models/video/thumbnail.ts b/server/types/models/video/thumbnail.ts index c03ba55ac..81a29e062 100644 --- a/server/types/models/video/thumbnail.ts +++ b/server/types/models/video/thumbnail.ts | |||
@@ -1,3 +1,15 @@ | |||
1 | import { PickWith } from '@shared/core-utils' | ||
1 | import { ThumbnailModel } from '../../../models/video/thumbnail' | 2 | import { ThumbnailModel } from '../../../models/video/thumbnail' |
3 | import { MVideo } from './video' | ||
4 | |||
5 | type Use<K extends keyof ThumbnailModel, M> = PickWith<ThumbnailModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
2 | 8 | ||
3 | export type MThumbnail = Omit<ThumbnailModel, 'Video' | 'VideoPlaylist'> | 9 | export type MThumbnail = Omit<ThumbnailModel, 'Video' | 'VideoPlaylist'> |
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MThumbnailVideo = | ||
14 | MThumbnail & | ||
15 | Use<'Video', MVideo> | ||