From 40e87e9ecc54e3513fb586928330a7855eb192c6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 12 Jul 2018 19:02:00 +0200 Subject: Implement captions/subtitles --- server/controllers/api/config.ts | 14 ++++- server/controllers/api/videos/captions.ts | 100 ++++++++++++++++++++++++++++++ server/controllers/api/videos/index.ts | 2 + 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 server/controllers/api/videos/captions.ts (limited to 'server/controllers/api') diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index f678e3c4a..3788975a9 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -80,6 +80,14 @@ async function getConfig (req: express.Request, res: express.Response, next: exp extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME } }, + videoCaption: { + file: { + size: { + max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max + }, + extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME + } + }, user: { videoQuota: CONFIG.USER.VIDEO_QUOTA } @@ -122,12 +130,13 @@ async function updateCustomConfig (req: express.Request, res: express.Response, // Force number conversion toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10) + toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10) toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10) toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10) toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10) // camelCase to snake_case key - const toUpdateJSON = omit(toUpdate, 'user.videoQuota', 'instance.defaultClientRoute', 'instance.shortDescription') + const toUpdateJSON = omit(toUpdate, 'user.videoQuota', 'instance.defaultClientRoute', 'instance.shortDescription', 'cache.videoCaptions') toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription @@ -172,6 +181,9 @@ function customConfig (): CustomConfig { cache: { previews: { size: CONFIG.CACHE.PREVIEWS.SIZE + }, + captions: { + size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE } }, signup: { diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts new file mode 100644 index 000000000..05412a17f --- /dev/null +++ b/server/controllers/api/videos/captions.ts @@ -0,0 +1,100 @@ +import * as express from 'express' +import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' +import { + addVideoCaptionValidator, + deleteVideoCaptionValidator, + listVideoCaptionsValidator +} from '../../../middlewares/validators/video-captions' +import { createReqFiles } from '../../../helpers/express-utils' +import { CONFIG, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers' +import { getFormattedObjects } from '../../../helpers/utils' +import { VideoCaptionModel } from '../../../models/video/video-caption' +import { renamePromise } from '../../../helpers/core-utils' +import { join } from 'path' +import { VideoModel } from '../../../models/video/video' +import { logger } from '../../../helpers/logger' +import { federateVideoIfNeeded } from '../../../lib/activitypub' + +const reqVideoCaptionAdd = createReqFiles( + [ 'captionfile' ], + VIDEO_CAPTIONS_MIMETYPE_EXT, + { + captionfile: CONFIG.STORAGE.CAPTIONS_DIR + } +) + +const videoCaptionsRouter = express.Router() + +videoCaptionsRouter.get('/:videoId/captions', + asyncMiddleware(listVideoCaptionsValidator), + asyncMiddleware(listVideoCaptions) +) +videoCaptionsRouter.put('/:videoId/captions/:captionLanguage', + authenticate, + reqVideoCaptionAdd, + asyncMiddleware(addVideoCaptionValidator), + asyncRetryTransactionMiddleware(addVideoCaption) +) +videoCaptionsRouter.delete('/:videoId/captions/:captionLanguage', + authenticate, + asyncMiddleware(deleteVideoCaptionValidator), + asyncRetryTransactionMiddleware(deleteVideoCaption) +) + +// --------------------------------------------------------------------------- + +export { + videoCaptionsRouter +} + +// --------------------------------------------------------------------------- + +async function listVideoCaptions (req: express.Request, res: express.Response) { + const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id) + + return res.json(getFormattedObjects(data, data.length)) +} + +async function addVideoCaption (req: express.Request, res: express.Response) { + const videoCaptionPhysicalFile = req.files['captionfile'][0] + const video = res.locals.video as VideoModel + + const videoCaption = new VideoCaptionModel({ + videoId: video.id, + language: req.params.captionLanguage + }) + videoCaption.Video = video + + // Move physical file + const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR + const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) + await renamePromise(videoCaptionPhysicalFile.path, destination) + // This is important in case if there is another attempt in the retry process + videoCaptionPhysicalFile.filename = videoCaption.getCaptionName() + videoCaptionPhysicalFile.path = destination + + await sequelizeTypescript.transaction(async t => { + await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, t) + + // Update video update + await federateVideoIfNeeded(video, false, t) + }) + + return res.status(204).end() +} + +async function deleteVideoCaption (req: express.Request, res: express.Response) { + const video = res.locals.video as VideoModel + const videoCaption = res.locals.videoCaption as VideoCaptionModel + + await sequelizeTypescript.transaction(async t => { + await videoCaption.destroy({ transaction: t }) + + // Send video update + await federateVideoIfNeeded(video, false, t) + }) + + logger.info('Video caption %s of video %s deleted.', videoCaption.language, video.uuid) + + return res.type('json').status(204).end() +} diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 8c93ae89c..bbb5b8b4c 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -53,6 +53,7 @@ import { VideoFilter } from '../../../../shared/models/videos/video-query.type' import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' import { createReqFiles, isNSFWHidden } from '../../../helpers/express-utils' import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' +import { videoCaptionsRouter } from './captions' const videosRouter = express.Router() @@ -78,6 +79,7 @@ videosRouter.use('/', abuseVideoRouter) videosRouter.use('/', blacklistRouter) videosRouter.use('/', rateVideoRouter) videosRouter.use('/', videoCommentRouter) +videosRouter.use('/', videoCaptionsRouter) videosRouter.get('/categories', listVideoCategories) videosRouter.get('/licences', listVideoLicences) -- cgit v1.2.3