diff options
author | Chocobozzz <me@florianbigard.com> | 2021-02-15 14:08:16 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2021-02-16 10:36:44 +0100 |
commit | 6302d599cdf98b5a5363a2a1dcdc266447950191 (patch) | |
tree | b7dc6dc0f08f0fb8a20720242c9c0a71afeeaa3f /server | |
parent | a8b1b40485145ac1eae513a661d7dd6e0986ce96 (diff) | |
download | PeerTube-6302d599cdf98b5a5363a2a1dcdc266447950191.tar.gz PeerTube-6302d599cdf98b5a5363a2a1dcdc266447950191.tar.zst PeerTube-6302d599cdf98b5a5363a2a1dcdc266447950191.zip |
Generate a name for caption files
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/videos/captions.ts | 28 | ||||
-rw-r--r-- | server/controllers/api/videos/import.ts | 32 | ||||
-rw-r--r-- | server/controllers/lazy-static.ts | 7 | ||||
-rw-r--r-- | server/helpers/captions-utils.ts | 12 | ||||
-rw-r--r-- | server/helpers/middlewares/video-captions.ts | 1 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/initializers/migrations/0580-caption-filename.ts | 48 | ||||
-rw-r--r-- | server/lib/activitypub/playlist.ts | 2 | ||||
-rw-r--r-- | server/lib/activitypub/videos.ts | 21 | ||||
-rw-r--r-- | server/lib/files-cache/videos-caption-cache.ts | 30 | ||||
-rw-r--r-- | server/lib/files-cache/videos-preview-cache.ts | 1 | ||||
-rw-r--r-- | server/lib/thumbnail.ts | 3 | ||||
-rw-r--r-- | server/models/video/video-caption.ts | 58 | ||||
-rw-r--r-- | server/models/video/video-playlist.ts | 2 | ||||
-rw-r--r-- | server/tests/api/server/services.ts | 27 | ||||
-rw-r--r-- | server/tests/api/videos/video-captions.ts | 12 | ||||
-rw-r--r-- | server/types/models/video/video-caption.ts | 4 |
17 files changed, 183 insertions, 107 deletions
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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' | 2 | import { MVideoCaption } from '@server/types/models' |
3 | import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' | 3 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
4 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | ||
4 | import { createReqFiles } from '../../../helpers/express-utils' | 5 | import { createReqFiles } from '../../../helpers/express-utils' |
5 | import { MIMETYPES } from '../../../initializers/constants' | ||
6 | import { getFormattedObjects } from '../../../helpers/utils' | ||
7 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
8 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
9 | import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' | 7 | import { getFormattedObjects } from '../../../helpers/utils' |
10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | ||
11 | import { CONFIG } from '../../../initializers/config' | 8 | import { CONFIG } from '../../../initializers/config' |
9 | import { MIMETYPES } from '../../../initializers/constants' | ||
12 | import { sequelizeTypescript } from '../../../initializers/database' | 10 | import { sequelizeTypescript } from '../../../initializers/database' |
13 | import { MVideoCaptionVideo } from '@server/types/models' | 11 | import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' |
14 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 12 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' |
13 | import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' | ||
14 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
15 | 15 | ||
16 | const reqVideoCaptionAdd = createReqFiles( | 16 | const reqVideoCaptionAdd = createReqFiles( |
17 | [ 'captionfile' ], | 17 | [ 'captionfile' ], |
@@ -57,17 +57,19 @@ async function addVideoCaption (req: express.Request, res: express.Response) { | |||
57 | const videoCaptionPhysicalFile = req.files['captionfile'][0] | 57 | const videoCaptionPhysicalFile = req.files['captionfile'][0] |
58 | const video = res.locals.videoAll | 58 | const video = res.locals.videoAll |
59 | 59 | ||
60 | const captionLanguage = req.params.captionLanguage | ||
61 | |||
60 | const videoCaption = new VideoCaptionModel({ | 62 | const videoCaption = new VideoCaptionModel({ |
61 | videoId: video.id, | 63 | videoId: video.id, |
62 | language: req.params.captionLanguage | 64 | filename: VideoCaptionModel.generateCaptionName(captionLanguage), |
63 | }) as MVideoCaptionVideo | 65 | language: captionLanguage |
64 | videoCaption.Video = video | 66 | }) as MVideoCaption |
65 | 67 | ||
66 | // Move physical file | 68 | // Move physical file |
67 | await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) | 69 | await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) |
68 | 70 | ||
69 | await sequelizeTypescript.transaction(async t => { | 71 | await sequelizeTypescript.transaction(async t => { |
70 | await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, null, t) | 72 | await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t) |
71 | 73 | ||
72 | // Update video update | 74 | // Update video update |
73 | await federateVideoIfNeeded(video, false, t) | 75 | 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 { | |||
9 | MThumbnail, | 9 | MThumbnail, |
10 | MUser, | 10 | MUser, |
11 | MVideoAccountDefault, | 11 | MVideoAccountDefault, |
12 | MVideoCaptionVideo, | 12 | MVideoCaption, |
13 | MVideoTag, | 13 | MVideoTag, |
14 | MVideoThumbnailAccountDefault, | 14 | MVideoThumbnail, |
15 | MVideoWithBlacklistLight | 15 | MVideoWithBlacklistLight |
16 | } from '@server/types/models' | 16 | } from '@server/types/models' |
17 | import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import' | 17 | import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import' |
@@ -154,20 +154,16 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
154 | 154 | ||
155 | const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) | 155 | const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) |
156 | 156 | ||
157 | let thumbnailModel: MThumbnail | ||
158 | |||
159 | // Process video thumbnail from request.files | 157 | // Process video thumbnail from request.files |
160 | thumbnailModel = await processThumbnail(req, video) | 158 | let thumbnailModel = await processThumbnail(req, video) |
161 | 159 | ||
162 | // Process video thumbnail from url if processing from request.files failed | 160 | // Process video thumbnail from url if processing from request.files failed |
163 | if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) { | 161 | if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) { |
164 | thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video) | 162 | thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video) |
165 | } | 163 | } |
166 | 164 | ||
167 | let previewModel: MThumbnail | ||
168 | |||
169 | // Process video preview from request.files | 165 | // Process video preview from request.files |
170 | previewModel = await processPreview(req, video) | 166 | let previewModel = await processPreview(req, video) |
171 | 167 | ||
172 | // Process video preview from url if processing from request.files failed | 168 | // Process video preview from url if processing from request.files failed |
173 | if (!previewModel && youtubeDLInfo.thumbnailUrl) { | 169 | if (!previewModel && youtubeDLInfo.thumbnailUrl) { |
@@ -199,15 +195,15 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
199 | for (const subtitle of subtitles) { | 195 | for (const subtitle of subtitles) { |
200 | const videoCaption = new VideoCaptionModel({ | 196 | const videoCaption = new VideoCaptionModel({ |
201 | videoId: video.id, | 197 | videoId: video.id, |
202 | language: subtitle.language | 198 | language: subtitle.language, |
203 | }) as MVideoCaptionVideo | 199 | filename: VideoCaptionModel.generateCaptionName(subtitle.language) |
204 | videoCaption.Video = video | 200 | }) as MVideoCaption |
205 | 201 | ||
206 | // Move physical file | 202 | // Move physical file |
207 | await moveAndProcessCaptionFile(subtitle, videoCaption) | 203 | await moveAndProcessCaptionFile(subtitle, videoCaption) |
208 | 204 | ||
209 | await sequelizeTypescript.transaction(async t => { | 205 | await sequelizeTypescript.transaction(async t => { |
210 | await VideoCaptionModel.insertOrReplaceLanguage(video.id, subtitle.language, null, t) | 206 | await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t) |
211 | }) | 207 | }) |
212 | } | 208 | } |
213 | } catch (err) { | 209 | } catch (err) { |
@@ -227,7 +223,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
227 | return res.json(videoImport.toFormattedJSON()).end() | 223 | return res.json(videoImport.toFormattedJSON()).end() |
228 | } | 224 | } |
229 | 225 | ||
230 | function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo) { | 226 | function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo): MVideoThumbnail { |
231 | const videoData = { | 227 | const videoData = { |
232 | name: body.name || importData.name || 'Unknown name', | 228 | name: body.name || importData.name || 'Unknown name', |
233 | remote: false, | 229 | remote: false, |
@@ -252,7 +248,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You | |||
252 | return video | 248 | return video |
253 | } | 249 | } |
254 | 250 | ||
255 | async function processThumbnail (req: express.Request, video: VideoModel) { | 251 | async function processThumbnail (req: express.Request, video: MVideoThumbnail) { |
256 | const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined | 252 | const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined |
257 | if (thumbnailField) { | 253 | if (thumbnailField) { |
258 | const thumbnailPhysicalFile = thumbnailField[0] | 254 | const thumbnailPhysicalFile = thumbnailField[0] |
@@ -268,7 +264,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) { | |||
268 | return undefined | 264 | return undefined |
269 | } | 265 | } |
270 | 266 | ||
271 | async function processPreview (req: express.Request, video: VideoModel) { | 267 | async function processPreview (req: express.Request, video: MVideoThumbnail): Promise<MThumbnail> { |
272 | const previewField = req.files ? req.files['previewfile'] : undefined | 268 | const previewField = req.files ? req.files['previewfile'] : undefined |
273 | if (previewField) { | 269 | if (previewField) { |
274 | const previewPhysicalFile = previewField[0] | 270 | const previewPhysicalFile = previewField[0] |
@@ -284,7 +280,7 @@ async function processPreview (req: express.Request, video: VideoModel) { | |||
284 | return undefined | 280 | return undefined |
285 | } | 281 | } |
286 | 282 | ||
287 | async function processThumbnailFromUrl (url: string, video: VideoModel) { | 283 | async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) { |
288 | try { | 284 | try { |
289 | return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE) | 285 | return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE) |
290 | } catch (err) { | 286 | } catch (err) { |
@@ -293,7 +289,7 @@ async function processThumbnailFromUrl (url: string, video: VideoModel) { | |||
293 | } | 289 | } |
294 | } | 290 | } |
295 | 291 | ||
296 | async function processPreviewFromUrl (url: string, video: VideoModel) { | 292 | async function processPreviewFromUrl (url: string, video: MVideoThumbnail) { |
297 | try { | 293 | try { |
298 | return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW) | 294 | return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW) |
299 | } catch (err) { | 295 | } catch (err) { |
@@ -303,7 +299,7 @@ async function processPreviewFromUrl (url: string, video: VideoModel) { | |||
303 | } | 299 | } |
304 | 300 | ||
305 | function insertIntoDB (parameters: { | 301 | function insertIntoDB (parameters: { |
306 | video: MVideoThumbnailAccountDefault | 302 | video: MVideoThumbnail |
307 | thumbnailModel: MThumbnail | 303 | thumbnailModel: MThumbnail |
308 | previewModel: MThumbnail | 304 | previewModel: MThumbnail |
309 | videoChannel: MChannelAccountDefault | 305 | 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( | |||
23 | ) | 23 | ) |
24 | 24 | ||
25 | lazyStaticRouter.use( | 25 | lazyStaticRouter.use( |
26 | LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':videoId-:captionLanguage([a-z]+).vtt', | 26 | LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':filename', |
27 | asyncMiddleware(getVideoCaption) | 27 | asyncMiddleware(getVideoCaption) |
28 | ) | 28 | ) |
29 | 29 | ||
@@ -78,10 +78,7 @@ async function getPreview (req: express.Request, res: express.Response) { | |||
78 | } | 78 | } |
79 | 79 | ||
80 | async function getVideoCaption (req: express.Request, res: express.Response) { | 80 | async function getVideoCaption (req: express.Request, res: express.Response) { |
81 | const result = await VideosCaptionCache.Instance.getFilePath({ | 81 | const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename) |
82 | videoId: req.params.videoId, | ||
83 | language: req.params.captionLanguage | ||
84 | }) | ||
85 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 82 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) |
86 | 83 | ||
87 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) | 84 | 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 @@ | |||
1 | import { createReadStream, createWriteStream, move, remove } from 'fs-extra' | ||
1 | import { join } from 'path' | 2 | import { join } from 'path' |
2 | import { CONFIG } from '../initializers/config' | ||
3 | import * as srt2vtt from 'srt-to-vtt' | 3 | import * as srt2vtt from 'srt-to-vtt' |
4 | import { createReadStream, createWriteStream, move, remove } from 'fs-extra' | 4 | import { MVideoCaption } from '@server/types/models' |
5 | import { MVideoCaptionFormattable } from '@server/types/models' | 5 | import { CONFIG } from '../initializers/config' |
6 | 6 | ||
7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) { | 7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaption) { |
8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR | 8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR |
9 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) | 9 | const destination = join(videoCaptionsDir, videoCaption.filename) |
10 | 10 | ||
11 | // Convert this srt file to vtt | 11 | // Convert this srt file to vtt |
12 | if (physicalFile.path.endsWith('.srt')) { | 12 | if (physicalFile.path.endsWith('.srt')) { |
@@ -17,7 +17,7 @@ async function moveAndProcessCaptionFile (physicalFile: { filename: string, path | |||
17 | } | 17 | } |
18 | 18 | ||
19 | // This is important in case if there is another attempt in the retry process | 19 | // This is important in case if there is another attempt in the retry process |
20 | physicalFile.filename = videoCaption.getCaptionName() | 20 | physicalFile.filename = videoCaption.filename |
21 | physicalFile.path = destination | 21 | physicalFile.path = destination |
22 | } | 22 | } |
23 | 23 | ||
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 | |||
9 | if (!videoCaption) { | 9 | if (!videoCaption) { |
10 | res.status(HttpStatusCode.NOT_FOUND_404) | 10 | res.status(HttpStatusCode.NOT_FOUND_404) |
11 | .json({ error: 'Video caption not found' }) | 11 | .json({ error: 'Video caption not found' }) |
12 | .end() | ||
13 | 12 | ||
14 | return false | 13 | return false |
15 | } | 14 | } |
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' | |||
24 | 24 | ||
25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
26 | 26 | ||
27 | const LAST_MIGRATION_VERSION = 575 | 27 | const LAST_MIGRATION_VERSION = 580 |
28 | 28 | ||
29 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
30 | 30 | ||
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 @@ | |||
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 data = { | ||
11 | type: Sequelize.STRING, | ||
12 | allowNull: true, | ||
13 | defaultValue: null | ||
14 | } | ||
15 | |||
16 | await utils.queryInterface.addColumn('videoCaption', 'filename', data) | ||
17 | } | ||
18 | |||
19 | { | ||
20 | const query = `UPDATE "videoCaption" SET "filename" = s.uuid || '-' || s.language || '.vtt' ` + | ||
21 | `FROM (` + | ||
22 | ` SELECT "videoCaption"."id", video.uuid, "videoCaption".language ` + | ||
23 | ` FROM "videoCaption" INNER JOIN video ON video.id = "videoCaption"."videoId"` + | ||
24 | `) AS s ` + | ||
25 | `WHERE "videoCaption".id = s.id` | ||
26 | |||
27 | await utils.sequelize.query(query) | ||
28 | } | ||
29 | |||
30 | { | ||
31 | const data = { | ||
32 | type: Sequelize.STRING, | ||
33 | allowNull: false, | ||
34 | defaultValue: null | ||
35 | } | ||
36 | |||
37 | await utils.queryInterface.changeColumn('videoCaption', 'filename', data) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | function down (options) { | ||
42 | throw new Error('Not implemented.') | ||
43 | } | ||
44 | |||
45 | export { | ||
46 | up, | ||
47 | down | ||
48 | } | ||
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 | |||
99 | return Promise.resolve() | 99 | return Promise.resolve() |
100 | }) | 100 | }) |
101 | 101 | ||
102 | logger.info('toto', { playlist, id: playlist.id }) | ||
103 | |||
104 | const refreshedPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(playlist.id, null) | 102 | const refreshedPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(playlist.id, null) |
105 | 103 | ||
106 | if (playlistObject.icon) { | 104 | 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 { | |||
56 | MVideoAccountLightBlacklistAllFiles, | 56 | MVideoAccountLightBlacklistAllFiles, |
57 | MVideoAP, | 57 | MVideoAP, |
58 | MVideoAPWithoutCaption, | 58 | MVideoAPWithoutCaption, |
59 | MVideoCaption, | ||
59 | MVideoFile, | 60 | MVideoFile, |
60 | MVideoFullLight, | 61 | MVideoFullLight, |
61 | MVideoId, | 62 | MVideoId, |
@@ -90,7 +91,7 @@ async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVid | |||
90 | // Fetch more attributes that we will need to serialize in AP object | 91 | // Fetch more attributes that we will need to serialize in AP object |
91 | if (isArray(video.VideoCaptions) === false) { | 92 | if (isArray(video.VideoCaptions) === false) { |
92 | video.VideoCaptions = await video.$get('VideoCaptions', { | 93 | video.VideoCaptions = await video.$get('VideoCaptions', { |
93 | attributes: [ 'language' ], | 94 | attributes: [ 'filename', 'language' ], |
94 | transaction | 95 | transaction |
95 | }) | 96 | }) |
96 | } | 97 | } |
@@ -423,7 +424,14 @@ async function updateVideoFromAP (options: { | |||
423 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) | 424 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) |
424 | 425 | ||
425 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 426 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
426 | return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, c.url, t) | 427 | const caption = new VideoCaptionModel({ |
428 | videoId: videoUpdated.id, | ||
429 | filename: VideoCaptionModel.generateCaptionName(c.identifier), | ||
430 | language: c.identifier, | ||
431 | fileUrl: c.url | ||
432 | }) as MVideoCaption | ||
433 | |||
434 | return VideoCaptionModel.insertOrReplaceLanguage(caption, t) | ||
427 | }) | 435 | }) |
428 | await Promise.all(videoCaptionsPromises) | 436 | await Promise.all(videoCaptionsPromises) |
429 | } | 437 | } |
@@ -629,7 +637,14 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi | |||
629 | 637 | ||
630 | // Process captions | 638 | // Process captions |
631 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 639 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
632 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, c.url, t) | 640 | const caption = new VideoCaptionModel({ |
641 | videoId: videoCreated.id, | ||
642 | filename: VideoCaptionModel.generateCaptionName(c.identifier), | ||
643 | language: c.identifier, | ||
644 | fileUrl: c.url | ||
645 | }) as MVideoCaption | ||
646 | |||
647 | return VideoCaptionModel.insertOrReplaceLanguage(caption, t) | ||
633 | }) | 648 | }) |
634 | await Promise.all(videoCaptionsPromises) | 649 | await Promise.all(videoCaptionsPromises) |
635 | 650 | ||
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 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | ||
3 | import { CONFIG } from '../../initializers/config' | ||
2 | import { FILES_CACHE } from '../../initializers/constants' | 4 | import { FILES_CACHE } from '../../initializers/constants' |
3 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
4 | import { VideoCaptionModel } from '../../models/video/video-caption' | 6 | import { VideoCaptionModel } from '../../models/video/video-caption' |
5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 7 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
6 | import { CONFIG } from '../../initializers/config' | ||
7 | import { logger } from '../../helpers/logger' | ||
8 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | ||
9 | 8 | ||
10 | type GetPathParam = { videoId: string, language: string } | 9 | class VideosCaptionCache extends AbstractVideoStaticFileCache <string> { |
11 | 10 | ||
12 | class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | ||
13 | |||
14 | private static readonly KEY_DELIMITER = '%' | ||
15 | private static instance: VideosCaptionCache | 11 | private static instance: VideosCaptionCache |
16 | 12 | ||
17 | private constructor () { | 13 | private constructor () { |
@@ -22,32 +18,28 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
22 | return this.instance || (this.instance = new this()) | 18 | return this.instance || (this.instance = new this()) |
23 | } | 19 | } |
24 | 20 | ||
25 | async getFilePathImpl (params: GetPathParam) { | 21 | async getFilePathImpl (filename: string) { |
26 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) | 22 | const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(filename) |
27 | if (!videoCaption) return undefined | 23 | if (!videoCaption) return undefined |
28 | 24 | ||
29 | if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) } | 25 | if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.filename) } |
30 | 26 | ||
31 | const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language | 27 | return this.loadRemoteFile(filename) |
32 | return this.loadRemoteFile(key) | ||
33 | } | 28 | } |
34 | 29 | ||
30 | // Key is the caption filename | ||
35 | protected async loadRemoteFile (key: string) { | 31 | protected async loadRemoteFile (key: string) { |
36 | logger.debug('Loading remote caption file %s.', key) | 32 | const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(key) |
37 | |||
38 | const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) | ||
39 | |||
40 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) | ||
41 | if (!videoCaption) return undefined | 33 | if (!videoCaption) return undefined |
42 | 34 | ||
43 | if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.') | 35 | if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.') |
44 | 36 | ||
45 | // Used to fetch the path | 37 | // Used to fetch the path |
46 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 38 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoCaption.videoId) |
47 | if (!video) return undefined | 39 | if (!video) return undefined |
48 | 40 | ||
49 | const remoteUrl = videoCaption.getFileUrl(video) | 41 | const remoteUrl = videoCaption.getFileUrl(video) |
50 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) | 42 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.filename) |
51 | 43 | ||
52 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) | 44 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) |
53 | 45 | ||
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 <string> { | |||
28 | return this.loadRemoteFile(thumbnail.Video.uuid) | 28 | return this.loadRemoteFile(thumbnail.Video.uuid) |
29 | } | 29 | } |
30 | 30 | ||
31 | // Key is the video UUID | ||
31 | protected async loadRemoteFile (key: string) { | 32 | protected async loadRemoteFile (key: string) { |
32 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(key) | 33 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(key) |
33 | if (!video) return undefined | 34 | 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: { | |||
166 | }) { | 166 | }) { |
167 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters | 167 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters |
168 | 168 | ||
169 | // Remove old file | ||
170 | if (existingThumbnail) await existingThumbnail.removeThumbnail() | ||
171 | |||
169 | const thumbnail = existingThumbnail || new ThumbnailModel() | 172 | const thumbnail = existingThumbnail || new ThumbnailModel() |
170 | 173 | ||
171 | thumbnail.filename = filename | 174 | 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 { | |||
16 | UpdatedAt | 16 | UpdatedAt |
17 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
18 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | 18 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' |
19 | import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' | 19 | import { MVideoAccountLight, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' |
20 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 20 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
21 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 21 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
22 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
@@ -24,6 +24,7 @@ import { CONFIG } from '../../initializers/config' | |||
24 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' | 24 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' |
25 | import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' | 25 | import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' |
26 | import { VideoModel } from './video' | 26 | import { VideoModel } from './video' |
27 | import { v4 as uuidv4 } from 'uuid' | ||
27 | 28 | ||
28 | export enum ScopeNames { | 29 | export enum ScopeNames { |
29 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 30 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
@@ -45,6 +46,10 @@ export enum ScopeNames { | |||
45 | tableName: 'videoCaption', | 46 | tableName: 'videoCaption', |
46 | indexes: [ | 47 | indexes: [ |
47 | { | 48 | { |
49 | fields: [ 'filename' ], | ||
50 | unique: true | ||
51 | }, | ||
52 | { | ||
48 | fields: [ 'videoId' ] | 53 | fields: [ 'videoId' ] |
49 | }, | 54 | }, |
50 | { | 55 | { |
@@ -65,6 +70,10 @@ export class VideoCaptionModel extends Model { | |||
65 | @Column | 70 | @Column |
66 | language: string | 71 | language: string |
67 | 72 | ||
73 | @AllowNull(false) | ||
74 | @Column | ||
75 | filename: string | ||
76 | |||
68 | @AllowNull(true) | 77 | @AllowNull(true) |
69 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max)) | 78 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max)) |
70 | fileUrl: string | 79 | fileUrl: string |
@@ -88,12 +97,12 @@ export class VideoCaptionModel extends Model { | |||
88 | } | 97 | } |
89 | 98 | ||
90 | if (instance.isOwned()) { | 99 | if (instance.isOwned()) { |
91 | logger.info('Removing captions %s of video %s.', instance.Video.uuid, instance.language) | 100 | logger.info('Removing caption %s.', instance.filename) |
92 | 101 | ||
93 | try { | 102 | try { |
94 | await instance.removeCaptionFile() | 103 | await instance.removeCaptionFile() |
95 | } catch (err) { | 104 | } catch (err) { |
96 | logger.error('Cannot remove caption file of video %s.', instance.Video.uuid) | 105 | logger.error('Cannot remove caption file %s.', instance.filename) |
97 | } | 106 | } |
98 | } | 107 | } |
99 | 108 | ||
@@ -119,15 +128,28 @@ export class VideoCaptionModel extends Model { | |||
119 | return VideoCaptionModel.findOne(query) | 128 | return VideoCaptionModel.findOne(query) |
120 | } | 129 | } |
121 | 130 | ||
122 | static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) { | 131 | static loadWithVideoByFilename (filename: string): Promise<MVideoCaptionVideo> { |
123 | const values = { | 132 | const query = { |
124 | videoId, | 133 | where: { |
125 | language, | 134 | filename |
126 | fileUrl | 135 | }, |
136 | include: [ | ||
137 | { | ||
138 | model: VideoModel.unscoped(), | ||
139 | attributes: [ 'id', 'remote', 'uuid' ] | ||
140 | } | ||
141 | ] | ||
127 | } | 142 | } |
128 | 143 | ||
129 | return VideoCaptionModel.upsert(values, { transaction, returning: true }) | 144 | return VideoCaptionModel.findOne(query) |
130 | .then(([ caption ]) => caption) | 145 | } |
146 | |||
147 | static async insertOrReplaceLanguage (caption: MVideoCaption, transaction: Transaction) { | ||
148 | const existing = await VideoCaptionModel.loadByVideoIdAndLanguage(caption.videoId, caption.language) | ||
149 | // Delete existing file | ||
150 | if (existing) await existing.destroy({ transaction }) | ||
151 | |||
152 | return caption.save({ transaction }) | ||
131 | } | 153 | } |
132 | 154 | ||
133 | static listVideoCaptions (videoId: number): Promise<MVideoCaptionVideo[]> { | 155 | static listVideoCaptions (videoId: number): Promise<MVideoCaptionVideo[]> { |
@@ -156,6 +178,10 @@ export class VideoCaptionModel extends Model { | |||
156 | return VideoCaptionModel.destroy(query) | 178 | return VideoCaptionModel.destroy(query) |
157 | } | 179 | } |
158 | 180 | ||
181 | static generateCaptionName (language: string) { | ||
182 | return `${uuidv4()}-${language}.vtt` | ||
183 | } | ||
184 | |||
159 | isOwned () { | 185 | isOwned () { |
160 | return this.Video.remote === false | 186 | return this.Video.remote === false |
161 | } | 187 | } |
@@ -170,16 +196,12 @@ export class VideoCaptionModel extends Model { | |||
170 | } | 196 | } |
171 | } | 197 | } |
172 | 198 | ||
173 | getCaptionStaticPath (this: MVideoCaptionFormattable) { | 199 | getCaptionStaticPath (this: MVideoCaption) { |
174 | return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) | 200 | return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.filename) |
175 | } | ||
176 | |||
177 | getCaptionName (this: MVideoCaptionFormattable) { | ||
178 | return `${this.Video.uuid}-${this.language}.vtt` | ||
179 | } | 201 | } |
180 | 202 | ||
181 | removeCaptionFile (this: MVideoCaptionFormattable) { | 203 | removeCaptionFile (this: MVideoCaption) { |
182 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) | 204 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.filename) |
183 | } | 205 | } |
184 | 206 | ||
185 | getFileUrl (video: MVideoAccountLight) { | 207 | 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 { | |||
471 | generateThumbnailName () { | 471 | generateThumbnailName () { |
472 | const extension = '.jpg' | 472 | const extension = '.jpg' |
473 | 473 | ||
474 | return 'playlist-' + this.uuid + extension | 474 | return 'playlist-' + uuidv4() + extension |
475 | } | 475 | } |
476 | 476 | ||
477 | getThumbnailUrl () { | 477 | 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 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { Video, VideoPlaylistPrivacy } from '@shared/models' | ||
5 | import { | 6 | import { |
7 | addVideoInPlaylist, | ||
8 | createVideoPlaylist, | ||
6 | getOEmbed, | 9 | getOEmbed, |
7 | getVideosList, | 10 | getVideosList, |
8 | ServerInfo, | 11 | ServerInfo, |
9 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
10 | setDefaultVideoChannel, | 13 | setDefaultVideoChannel, |
11 | uploadVideo, | 14 | uploadVideo |
12 | createVideoPlaylist, | ||
13 | addVideoInPlaylist | ||
14 | } from '../../../../shared/extra-utils' | 15 | } from '../../../../shared/extra-utils' |
15 | import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers' | 16 | import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers' |
16 | import { VideoPlaylistPrivacy } from '@shared/models' | ||
17 | 17 | ||
18 | const expect = chai.expect | 18 | const expect = chai.expect |
19 | 19 | ||
20 | describe('Test services', function () { | 20 | describe('Test services', function () { |
21 | let server: ServerInfo = null | 21 | let server: ServerInfo = null |
22 | let playlistUUID: string | 22 | let playlistUUID: string |
23 | let video: Video | ||
23 | 24 | ||
24 | before(async function () { | 25 | before(async function () { |
25 | this.timeout(30000) | 26 | this.timeout(30000) |
@@ -36,7 +37,7 @@ describe('Test services', function () { | |||
36 | await uploadVideo(server.url, server.accessToken, videoAttributes) | 37 | await uploadVideo(server.url, server.accessToken, videoAttributes) |
37 | 38 | ||
38 | const res = await getVideosList(server.url) | 39 | const res = await getVideosList(server.url) |
39 | server.video = res.body.data[0] | 40 | video = res.body.data[0] |
40 | } | 41 | } |
41 | 42 | ||
42 | { | 43 | { |
@@ -57,23 +58,23 @@ describe('Test services', function () { | |||
57 | token: server.accessToken, | 58 | token: server.accessToken, |
58 | playlistId: res.body.videoPlaylist.id, | 59 | playlistId: res.body.videoPlaylist.id, |
59 | elementAttrs: { | 60 | elementAttrs: { |
60 | videoId: server.video.id | 61 | videoId: video.id |
61 | } | 62 | } |
62 | }) | 63 | }) |
63 | } | 64 | } |
64 | }) | 65 | }) |
65 | 66 | ||
66 | it('Should have a valid oEmbed video response', async function () { | 67 | it('Should have a valid oEmbed video response', async function () { |
67 | const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid | 68 | const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid |
68 | 69 | ||
69 | const res = await getOEmbed(server.url, oembedUrl) | 70 | const res = await getOEmbed(server.url, oembedUrl) |
70 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + | 71 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + |
71 | `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + | 72 | `src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + |
72 | 'frameborder="0" allowfullscreen></iframe>' | 73 | 'frameborder="0" allowfullscreen></iframe>' |
73 | const expectedThumbnailUrl = 'http://localhost:' + server.port + '/lazy-static/previews/' + server.video.uuid + '.jpg' | 74 | const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath |
74 | 75 | ||
75 | expect(res.body.html).to.equal(expectedHtml) | 76 | expect(res.body.html).to.equal(expectedHtml) |
76 | expect(res.body.title).to.equal(server.video.name) | 77 | expect(res.body.title).to.equal(video.name) |
77 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) | 78 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) |
78 | expect(res.body.width).to.equal(560) | 79 | expect(res.body.width).to.equal(560) |
79 | expect(res.body.height).to.equal(315) | 80 | expect(res.body.height).to.equal(315) |
@@ -101,18 +102,18 @@ describe('Test services', function () { | |||
101 | }) | 102 | }) |
102 | 103 | ||
103 | it('Should have a valid oEmbed response with small max height query', async function () { | 104 | it('Should have a valid oEmbed response with small max height query', async function () { |
104 | const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid | 105 | const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid |
105 | const format = 'json' | 106 | const format = 'json' |
106 | const maxHeight = 50 | 107 | const maxHeight = 50 |
107 | const maxWidth = 50 | 108 | const maxWidth = 50 |
108 | 109 | ||
109 | const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) | 110 | const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) |
110 | const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + | 111 | const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + |
111 | `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + | 112 | `src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + |
112 | 'frameborder="0" allowfullscreen></iframe>' | 113 | 'frameborder="0" allowfullscreen></iframe>' |
113 | 114 | ||
114 | expect(res.body.html).to.equal(expectedHtml) | 115 | expect(res.body.html).to.equal(expectedHtml) |
115 | expect(res.body.title).to.equal(server.video.name) | 116 | expect(res.body.title).to.equal(video.name) |
116 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) | 117 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) |
117 | expect(res.body.height).to.equal(50) | 118 | expect(res.body.height).to.equal(50) |
118 | expect(res.body.width).to.equal(50) | 119 | 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 | |||
24 | const expect = chai.expect | 24 | const expect = chai.expect |
25 | 25 | ||
26 | describe('Test video captions', function () { | 26 | describe('Test video captions', function () { |
27 | const uuidRegex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | ||
28 | |||
27 | let servers: ServerInfo[] | 29 | let servers: ServerInfo[] |
28 | let videoUUID: string | 30 | let videoUUID: string |
29 | 31 | ||
@@ -83,13 +85,13 @@ describe('Test video captions', function () { | |||
83 | const caption1: VideoCaption = res.body.data[0] | 85 | const caption1: VideoCaption = res.body.data[0] |
84 | expect(caption1.language.id).to.equal('ar') | 86 | expect(caption1.language.id).to.equal('ar') |
85 | expect(caption1.language.label).to.equal('Arabic') | 87 | expect(caption1.language.label).to.equal('Arabic') |
86 | expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt') | 88 | expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) |
87 | await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 1.') | 89 | await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 1.') |
88 | 90 | ||
89 | const caption2: VideoCaption = res.body.data[1] | 91 | const caption2: VideoCaption = res.body.data[1] |
90 | expect(caption2.language.id).to.equal('zh') | 92 | expect(caption2.language.id).to.equal('zh') |
91 | expect(caption2.language.label).to.equal('Chinese') | 93 | expect(caption2.language.label).to.equal('Chinese') |
92 | expect(caption2.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-zh.vtt') | 94 | expect(caption2.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$')) |
93 | await testCaptionFile(server.url, caption2.captionPath, 'Subtitle good 2.') | 95 | await testCaptionFile(server.url, caption2.captionPath, 'Subtitle good 2.') |
94 | } | 96 | } |
95 | }) | 97 | }) |
@@ -117,7 +119,7 @@ describe('Test video captions', function () { | |||
117 | const caption1: VideoCaption = res.body.data[0] | 119 | const caption1: VideoCaption = res.body.data[0] |
118 | expect(caption1.language.id).to.equal('ar') | 120 | expect(caption1.language.id).to.equal('ar') |
119 | expect(caption1.language.label).to.equal('Arabic') | 121 | expect(caption1.language.label).to.equal('Arabic') |
120 | expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt') | 122 | expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) |
121 | await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 2.') | 123 | await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 2.') |
122 | } | 124 | } |
123 | }) | 125 | }) |
@@ -148,7 +150,7 @@ describe('Test video captions', function () { | |||
148 | const caption1: VideoCaption = res.body.data[0] | 150 | const caption1: VideoCaption = res.body.data[0] |
149 | expect(caption1.language.id).to.equal('ar') | 151 | expect(caption1.language.id).to.equal('ar') |
150 | expect(caption1.language.label).to.equal('Arabic') | 152 | expect(caption1.language.label).to.equal('Arabic') |
151 | expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt') | 153 | expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$')) |
152 | 154 | ||
153 | const expected = 'WEBVTT FILE\r\n' + | 155 | const expected = 'WEBVTT FILE\r\n' + |
154 | '\r\n' + | 156 | '\r\n' + |
@@ -185,7 +187,7 @@ describe('Test video captions', function () { | |||
185 | 187 | ||
186 | expect(caption.language.id).to.equal('zh') | 188 | expect(caption.language.id).to.equal('zh') |
187 | expect(caption.language.label).to.equal('Chinese') | 189 | expect(caption.language.label).to.equal('Chinese') |
188 | expect(caption.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-zh.vtt') | 190 | expect(caption.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$')) |
189 | await testCaptionFile(server.url, caption.captionPath, 'Subtitle good 2.') | 191 | await testCaptionFile(server.url, caption.captionPath, 'Subtitle good 2.') |
190 | } | 192 | } |
191 | }) | 193 | }) |
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 @@ | |||
1 | import { PickWith } from '@shared/core-utils' | ||
1 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 2 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
2 | import { FunctionProperties, PickWith } from '@shared/core-utils' | ||
3 | import { MVideo, MVideoUUID } from './video' | 3 | import { MVideo, MVideoUUID } from './video' |
4 | 4 | ||
5 | type Use<K extends keyof VideoCaptionModel, M> = PickWith<VideoCaptionModel, K, M> | 5 | type Use<K extends keyof VideoCaptionModel, M> = PickWith<VideoCaptionModel, K, M> |
@@ -22,6 +22,6 @@ export type MVideoCaptionVideo = | |||
22 | // Format for API or AP object | 22 | // Format for API or AP object |
23 | 23 | ||
24 | export type MVideoCaptionFormattable = | 24 | export type MVideoCaptionFormattable = |
25 | FunctionProperties<MVideoCaption> & | 25 | MVideoCaption & |
26 | Pick<MVideoCaption, 'language'> & | 26 | Pick<MVideoCaption, 'language'> & |
27 | Use<'Video', MVideoUUID> | 27 | Use<'Video', MVideoUUID> |