From 6302d599cdf98b5a5363a2a1dcdc266447950191 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 15 Feb 2021 14:08:16 +0100 Subject: [PATCH] Generate a name for caption files --- server/controllers/api/videos/captions.ts | 28 ++++----- server/controllers/api/videos/import.ts | 32 +++++----- server/controllers/lazy-static.ts | 7 +-- server/helpers/captions-utils.ts | 12 ++-- server/helpers/middlewares/video-captions.ts | 1 - server/initializers/constants.ts | 2 +- .../migrations/0580-caption-filename.ts | 48 +++++++++++++++ server/lib/activitypub/playlist.ts | 2 - server/lib/activitypub/videos.ts | 21 ++++++- .../lib/files-cache/videos-caption-cache.ts | 30 ++++------ .../lib/files-cache/videos-preview-cache.ts | 1 + server/lib/thumbnail.ts | 3 + server/models/video/video-caption.ts | 58 +++++++++++++------ server/models/video/video-playlist.ts | 2 +- server/tests/api/server/services.ts | 27 ++++----- server/tests/api/videos/video-captions.ts | 12 ++-- server/types/models/video/video-caption.ts | 4 +- shared/extra-utils/server/servers.ts | 2 +- 18 files changed, 184 insertions(+), 108 deletions(-) create mode 100644 server/initializers/migrations/0580-caption-filename.ts diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index bf82e2c19..ad7423a31 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts @@ -1,17 +1,17 @@ import * as express from 'express' -import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' -import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' +import { MVideoCaption } from '@server/types/models' +import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' +import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' import { createReqFiles } from '../../../helpers/express-utils' -import { MIMETYPES } from '../../../initializers/constants' -import { getFormattedObjects } from '../../../helpers/utils' -import { VideoCaptionModel } from '../../../models/video/video-caption' import { logger } from '../../../helpers/logger' -import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' -import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' +import { getFormattedObjects } from '../../../helpers/utils' import { CONFIG } from '../../../initializers/config' +import { MIMETYPES } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' -import { MVideoCaptionVideo } from '@server/types/models' -import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' +import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' +import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' +import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' +import { VideoCaptionModel } from '../../../models/video/video-caption' const reqVideoCaptionAdd = createReqFiles( [ 'captionfile' ], @@ -57,17 +57,19 @@ async function addVideoCaption (req: express.Request, res: express.Response) { const videoCaptionPhysicalFile = req.files['captionfile'][0] const video = res.locals.videoAll + const captionLanguage = req.params.captionLanguage + const videoCaption = new VideoCaptionModel({ videoId: video.id, - language: req.params.captionLanguage - }) as MVideoCaptionVideo - videoCaption.Video = video + filename: VideoCaptionModel.generateCaptionName(captionLanguage), + language: captionLanguage + }) as MVideoCaption // Move physical file await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) await sequelizeTypescript.transaction(async t => { - await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, null, t) + await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t) // Update video update await federateVideoIfNeeded(video, false, t) diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 01f41e7bc..c689cb6f9 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts @@ -9,9 +9,9 @@ import { MThumbnail, MUser, MVideoAccountDefault, - MVideoCaptionVideo, + MVideoCaption, MVideoTag, - MVideoThumbnailAccountDefault, + MVideoThumbnail, MVideoWithBlacklistLight } from '@server/types/models' import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import' @@ -154,20 +154,16 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) - let thumbnailModel: MThumbnail - // Process video thumbnail from request.files - thumbnailModel = await processThumbnail(req, video) + let thumbnailModel = await processThumbnail(req, video) // Process video thumbnail from url if processing from request.files failed if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) { thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video) } - let previewModel: MThumbnail - // Process video preview from request.files - previewModel = await processPreview(req, video) + let previewModel = await processPreview(req, video) // Process video preview from url if processing from request.files failed if (!previewModel && youtubeDLInfo.thumbnailUrl) { @@ -199,15 +195,15 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) for (const subtitle of subtitles) { const videoCaption = new VideoCaptionModel({ videoId: video.id, - language: subtitle.language - }) as MVideoCaptionVideo - videoCaption.Video = video + language: subtitle.language, + filename: VideoCaptionModel.generateCaptionName(subtitle.language) + }) as MVideoCaption // Move physical file await moveAndProcessCaptionFile(subtitle, videoCaption) await sequelizeTypescript.transaction(async t => { - await VideoCaptionModel.insertOrReplaceLanguage(video.id, subtitle.language, null, t) + await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t) }) } } catch (err) { @@ -227,7 +223,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) return res.json(videoImport.toFormattedJSON()).end() } -function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo) { +function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo): MVideoThumbnail { const videoData = { name: body.name || importData.name || 'Unknown name', remote: false, @@ -252,7 +248,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You return video } -async function processThumbnail (req: express.Request, video: VideoModel) { +async function processThumbnail (req: express.Request, video: MVideoThumbnail) { const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined if (thumbnailField) { const thumbnailPhysicalFile = thumbnailField[0] @@ -268,7 +264,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) { return undefined } -async function processPreview (req: express.Request, video: VideoModel) { +async function processPreview (req: express.Request, video: MVideoThumbnail): Promise { const previewField = req.files ? req.files['previewfile'] : undefined if (previewField) { const previewPhysicalFile = previewField[0] @@ -284,7 +280,7 @@ async function processPreview (req: express.Request, video: VideoModel) { return undefined } -async function processThumbnailFromUrl (url: string, video: VideoModel) { +async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) { try { return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE) } catch (err) { @@ -293,7 +289,7 @@ async function processThumbnailFromUrl (url: string, video: VideoModel) { } } -async function processPreviewFromUrl (url: string, video: VideoModel) { +async function processPreviewFromUrl (url: string, video: MVideoThumbnail) { try { return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW) } catch (err) { @@ -303,7 +299,7 @@ async function processPreviewFromUrl (url: string, video: VideoModel) { } function insertIntoDB (parameters: { - video: MVideoThumbnailAccountDefault + video: MVideoThumbnail thumbnailModel: MThumbnail previewModel: MThumbnail videoChannel: MChannelAccountDefault diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts index 847d24fd4..656dea223 100644 --- a/server/controllers/lazy-static.ts +++ b/server/controllers/lazy-static.ts @@ -23,7 +23,7 @@ lazyStaticRouter.use( ) lazyStaticRouter.use( - LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':videoId-:captionLanguage([a-z]+).vtt', + LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':filename', asyncMiddleware(getVideoCaption) ) @@ -78,10 +78,7 @@ async function getPreview (req: express.Request, res: express.Response) { } async function getVideoCaption (req: express.Request, res: express.Response) { - const result = await VideosCaptionCache.Instance.getFilePath({ - videoId: req.params.videoId, - language: req.params.captionLanguage - }) + const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename) if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 7cbfb3561..401f2fb7b 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts @@ -1,12 +1,12 @@ +import { createReadStream, createWriteStream, move, remove } from 'fs-extra' import { join } from 'path' -import { CONFIG } from '../initializers/config' import * as srt2vtt from 'srt-to-vtt' -import { createReadStream, createWriteStream, move, remove } from 'fs-extra' -import { MVideoCaptionFormattable } from '@server/types/models' +import { MVideoCaption } from '@server/types/models' +import { CONFIG } from '../initializers/config' -async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) { +async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaption) { const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR - const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) + const destination = join(videoCaptionsDir, videoCaption.filename) // Convert this srt file to vtt if (physicalFile.path.endsWith('.srt')) { @@ -17,7 +17,7 @@ async function moveAndProcessCaptionFile (physicalFile: { filename: string, path } // This is important in case if there is another attempt in the retry process - physicalFile.filename = videoCaption.getCaptionName() + physicalFile.filename = videoCaption.filename physicalFile.path = destination } diff --git a/server/helpers/middlewares/video-captions.ts b/server/helpers/middlewares/video-captions.ts index 10267eda1..226d3c5f8 100644 --- a/server/helpers/middlewares/video-captions.ts +++ b/server/helpers/middlewares/video-captions.ts @@ -9,7 +9,6 @@ async function doesVideoCaptionExist (video: MVideoId, language: string, res: Re if (!videoCaption) { res.status(HttpStatusCode.NOT_FOUND_404) .json({ error: 'Video caption not found' }) - .end() return false } diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index a9f7a8e58..be5db8fe8 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 575 +const LAST_MIGRATION_VERSION = 580 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0580-caption-filename.ts b/server/initializers/migrations/0580-caption-filename.ts new file mode 100644 index 000000000..5281fd0c0 --- /dev/null +++ b/server/initializers/migrations/0580-caption-filename.ts @@ -0,0 +1,48 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction + queryInterface: Sequelize.QueryInterface + sequelize: Sequelize.Sequelize + db: any +}): Promise { + { + const data = { + type: Sequelize.STRING, + allowNull: true, + defaultValue: null + } + + await utils.queryInterface.addColumn('videoCaption', 'filename', data) + } + + { + const query = `UPDATE "videoCaption" SET "filename" = s.uuid || '-' || s.language || '.vtt' ` + + `FROM (` + + ` SELECT "videoCaption"."id", video.uuid, "videoCaption".language ` + + ` FROM "videoCaption" INNER JOIN video ON video.id = "videoCaption"."videoId"` + + `) AS s ` + + `WHERE "videoCaption".id = s.id` + + await utils.sequelize.query(query) + } + + { + const data = { + type: Sequelize.STRING, + allowNull: false, + defaultValue: null + } + + await utils.queryInterface.changeColumn('videoCaption', 'filename', data) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts index 8b54a001a..53298e968 100644 --- a/server/lib/activitypub/playlist.ts +++ b/server/lib/activitypub/playlist.ts @@ -99,8 +99,6 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc return Promise.resolve() }) - logger.info('toto', { playlist, id: playlist.id }) - const refreshedPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(playlist.id, null) if (playlistObject.icon) { diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index b5a199e67..201ef0302 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -56,6 +56,7 @@ import { MVideoAccountLightBlacklistAllFiles, MVideoAP, MVideoAPWithoutCaption, + MVideoCaption, MVideoFile, MVideoFullLight, MVideoId, @@ -90,7 +91,7 @@ async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVid // Fetch more attributes that we will need to serialize in AP object if (isArray(video.VideoCaptions) === false) { video.VideoCaptions = await video.$get('VideoCaptions', { - attributes: [ 'language' ], + attributes: [ 'filename', 'language' ], transaction }) } @@ -423,7 +424,14 @@ async function updateVideoFromAP (options: { await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { - return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, c.url, t) + const caption = new VideoCaptionModel({ + videoId: videoUpdated.id, + filename: VideoCaptionModel.generateCaptionName(c.identifier), + language: c.identifier, + fileUrl: c.url + }) as MVideoCaption + + return VideoCaptionModel.insertOrReplaceLanguage(caption, t) }) await Promise.all(videoCaptionsPromises) } @@ -629,7 +637,14 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi // Process captions const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { - return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, c.url, t) + const caption = new VideoCaptionModel({ + videoId: videoCreated.id, + filename: VideoCaptionModel.generateCaptionName(c.identifier), + language: c.identifier, + fileUrl: c.url + }) as MVideoCaption + + return VideoCaptionModel.insertOrReplaceLanguage(caption, t) }) await Promise.all(videoCaptionsPromises) diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts index 26ab3bd0d..ee0447010 100644 --- a/server/lib/files-cache/videos-caption-cache.ts +++ b/server/lib/files-cache/videos-caption-cache.ts @@ -1,17 +1,13 @@ import { join } from 'path' +import { doRequestAndSaveToFile } from '@server/helpers/requests' +import { CONFIG } from '../../initializers/config' import { FILES_CACHE } from '../../initializers/constants' import { VideoModel } from '../../models/video/video' import { VideoCaptionModel } from '../../models/video/video-caption' import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' -import { CONFIG } from '../../initializers/config' -import { logger } from '../../helpers/logger' -import { doRequestAndSaveToFile } from '@server/helpers/requests' -type GetPathParam = { videoId: string, language: string } +class VideosCaptionCache extends AbstractVideoStaticFileCache { -class VideosCaptionCache extends AbstractVideoStaticFileCache { - - private static readonly KEY_DELIMITER = '%' private static instance: VideosCaptionCache private constructor () { @@ -22,32 +18,28 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache { return this.instance || (this.instance = new this()) } - async getFilePathImpl (params: GetPathParam) { - const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) + async getFilePathImpl (filename: string) { + const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(filename) if (!videoCaption) return undefined - if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) } + if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.filename) } - const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language - return this.loadRemoteFile(key) + return this.loadRemoteFile(filename) } + // Key is the caption filename protected async loadRemoteFile (key: string) { - logger.debug('Loading remote caption file %s.', key) - - const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) - - const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) + const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(key) if (!videoCaption) return undefined if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.') // Used to fetch the path - const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoCaption.videoId) if (!video) return undefined const remoteUrl = videoCaption.getFileUrl(video) - const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) + const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.filename) await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index 51146d718..47488da74 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts @@ -28,6 +28,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache { return this.loadRemoteFile(thumbnail.Video.uuid) } + // Key is the video UUID protected async loadRemoteFile (key: string) { const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(key) if (!video) return undefined diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index 740b83acb..33aa7159c 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts @@ -166,6 +166,9 @@ async function createThumbnailFromFunction (parameters: { }) { const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters + // Remove old file + if (existingThumbnail) await existingThumbnail.removeThumbnail() + const thumbnail = existingThumbnail || new ThumbnailModel() thumbnail.filename = filename diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index e8e883dd0..a1553ea15 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts @@ -16,7 +16,7 @@ import { UpdatedAt } from 'sequelize-typescript' import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' -import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' +import { MVideoAccountLight, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' import { logger } from '../../helpers/logger' @@ -24,6 +24,7 @@ import { CONFIG } from '../../initializers/config' import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' import { VideoModel } from './video' +import { v4 as uuidv4 } from 'uuid' export enum ScopeNames { WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' @@ -44,6 +45,10 @@ export enum ScopeNames { @Table({ tableName: 'videoCaption', indexes: [ + { + fields: [ 'filename' ], + unique: true + }, { fields: [ 'videoId' ] }, @@ -65,6 +70,10 @@ export class VideoCaptionModel extends Model { @Column language: string + @AllowNull(false) + @Column + filename: string + @AllowNull(true) @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max)) fileUrl: string @@ -88,12 +97,12 @@ export class VideoCaptionModel extends Model { } if (instance.isOwned()) { - logger.info('Removing captions %s of video %s.', instance.Video.uuid, instance.language) + logger.info('Removing caption %s.', instance.filename) try { await instance.removeCaptionFile() } catch (err) { - logger.error('Cannot remove caption file of video %s.', instance.Video.uuid) + logger.error('Cannot remove caption file %s.', instance.filename) } } @@ -119,15 +128,28 @@ export class VideoCaptionModel extends Model { return VideoCaptionModel.findOne(query) } - static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) { - const values = { - videoId, - language, - fileUrl + static loadWithVideoByFilename (filename: string): Promise { + const query = { + where: { + filename + }, + include: [ + { + model: VideoModel.unscoped(), + attributes: [ 'id', 'remote', 'uuid' ] + } + ] } - return VideoCaptionModel.upsert(values, { transaction, returning: true }) - .then(([ caption ]) => caption) + return VideoCaptionModel.findOne(query) + } + + static async insertOrReplaceLanguage (caption: MVideoCaption, transaction: Transaction) { + const existing = await VideoCaptionModel.loadByVideoIdAndLanguage(caption.videoId, caption.language) + // Delete existing file + if (existing) await existing.destroy({ transaction }) + + return caption.save({ transaction }) } static listVideoCaptions (videoId: number): Promise { @@ -156,6 +178,10 @@ export class VideoCaptionModel extends Model { return VideoCaptionModel.destroy(query) } + static generateCaptionName (language: string) { + return `${uuidv4()}-${language}.vtt` + } + isOwned () { return this.Video.remote === false } @@ -170,16 +196,12 @@ export class VideoCaptionModel extends Model { } } - getCaptionStaticPath (this: MVideoCaptionFormattable) { - return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) - } - - getCaptionName (this: MVideoCaptionFormattable) { - return `${this.Video.uuid}-${this.language}.vtt` + getCaptionStaticPath (this: MVideoCaption) { + return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.filename) } - removeCaptionFile (this: MVideoCaptionFormattable) { - return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) + removeCaptionFile (this: MVideoCaption) { + return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.filename) } getFileUrl (video: MVideoAccountLight) { diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index 93ecf8cea..9e6ff1f81 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts @@ -471,7 +471,7 @@ export class VideoPlaylistModel extends Model { generateThumbnailName () { const extension = '.jpg' - return 'playlist-' + this.uuid + extension + return 'playlist-' + uuidv4() + extension } getThumbnailUrl () { diff --git a/server/tests/api/server/services.ts b/server/tests/api/server/services.ts index 680e7a817..df910c111 100644 --- a/server/tests/api/server/services.ts +++ b/server/tests/api/server/services.ts @@ -2,24 +2,25 @@ import 'mocha' import * as chai from 'chai' +import { Video, VideoPlaylistPrivacy } from '@shared/models' import { + addVideoInPlaylist, + createVideoPlaylist, getOEmbed, getVideosList, ServerInfo, setAccessTokensToServers, setDefaultVideoChannel, - uploadVideo, - createVideoPlaylist, - addVideoInPlaylist + uploadVideo } from '../../../../shared/extra-utils' import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers' -import { VideoPlaylistPrivacy } from '@shared/models' const expect = chai.expect describe('Test services', function () { let server: ServerInfo = null let playlistUUID: string + let video: Video before(async function () { this.timeout(30000) @@ -36,7 +37,7 @@ describe('Test services', function () { await uploadVideo(server.url, server.accessToken, videoAttributes) const res = await getVideosList(server.url) - server.video = res.body.data[0] + video = res.body.data[0] } { @@ -57,23 +58,23 @@ describe('Test services', function () { token: server.accessToken, playlistId: res.body.videoPlaylist.id, elementAttrs: { - videoId: server.video.id + videoId: video.id } }) } }) it('Should have a valid oEmbed video response', async function () { - const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid + const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid const res = await getOEmbed(server.url, oembedUrl) const expectedHtml = '' - const expectedThumbnailUrl = 'http://localhost:' + server.port + '/lazy-static/previews/' + server.video.uuid + '.jpg' + const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath expect(res.body.html).to.equal(expectedHtml) - expect(res.body.title).to.equal(server.video.name) + expect(res.body.title).to.equal(video.name) expect(res.body.author_name).to.equal(server.videoChannel.displayName) expect(res.body.width).to.equal(560) expect(res.body.height).to.equal(315) @@ -101,18 +102,18 @@ describe('Test services', function () { }) it('Should have a valid oEmbed response with small max height query', async function () { - const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid + const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid const format = 'json' const maxHeight = 50 const maxWidth = 50 const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) const expectedHtml = '' expect(res.body.html).to.equal(expectedHtml) - expect(res.body.title).to.equal(server.video.name) + expect(res.body.title).to.equal(video.name) expect(res.body.author_name).to.equal(server.videoChannel.displayName) expect(res.body.height).to.equal(50) expect(res.body.width).to.equal(50) diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts index 5b36dc021..14ecedfa6 100644 --- a/server/tests/api/videos/video-captions.ts +++ b/server/tests/api/videos/video-captions.ts @@ -24,6 +24,8 @@ import { VideoCaption } from '../../../../shared/models/videos/caption/video-cap const expect = chai.expect describe('Test video captions', function () { + const uuidRegex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' + let servers: ServerInfo[] let videoUUID: string @@ -83,13 +85,13 @@ describe('Test video captions', function () { const caption1: VideoCaption = res.body.data[0] expect(caption1.language.id).to.equal('ar') expect(caption1.language.label).to.equal('Arabic') - expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt') + expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 1.') const caption2: VideoCaption = res.body.data[1] expect(caption2.language.id).to.equal('zh') expect(caption2.language.label).to.equal('Chinese') - expect(caption2.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-zh.vtt') + expect(caption2.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$')) await testCaptionFile(server.url, caption2.captionPath, 'Subtitle good 2.') } }) @@ -117,7 +119,7 @@ describe('Test video captions', function () { const caption1: VideoCaption = res.body.data[0] expect(caption1.language.id).to.equal('ar') expect(caption1.language.label).to.equal('Arabic') - expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt') + expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 2.') } }) @@ -148,7 +150,7 @@ describe('Test video captions', function () { const caption1: VideoCaption = res.body.data[0] expect(caption1.language.id).to.equal('ar') expect(caption1.language.label).to.equal('Arabic') - expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt') + expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) const expected = 'WEBVTT FILE\r\n' + '\r\n' + @@ -185,7 +187,7 @@ describe('Test video captions', function () { expect(caption.language.id).to.equal('zh') expect(caption.language.label).to.equal('Chinese') - expect(caption.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-zh.vtt') + expect(caption.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$')) await testCaptionFile(server.url, caption.captionPath, 'Subtitle good 2.') } }) diff --git a/server/types/models/video/video-caption.ts b/server/types/models/video/video-caption.ts index ab80ff830..1f761a866 100644 --- a/server/types/models/video/video-caption.ts +++ b/server/types/models/video/video-caption.ts @@ -1,5 +1,5 @@ +import { PickWith } from '@shared/core-utils' import { VideoCaptionModel } from '../../../models/video/video-caption' -import { FunctionProperties, PickWith } from '@shared/core-utils' import { MVideo, MVideoUUID } from './video' type Use = PickWith @@ -22,6 +22,6 @@ export type MVideoCaptionVideo = // Format for API or AP object export type MVideoCaptionFormattable = - FunctionProperties & + MVideoCaption & Pick & Use<'Video', MVideoUUID> diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts index 08d05ef36..424639f87 100644 --- a/shared/extra-utils/server/servers.ts +++ b/shared/extra-utils/server/servers.ts @@ -5,7 +5,7 @@ import { ChildProcess, exec, fork } from 'child_process' import { copy, ensureDir, pathExists, readdir, readFile, remove } from 'fs-extra' import { join } from 'path' import { randomInt } from '../../core-utils/miscs/miscs' -import { VideoChannel } from '../../models/videos' +import { Video, VideoChannel } from '../../models/videos' import { buildServerDirectory, getFileSize, isGithubCI, root, wait } from '../miscs/miscs' import { makeGetRequest } from '../requests/requests' -- 2.41.0