aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-07-12 19:02:00 +0200
committerChocobozzz <me@florianbigard.com>2018-07-16 11:50:08 +0200
commit40e87e9ecc54e3513fb586928330a7855eb192c6 (patch)
treeaf1111ecba85f9cd8286811ff332a67cf21be2f6 /server/controllers
parentd4557fd3ecc8d4ed4fb0e5c868929bc36c959ed2 (diff)
downloadPeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.gz
PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.zst
PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.zip
Implement captions/subtitles
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/activitypub/client.ts5
-rw-r--r--server/controllers/api/config.ts14
-rw-r--r--server/controllers/api/videos/captions.ts100
-rw-r--r--server/controllers/api/videos/index.ts2
-rw-r--r--server/controllers/client.ts2
-rw-r--r--server/controllers/feeds.ts2
-rw-r--r--server/controllers/services.ts4
-rw-r--r--server/controllers/static.ts21
8 files changed, 143 insertions, 7 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index ea8e25f68..3e6361906 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -25,6 +25,8 @@ import {
25 getVideoLikesActivityPubUrl, 25 getVideoLikesActivityPubUrl,
26 getVideoSharesActivityPubUrl 26 getVideoSharesActivityPubUrl
27} from '../../lib/activitypub' 27} from '../../lib/activitypub'
28import { VideoCaption } from '../../../shared/models/videos/video-caption.model'
29import { VideoCaptionModel } from '../../models/video/video-caption'
28 30
29const activityPubClientRouter = express.Router() 31const activityPubClientRouter = express.Router()
30 32
@@ -123,6 +125,9 @@ async function accountFollowingController (req: express.Request, res: express.Re
123async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { 125async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) {
124 const video: VideoModel = res.locals.video 126 const video: VideoModel = res.locals.video
125 127
128 // We need captions to render AP object
129 video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id)
130
126 const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) 131 const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC)
127 const videoObject = audiencify(video.toActivityPubObject(), audience) 132 const videoObject = audiencify(video.toActivityPubObject(), audience)
128 133
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
80 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME 80 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
81 } 81 }
82 }, 82 },
83 videoCaption: {
84 file: {
85 size: {
86 max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
87 },
88 extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
89 }
90 },
83 user: { 91 user: {
84 videoQuota: CONFIG.USER.VIDEO_QUOTA 92 videoQuota: CONFIG.USER.VIDEO_QUOTA
85 } 93 }
@@ -122,12 +130,13 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
122 130
123 // Force number conversion 131 // Force number conversion
124 toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10) 132 toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10)
133 toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10)
125 toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10) 134 toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10)
126 toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10) 135 toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10)
127 toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10) 136 toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10)
128 137
129 // camelCase to snake_case key 138 // camelCase to snake_case key
130 const toUpdateJSON = omit(toUpdate, 'user.videoQuota', 'instance.defaultClientRoute', 'instance.shortDescription') 139 const toUpdateJSON = omit(toUpdate, 'user.videoQuota', 'instance.defaultClientRoute', 'instance.shortDescription', 'cache.videoCaptions')
131 toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota 140 toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
132 toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute 141 toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute
133 toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription 142 toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription
@@ -172,6 +181,9 @@ function customConfig (): CustomConfig {
172 cache: { 181 cache: {
173 previews: { 182 previews: {
174 size: CONFIG.CACHE.PREVIEWS.SIZE 183 size: CONFIG.CACHE.PREVIEWS.SIZE
184 },
185 captions: {
186 size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
175 } 187 }
176 }, 188 },
177 signup: { 189 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 @@
1import * as express from 'express'
2import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
3import {
4 addVideoCaptionValidator,
5 deleteVideoCaptionValidator,
6 listVideoCaptionsValidator
7} from '../../../middlewares/validators/video-captions'
8import { createReqFiles } from '../../../helpers/express-utils'
9import { CONFIG, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers'
10import { getFormattedObjects } from '../../../helpers/utils'
11import { VideoCaptionModel } from '../../../models/video/video-caption'
12import { renamePromise } from '../../../helpers/core-utils'
13import { join } from 'path'
14import { VideoModel } from '../../../models/video/video'
15import { logger } from '../../../helpers/logger'
16import { federateVideoIfNeeded } from '../../../lib/activitypub'
17
18const reqVideoCaptionAdd = createReqFiles(
19 [ 'captionfile' ],
20 VIDEO_CAPTIONS_MIMETYPE_EXT,
21 {
22 captionfile: CONFIG.STORAGE.CAPTIONS_DIR
23 }
24)
25
26const videoCaptionsRouter = express.Router()
27
28videoCaptionsRouter.get('/:videoId/captions',
29 asyncMiddleware(listVideoCaptionsValidator),
30 asyncMiddleware(listVideoCaptions)
31)
32videoCaptionsRouter.put('/:videoId/captions/:captionLanguage',
33 authenticate,
34 reqVideoCaptionAdd,
35 asyncMiddleware(addVideoCaptionValidator),
36 asyncRetryTransactionMiddleware(addVideoCaption)
37)
38videoCaptionsRouter.delete('/:videoId/captions/:captionLanguage',
39 authenticate,
40 asyncMiddleware(deleteVideoCaptionValidator),
41 asyncRetryTransactionMiddleware(deleteVideoCaption)
42)
43
44// ---------------------------------------------------------------------------
45
46export {
47 videoCaptionsRouter
48}
49
50// ---------------------------------------------------------------------------
51
52async function listVideoCaptions (req: express.Request, res: express.Response) {
53 const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id)
54
55 return res.json(getFormattedObjects(data, data.length))
56}
57
58async function addVideoCaption (req: express.Request, res: express.Response) {
59 const videoCaptionPhysicalFile = req.files['captionfile'][0]
60 const video = res.locals.video as VideoModel
61
62 const videoCaption = new VideoCaptionModel({
63 videoId: video.id,
64 language: req.params.captionLanguage
65 })
66 videoCaption.Video = video
67
68 // Move physical file
69 const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR
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
76 await sequelizeTypescript.transaction(async t => {
77 await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, t)
78
79 // Update video update
80 await federateVideoIfNeeded(video, false, t)
81 })
82
83 return res.status(204).end()
84}
85
86async function deleteVideoCaption (req: express.Request, res: express.Response) {
87 const video = res.locals.video as VideoModel
88 const videoCaption = res.locals.videoCaption as VideoCaptionModel
89
90 await sequelizeTypescript.transaction(async t => {
91 await videoCaption.destroy({ transaction: t })
92
93 // Send video update
94 await federateVideoIfNeeded(video, false, t)
95 })
96
97 logger.info('Video caption %s of video %s deleted.', videoCaption.language, video.uuid)
98
99 return res.type('json').status(204).end()
100}
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'
53import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' 53import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type'
54import { createReqFiles, isNSFWHidden } from '../../../helpers/express-utils' 54import { createReqFiles, isNSFWHidden } from '../../../helpers/express-utils'
55import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 55import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
56import { videoCaptionsRouter } from './captions'
56 57
57const videosRouter = express.Router() 58const videosRouter = express.Router()
58 59
@@ -78,6 +79,7 @@ videosRouter.use('/', abuseVideoRouter)
78videosRouter.use('/', blacklistRouter) 79videosRouter.use('/', blacklistRouter)
79videosRouter.use('/', rateVideoRouter) 80videosRouter.use('/', rateVideoRouter)
80videosRouter.use('/', videoCommentRouter) 81videosRouter.use('/', videoCommentRouter)
82videosRouter.use('/', videoCaptionsRouter)
81 83
82videosRouter.get('/categories', listVideoCategories) 84videosRouter.get('/categories', listVideoCategories)
83videosRouter.get('/licences', listVideoLicences) 85videosRouter.get('/licences', listVideoLicences)
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 5413f61e8..bfdf35021 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -118,7 +118,7 @@ function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
118 118
119 const videoNameEscaped = escapeHTML(video.name) 119 const videoNameEscaped = escapeHTML(video.name)
120 const videoDescriptionEscaped = escapeHTML(video.description) 120 const videoDescriptionEscaped = escapeHTML(video.description)
121 const embedUrl = CONFIG.WEBSERVER.URL + video.getEmbedPath() 121 const embedUrl = CONFIG.WEBSERVER.URL + video.getEmbedStaticPath()
122 122
123 const openGraphMetaTags = { 123 const openGraphMetaTags = {
124 'og:type': 'video', 124 'og:type': 'video',
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index 1773fc71e..ff6b423d9 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -129,7 +129,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response, n
129 torrent: torrents, 129 torrent: torrents,
130 thumbnail: [ 130 thumbnail: [
131 { 131 {
132 url: CONFIG.WEBSERVER.URL + video.getThumbnailPath(), 132 url: CONFIG.WEBSERVER.URL + video.getThumbnailStaticPath(),
133 height: THUMBNAILS_SIZE.height, 133 height: THUMBNAILS_SIZE.height,
134 width: THUMBNAILS_SIZE.width 134 width: THUMBNAILS_SIZE.width
135 } 135 }
diff --git a/server/controllers/services.ts b/server/controllers/services.ts
index bd4404b62..352d0b19a 100644
--- a/server/controllers/services.ts
+++ b/server/controllers/services.ts
@@ -29,8 +29,8 @@ function generateOEmbed (req: express.Request, res: express.Response, next: expr
29 const maxHeight = parseInt(req.query.maxheight, 10) 29 const maxHeight = parseInt(req.query.maxheight, 10)
30 const maxWidth = parseInt(req.query.maxwidth, 10) 30 const maxWidth = parseInt(req.query.maxwidth, 10)
31 31
32 const embedUrl = webserverUrl + video.getEmbedPath() 32 const embedUrl = webserverUrl + video.getEmbedStaticPath()
33 let thumbnailUrl = webserverUrl + video.getPreviewPath() 33 let thumbnailUrl = webserverUrl + video.getPreviewStaticPath()
34 let embedWidth = EMBED_SIZE.width 34 let embedWidth = EMBED_SIZE.width
35 let embedHeight = EMBED_SIZE.height 35 let embedHeight = EMBED_SIZE.height
36 36
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 139ba67cc..679999859 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -4,6 +4,7 @@ import { CONFIG, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS } from '../
4import { VideosPreviewCache } from '../lib/cache' 4import { VideosPreviewCache } from '../lib/cache'
5import { asyncMiddleware, videosGetValidator } from '../middlewares' 5import { asyncMiddleware, videosGetValidator } from '../middlewares'
6import { VideoModel } from '../models/video/video' 6import { VideoModel } from '../models/video/video'
7import { VideosCaptionCache } from '../lib/cache/videos-caption-cache'
7 8
8const staticRouter = express.Router() 9const staticRouter = express.Router()
9 10
@@ -49,12 +50,18 @@ staticRouter.use(
49 express.static(avatarsPhysicalPath, { maxAge: STATIC_MAX_AGE }) 50 express.static(avatarsPhysicalPath, { maxAge: STATIC_MAX_AGE })
50) 51)
51 52
52// Video previews path for express 53// We don't have video previews, fetch them from the origin instance
53staticRouter.use( 54staticRouter.use(
54 STATIC_PATHS.PREVIEWS + ':uuid.jpg', 55 STATIC_PATHS.PREVIEWS + ':uuid.jpg',
55 asyncMiddleware(getPreview) 56 asyncMiddleware(getPreview)
56) 57)
57 58
59// We don't have video captions, fetch them from the origin instance
60staticRouter.use(
61 STATIC_PATHS.VIDEO_CAPTIONS + ':videoId-:captionLanguage([a-z]+).vtt',
62 asyncMiddleware(getVideoCaption)
63)
64
58// robots.txt service 65// robots.txt service
59staticRouter.get('/robots.txt', (req: express.Request, res: express.Response) => { 66staticRouter.get('/robots.txt', (req: express.Request, res: express.Response) => {
60 res.type('text/plain') 67 res.type('text/plain')
@@ -70,7 +77,17 @@ export {
70// --------------------------------------------------------------------------- 77// ---------------------------------------------------------------------------
71 78
72async function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) { 79async function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) {
73 const path = await VideosPreviewCache.Instance.getPreviewPath(req.params.uuid) 80 const path = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
81 if (!path) return res.sendStatus(404)
82
83 return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
84}
85
86async function getVideoCaption (req: express.Request, res: express.Response) {
87 const path = await VideosCaptionCache.Instance.getFilePath({
88 videoId: req.params.videoId,
89 language: req.params.captionLanguage
90 })
74 if (!path) return res.sendStatus(404) 91 if (!path) return res.sendStatus(404)
75 92
76 return res.sendFile(path, { maxAge: STATIC_MAX_AGE }) 93 return res.sendFile(path, { maxAge: STATIC_MAX_AGE })