diff options
author | Chocobozzz <me@florianbigard.com> | 2018-07-16 14:22:16 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-07-16 14:31:40 +0200 |
commit | f4001cf408a99049d01a356bfb20a62342de06ea (patch) | |
tree | 421776dfe64335dca2725ac3ac5f3b3e6b7564c6 /server | |
parent | 16f7022b06fb76c0b00c23c970bc8df605b0ec63 (diff) | |
download | PeerTube-f4001cf408a99049d01a356bfb20a62342de06ea.tar.gz PeerTube-f4001cf408a99049d01a356bfb20a62342de06ea.tar.zst PeerTube-f4001cf408a99049d01a356bfb20a62342de06ea.zip |
Handle .srt subtitles
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/videos/captions.ts | 10 | ||||
-rw-r--r-- | server/helpers/captions-utils.ts | 47 | ||||
-rw-r--r-- | server/helpers/custom-validators/video-captions.ts | 11 | ||||
-rw-r--r-- | server/initializers/constants.ts | 17 | ||||
-rw-r--r-- | server/initializers/installer.ts | 6 | ||||
-rw-r--r-- | server/lib/cache/abstract-video-static-file-cache.ts | 9 | ||||
-rw-r--r-- | server/lib/cache/videos-caption-cache.ts | 2 | ||||
-rw-r--r-- | server/lib/cache/videos-preview-cache.ts | 2 | ||||
-rw-r--r-- | server/models/video/video-caption.ts | 14 | ||||
-rw-r--r-- | server/tests/api/check-params/video-captions.ts | 35 | ||||
-rw-r--r-- | server/tests/api/videos/video-captions.ts | 53 | ||||
-rw-r--r-- | server/tests/fixtures/subtitle-bad.txt | 11 | ||||
-rw-r--r-- | server/tests/fixtures/subtitle-good.srt | 11 | ||||
-rw-r--r-- | server/tests/utils/videos/videos.ts | 2 |
14 files changed, 193 insertions, 37 deletions
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index 05412a17f..4cf8de1ef 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts | |||
@@ -9,11 +9,10 @@ import { createReqFiles } from '../../../helpers/express-utils' | |||
9 | import { CONFIG, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers' | 9 | import { CONFIG, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers' |
10 | import { getFormattedObjects } from '../../../helpers/utils' | 10 | import { getFormattedObjects } from '../../../helpers/utils' |
11 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 11 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
12 | import { renamePromise } from '../../../helpers/core-utils' | ||
13 | import { join } from 'path' | ||
14 | import { VideoModel } from '../../../models/video/video' | 12 | import { VideoModel } from '../../../models/video/video' |
15 | import { logger } from '../../../helpers/logger' | 13 | import { logger } from '../../../helpers/logger' |
16 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | 14 | import { federateVideoIfNeeded } from '../../../lib/activitypub' |
15 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | ||
17 | 16 | ||
18 | const reqVideoCaptionAdd = createReqFiles( | 17 | const reqVideoCaptionAdd = createReqFiles( |
19 | [ 'captionfile' ], | 18 | [ 'captionfile' ], |
@@ -66,12 +65,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) { | |||
66 | videoCaption.Video = video | 65 | videoCaption.Video = video |
67 | 66 | ||
68 | // Move physical file | 67 | // Move physical file |
69 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR | 68 | await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) |
70 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) | ||
71 | await renamePromise(videoCaptionPhysicalFile.path, destination) | ||
72 | // This is important in case if there is another attempt in the retry process | ||
73 | videoCaptionPhysicalFile.filename = videoCaption.getCaptionName() | ||
74 | videoCaptionPhysicalFile.path = destination | ||
75 | 69 | ||
76 | await sequelizeTypescript.transaction(async t => { | 70 | await sequelizeTypescript.transaction(async t => { |
77 | await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, t) | 71 | await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, t) |
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts new file mode 100644 index 000000000..8b04f878d --- /dev/null +++ b/server/helpers/captions-utils.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import { renamePromise, unlinkPromise } from './core-utils' | ||
2 | import { join } from 'path' | ||
3 | import { CONFIG } from '../initializers' | ||
4 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
5 | import * as srt2vtt from 'srt-to-vtt' | ||
6 | import { createReadStream, createWriteStream } from 'fs' | ||
7 | |||
8 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) { | ||
9 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR | ||
10 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) | ||
11 | |||
12 | // Convert this srt file to vtt | ||
13 | if (physicalFile.path.endsWith('.srt')) { | ||
14 | await convertSrtToVtt(physicalFile.path, destination) | ||
15 | await unlinkPromise(physicalFile.path) | ||
16 | } else { // Just move the vtt file | ||
17 | await renamePromise(physicalFile.path, destination) | ||
18 | } | ||
19 | |||
20 | // This is important in case if there is another attempt in the retry process | ||
21 | physicalFile.filename = videoCaption.getCaptionName() | ||
22 | physicalFile.path = destination | ||
23 | } | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | export { | ||
28 | moveAndProcessCaptionFile | ||
29 | } | ||
30 | |||
31 | // --------------------------------------------------------------------------- | ||
32 | |||
33 | function convertSrtToVtt (source: string, destination: string) { | ||
34 | return new Promise((res, rej) => { | ||
35 | const file = createReadStream(source) | ||
36 | const converter = srt2vtt() | ||
37 | const writer = createWriteStream(destination) | ||
38 | |||
39 | for (const s of [ file, converter, writer ]) { | ||
40 | s.on('error', err => rej(err)) | ||
41 | } | ||
42 | |||
43 | return file.pipe(converter) | ||
44 | .pipe(writer) | ||
45 | .on('finish', () => res()) | ||
46 | }) | ||
47 | } | ||
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts index fd4dc740b..6a9c6d75c 100644 --- a/server/helpers/custom-validators/video-captions.ts +++ b/server/helpers/custom-validators/video-captions.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { CONSTRAINTS_FIELDS, VIDEO_LANGUAGES } from '../../initializers' | 1 | import { CONSTRAINTS_FIELDS, VIDEO_CAPTIONS_MIMETYPE_EXT, VIDEO_LANGUAGES, VIDEO_MIMETYPE_EXT } from '../../initializers' |
2 | import { exists, isFileValid } from './misc' | 2 | import { exists, isFileValid } from './misc' |
3 | import { Response } from 'express' | 3 | import { Response } from 'express' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
@@ -8,13 +8,10 @@ function isVideoCaptionLanguageValid (value: any) { | |||
8 | return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined | 8 | return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined |
9 | } | 9 | } |
10 | 10 | ||
11 | const videoCaptionTypes = CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME | 11 | const videoCaptionTypes = Object.keys(VIDEO_CAPTIONS_MIMETYPE_EXT).map(m => `(${m})`) |
12 | .map(v => v.replace('.', '')) | 12 | const videoCaptionTypesRegex = videoCaptionTypes.join('|') |
13 | .join('|') | ||
14 | const videoCaptionsTypesRegex = `text/(${videoCaptionTypes})` | ||
15 | |||
16 | function isVideoCaptionFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) { | 13 | function isVideoCaptionFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) { |
17 | return isFileValid(files, videoCaptionsTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) | 14 | return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) |
18 | } | 15 | } |
19 | 16 | ||
20 | async function isVideoCaptionExist (video: VideoModel, language: string, res: Response) { | 17 | async function isVideoCaptionExist (video: VideoModel, language: string, res: Response) { |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 49809e64c..3837f7062 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -231,7 +231,7 @@ const CONSTRAINTS_FIELDS = { | |||
231 | }, | 231 | }, |
232 | VIDEO_CAPTIONS: { | 232 | VIDEO_CAPTIONS: { |
233 | CAPTION_FILE: { | 233 | CAPTION_FILE: { |
234 | EXTNAME: [ '.vtt' ], | 234 | EXTNAME: [ '.vtt', '.srt' ], |
235 | FILE_SIZE: { | 235 | FILE_SIZE: { |
236 | max: 2 * 1024 * 1024 // 2MB | 236 | max: 2 * 1024 * 1024 // 2MB |
237 | } | 237 | } |
@@ -364,7 +364,8 @@ const IMAGE_MIMETYPE_EXT = { | |||
364 | } | 364 | } |
365 | 365 | ||
366 | const VIDEO_CAPTIONS_MIMETYPE_EXT = { | 366 | const VIDEO_CAPTIONS_MIMETYPE_EXT = { |
367 | 'text/vtt': '.vtt' | 367 | 'text/vtt': '.vtt', |
368 | 'application/x-subrip': '.srt' | ||
368 | } | 369 | } |
369 | 370 | ||
370 | // --------------------------------------------------------------------------- | 371 | // --------------------------------------------------------------------------- |
@@ -451,9 +452,13 @@ const EMBED_SIZE = { | |||
451 | 452 | ||
452 | // Sub folders of cache directory | 453 | // Sub folders of cache directory |
453 | const CACHE = { | 454 | const CACHE = { |
454 | DIRECTORIES: { | 455 | PREVIEWS: { |
455 | PREVIEWS: join(CONFIG.STORAGE.CACHE_DIR, 'previews'), | 456 | DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'previews'), |
456 | VIDEO_CAPTIONS: join(CONFIG.STORAGE.CACHE_DIR, 'video-captions') | 457 | MAX_AGE: 1000 * 3600 * 3 // 3 hours |
458 | }, | ||
459 | VIDEO_CAPTIONS: { | ||
460 | DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'video-captions'), | ||
461 | MAX_AGE: 1000 * 3600 * 3 // 3 hours | ||
457 | } | 462 | } |
458 | } | 463 | } |
459 | 464 | ||
@@ -500,6 +505,8 @@ if (isTestInstance() === true) { | |||
500 | VIDEO_VIEW_LIFETIME = 1000 // 1 second | 505 | VIDEO_VIEW_LIFETIME = 1000 // 1 second |
501 | 506 | ||
502 | JOB_ATTEMPTS['email'] = 1 | 507 | JOB_ATTEMPTS['email'] = 1 |
508 | |||
509 | CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 | ||
503 | } | 510 | } |
504 | 511 | ||
505 | updateWebserverConfig() | 512 | updateWebserverConfig() |
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index b0084b368..1f513a9c3 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts | |||
@@ -33,7 +33,8 @@ export { | |||
33 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
34 | 34 | ||
35 | function removeCacheDirectories () { | 35 | function removeCacheDirectories () { |
36 | const cacheDirectories = CACHE.DIRECTORIES | 36 | const cacheDirectories = Object.keys(CACHE) |
37 | .map(k => CACHE[k].DIRECTORY) | ||
37 | 38 | ||
38 | const tasks: Promise<any>[] = [] | 39 | const tasks: Promise<any>[] = [] |
39 | 40 | ||
@@ -48,7 +49,8 @@ function removeCacheDirectories () { | |||
48 | 49 | ||
49 | function createDirectoriesIfNotExist () { | 50 | function createDirectoriesIfNotExist () { |
50 | const storage = CONFIG.STORAGE | 51 | const storage = CONFIG.STORAGE |
51 | const cacheDirectories = CACHE.DIRECTORIES | 52 | const cacheDirectories = Object.keys(CACHE) |
53 | .map(k => CACHE[k].DIRECTORY) | ||
52 | 54 | ||
53 | const tasks = [] | 55 | const tasks = [] |
54 | for (const key of Object.keys(storage)) { | 56 | for (const key of Object.keys(storage)) { |
diff --git a/server/lib/cache/abstract-video-static-file-cache.ts b/server/lib/cache/abstract-video-static-file-cache.ts index 7eeeb6b3a..8e895cc82 100644 --- a/server/lib/cache/abstract-video-static-file-cache.ts +++ b/server/lib/cache/abstract-video-static-file-cache.ts | |||
@@ -1,12 +1,9 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | 1 | import * as AsyncLRU from 'async-lru' |
2 | import { createWriteStream } from 'fs' | 2 | import { createWriteStream } from 'fs' |
3 | import { join } from 'path' | ||
4 | import { unlinkPromise } from '../../helpers/core-utils' | 3 | import { unlinkPromise } from '../../helpers/core-utils' |
5 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
6 | import { CACHE, CONFIG } from '../../initializers' | ||
7 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
8 | import { fetchRemoteVideoStaticFile } from '../activitypub' | 6 | import { fetchRemoteVideoStaticFile } from '../activitypub' |
9 | import { VideoCaptionModel } from '../../models/video/video-caption' | ||
10 | 7 | ||
11 | export abstract class AbstractVideoStaticFileCache <T> { | 8 | export abstract class AbstractVideoStaticFileCache <T> { |
12 | 9 | ||
@@ -17,9 +14,10 @@ export abstract class AbstractVideoStaticFileCache <T> { | |||
17 | // Load and save the remote file, then return the local path from filesystem | 14 | // Load and save the remote file, then return the local path from filesystem |
18 | protected abstract loadRemoteFile (key: string): Promise<string> | 15 | protected abstract loadRemoteFile (key: string): Promise<string> |
19 | 16 | ||
20 | init (max: number) { | 17 | init (max: number, maxAge: number) { |
21 | this.lru = new AsyncLRU({ | 18 | this.lru = new AsyncLRU({ |
22 | max, | 19 | max, |
20 | maxAge, | ||
23 | load: (key, cb) => { | 21 | load: (key, cb) => { |
24 | this.loadRemoteFile(key) | 22 | this.loadRemoteFile(key) |
25 | .then(res => cb(null, res)) | 23 | .then(res => cb(null, res)) |
@@ -28,7 +26,8 @@ export abstract class AbstractVideoStaticFileCache <T> { | |||
28 | }) | 26 | }) |
29 | 27 | ||
30 | this.lru.on('evict', (obj: { key: string, value: string }) => { | 28 | this.lru.on('evict', (obj: { key: string, value: string }) => { |
31 | unlinkPromise(obj.value).then(() => logger.debug('%s evicted from %s', obj.value, this.constructor.name)) | 29 | unlinkPromise(obj.value) |
30 | .then(() => logger.debug('%s evicted from %s', obj.value, this.constructor.name)) | ||
32 | }) | 31 | }) |
33 | } | 32 | } |
34 | 33 | ||
diff --git a/server/lib/cache/videos-caption-cache.ts b/server/lib/cache/videos-caption-cache.ts index 1336610b2..380d42b2c 100644 --- a/server/lib/cache/videos-caption-cache.ts +++ b/server/lib/cache/videos-caption-cache.ts | |||
@@ -42,7 +42,7 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
42 | if (!video) return undefined | 42 | if (!video) return undefined |
43 | 43 | ||
44 | const remoteStaticPath = videoCaption.getCaptionStaticPath() | 44 | const remoteStaticPath = videoCaption.getCaptionStaticPath() |
45 | const destPath = join(CACHE.DIRECTORIES.VIDEO_CAPTIONS, videoCaption.getCaptionName()) | 45 | const destPath = join(CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) |
46 | 46 | ||
47 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) | 47 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) |
48 | } | 48 | } |
diff --git a/server/lib/cache/videos-preview-cache.ts b/server/lib/cache/videos-preview-cache.ts index 1c0e7ed9d..22b6d9cb0 100644 --- a/server/lib/cache/videos-preview-cache.ts +++ b/server/lib/cache/videos-preview-cache.ts | |||
@@ -31,7 +31,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
31 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') | 31 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') |
32 | 32 | ||
33 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) | 33 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) |
34 | const destPath = join(CACHE.DIRECTORIES.PREVIEWS, video.getPreviewName()) | 34 | const destPath = join(CACHE.PREVIEWS.DIRECTORY, video.getPreviewName()) |
35 | 35 | ||
36 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) | 36 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) |
37 | } | 37 | } |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index 9920dfc7c..5a1becc47 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -75,14 +75,18 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
75 | 75 | ||
76 | @BeforeDestroy | 76 | @BeforeDestroy |
77 | static async removeFiles (instance: VideoCaptionModel) { | 77 | static async removeFiles (instance: VideoCaptionModel) { |
78 | if (!instance.Video) { | ||
79 | instance.Video = await instance.$get('Video') as VideoModel | ||
80 | } | ||
78 | 81 | ||
79 | if (instance.isOwned()) { | 82 | if (instance.isOwned()) { |
80 | if (!instance.Video) { | ||
81 | instance.Video = await instance.$get('Video') as VideoModel | ||
82 | } | ||
83 | |||
84 | logger.debug('Removing captions %s of video %s.', instance.Video.uuid, instance.language) | 83 | logger.debug('Removing captions %s of video %s.', instance.Video.uuid, instance.language) |
85 | return instance.removeCaptionFile() | 84 | |
85 | try { | ||
86 | await instance.removeCaptionFile() | ||
87 | } catch (err) { | ||
88 | logger.error('Cannot remove caption file of video %s.', instance.Video.uuid) | ||
89 | } | ||
86 | } | 90 | } |
87 | 91 | ||
88 | return undefined | 92 | return undefined |
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts index 12f890db8..a3d7ac35d 100644 --- a/server/tests/api/check-params/video-captions.ts +++ b/server/tests/api/check-params/video-captions.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* tslint:disable:no-unused-expression */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { | 4 | import { |
6 | createUser, | 5 | createUser, |
@@ -127,6 +126,40 @@ describe('Test video captions API validator', function () { | |||
127 | }) | 126 | }) |
128 | }) | 127 | }) |
129 | 128 | ||
129 | it('Should fail with an invalid captionfile extension', async function () { | ||
130 | const attaches = { | ||
131 | 'captionfile': join(__dirname, '..', '..', 'fixtures', 'subtitle-bad.txt') | ||
132 | } | ||
133 | |||
134 | const captionPath = path + videoUUID + '/captions/fr' | ||
135 | await makeUploadRequest({ | ||
136 | method: 'PUT', | ||
137 | url: server.url, | ||
138 | path: captionPath, | ||
139 | token: server.accessToken, | ||
140 | fields, | ||
141 | attaches, | ||
142 | statusCodeExpected: 400 | ||
143 | }) | ||
144 | }) | ||
145 | |||
146 | // it('Should fail with an invalid captionfile srt', async function () { | ||
147 | // const attaches = { | ||
148 | // 'captionfile': join(__dirname, '..', '..', 'fixtures', 'subtitle-bad.srt') | ||
149 | // } | ||
150 | // | ||
151 | // const captionPath = path + videoUUID + '/captions/fr' | ||
152 | // await makeUploadRequest({ | ||
153 | // method: 'PUT', | ||
154 | // url: server.url, | ||
155 | // path: captionPath, | ||
156 | // token: server.accessToken, | ||
157 | // fields, | ||
158 | // attaches, | ||
159 | // statusCodeExpected: 500 | ||
160 | // }) | ||
161 | // }) | ||
162 | |||
130 | it('Should success with the correct parameters', async function () { | 163 | it('Should success with the correct parameters', async function () { |
131 | const captionPath = path + videoUUID + '/captions/fr' | 164 | const captionPath = path + videoUUID + '/captions/fr' |
132 | await makeUploadRequest({ | 165 | await makeUploadRequest({ |
diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts index cbf5268f0..eb73c5baf 100644 --- a/server/tests/api/videos/video-captions.ts +++ b/server/tests/api/videos/video-captions.ts | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { doubleFollow, flushAndRunMultipleServers, uploadVideo } from '../../utils' | 5 | import { checkVideoFilesWereRemoved, doubleFollow, flushAndRunMultipleServers, removeVideo, uploadVideo, wait } from '../../utils' |
6 | import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../utils/index' | 6 | import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../utils/index' |
7 | import { waitJobs } from '../../utils/server/jobs' | 7 | import { waitJobs } from '../../utils/server/jobs' |
8 | import { createVideoCaption, deleteVideoCaption, listVideoCaptions, testCaptionFile } from '../../utils/videos/video-captions' | 8 | import { createVideoCaption, deleteVideoCaption, listVideoCaptions, testCaptionFile } from '../../utils/videos/video-captions' |
@@ -110,6 +110,51 @@ describe('Test video captions', function () { | |||
110 | } | 110 | } |
111 | }) | 111 | }) |
112 | 112 | ||
113 | it('Should replace an existing caption with a srt file and convert it', async function () { | ||
114 | this.timeout(30000) | ||
115 | |||
116 | await createVideoCaption({ | ||
117 | url: servers[0].url, | ||
118 | accessToken: servers[0].accessToken, | ||
119 | language: 'ar', | ||
120 | videoId: videoUUID, | ||
121 | fixture: 'subtitle-good.srt' | ||
122 | }) | ||
123 | |||
124 | await waitJobs(servers) | ||
125 | |||
126 | // Cache invalidation | ||
127 | await wait(3000) | ||
128 | }) | ||
129 | |||
130 | it('Should have this caption updated and converted', async function () { | ||
131 | for (const server of servers) { | ||
132 | const res = await listVideoCaptions(server.url, videoUUID) | ||
133 | expect(res.body.total).to.equal(2) | ||
134 | expect(res.body.data).to.have.lengthOf(2) | ||
135 | |||
136 | const caption1: VideoCaption = res.body.data[0] | ||
137 | expect(caption1.language.id).to.equal('ar') | ||
138 | expect(caption1.language.label).to.equal('Arabic') | ||
139 | expect(caption1.captionPath).to.equal('/static/video-captions/' + videoUUID + '-ar.vtt') | ||
140 | |||
141 | const expected = 'WEBVTT FILE\r\n' + | ||
142 | '\r\n' + | ||
143 | '1\r\n' + | ||
144 | '00:00:01.600 --> 00:00:04.200\r\n' + | ||
145 | 'English (US)\r\n' + | ||
146 | '\r\n' + | ||
147 | '2\r\n' + | ||
148 | '00:00:05.900 --> 00:00:07.999\r\n' + | ||
149 | 'This is a subtitle in American English\r\n' + | ||
150 | '\r\n' + | ||
151 | '3\r\n' + | ||
152 | '00:00:10.000 --> 00:00:14.000\r\n' + | ||
153 | 'Adding subtitles is very easy to do\r\n' | ||
154 | await testCaptionFile(server.url, caption1.captionPath, expected) | ||
155 | } | ||
156 | }) | ||
157 | |||
113 | it('Should remove one caption', async function () { | 158 | it('Should remove one caption', async function () { |
114 | this.timeout(30000) | 159 | this.timeout(30000) |
115 | 160 | ||
@@ -133,6 +178,12 @@ describe('Test video captions', function () { | |||
133 | } | 178 | } |
134 | }) | 179 | }) |
135 | 180 | ||
181 | it('Should remove the video, and thus all video captions', async function () { | ||
182 | await removeVideo(servers[0].url, servers[0].accessToken, videoUUID) | ||
183 | |||
184 | await checkVideoFilesWereRemoved(videoUUID, 1) | ||
185 | }) | ||
186 | |||
136 | after(async function () { | 187 | after(async function () { |
137 | killallServers(servers) | 188 | killallServers(servers) |
138 | }) | 189 | }) |
diff --git a/server/tests/fixtures/subtitle-bad.txt b/server/tests/fixtures/subtitle-bad.txt new file mode 100644 index 000000000..a2a30ae47 --- /dev/null +++ b/server/tests/fixtures/subtitle-bad.txt | |||
@@ -0,0 +1,11 @@ | |||
1 | 1 | ||
2 | 00:00:01,600 --> 00:00:04,200 | ||
3 | English (US) | ||
4 | |||
5 | 2 | ||
6 | 00:00:05,900 --> 00:00:07,999 | ||
7 | This is a subtitle in American English | ||
8 | |||
9 | 3 | ||
10 | 00:00:10,000 --> 00:00:14,000 | ||
11 | Adding subtitles is very easy to do \ No newline at end of file | ||
diff --git a/server/tests/fixtures/subtitle-good.srt b/server/tests/fixtures/subtitle-good.srt new file mode 100644 index 000000000..a2a30ae47 --- /dev/null +++ b/server/tests/fixtures/subtitle-good.srt | |||
@@ -0,0 +1,11 @@ | |||
1 | 1 | ||
2 | 00:00:01,600 --> 00:00:04,200 | ||
3 | English (US) | ||
4 | |||
5 | 2 | ||
6 | 00:00:05,900 --> 00:00:07,999 | ||
7 | This is a subtitle in American English | ||
8 | |||
9 | 3 | ||
10 | 00:00:10,000 --> 00:00:14,000 | ||
11 | Adding subtitles is very easy to do \ No newline at end of file | ||
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index 4f7ce6d6b..74bf7354e 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts | |||
@@ -301,7 +301,7 @@ function searchVideoWithSort (url: string, search: string, sort: string) { | |||
301 | async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) { | 301 | async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) { |
302 | const testDirectory = 'test' + serverNumber | 302 | const testDirectory = 'test' + serverNumber |
303 | 303 | ||
304 | for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews' ]) { | 304 | for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]) { |
305 | const directoryPath = join(root(), testDirectory, directory) | 305 | const directoryPath = join(root(), testDirectory, directory) |
306 | 306 | ||
307 | const directoryExists = existsSync(directoryPath) | 307 | const directoryExists = existsSync(directoryPath) |