diff options
author | Chocobozzz <me@florianbigard.com> | 2018-07-12 19:02:00 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-07-16 11:50:08 +0200 |
commit | 40e87e9ecc54e3513fb586928330a7855eb192c6 (patch) | |
tree | af1111ecba85f9cd8286811ff332a67cf21be2f6 /server/lib/cache | |
parent | d4557fd3ecc8d4ed4fb0e5c868929bc36c959ed2 (diff) | |
download | PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.gz PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.zst PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.zip |
Implement captions/subtitles
Diffstat (limited to 'server/lib/cache')
-rw-r--r-- | server/lib/cache/abstract-video-static-file-cache.ts | 54 | ||||
-rw-r--r-- | server/lib/cache/videos-caption-cache.ts | 53 | ||||
-rw-r--r-- | server/lib/cache/videos-preview-cache.ts | 60 |
3 files changed, 121 insertions, 46 deletions
diff --git a/server/lib/cache/abstract-video-static-file-cache.ts b/server/lib/cache/abstract-video-static-file-cache.ts new file mode 100644 index 000000000..7eeeb6b3a --- /dev/null +++ b/server/lib/cache/abstract-video-static-file-cache.ts | |||
@@ -0,0 +1,54 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | ||
2 | import { createWriteStream } from 'fs' | ||
3 | import { join } from 'path' | ||
4 | import { unlinkPromise } from '../../helpers/core-utils' | ||
5 | import { logger } from '../../helpers/logger' | ||
6 | import { CACHE, CONFIG } from '../../initializers' | ||
7 | import { VideoModel } from '../../models/video/video' | ||
8 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
9 | import { VideoCaptionModel } from '../../models/video/video-caption' | ||
10 | |||
11 | export abstract class AbstractVideoStaticFileCache <T> { | ||
12 | |||
13 | protected lru | ||
14 | |||
15 | abstract getFilePath (params: T): Promise<string> | ||
16 | |||
17 | // Load and save the remote file, then return the local path from filesystem | ||
18 | protected abstract loadRemoteFile (key: string): Promise<string> | ||
19 | |||
20 | init (max: number) { | ||
21 | this.lru = new AsyncLRU({ | ||
22 | max, | ||
23 | load: (key, cb) => { | ||
24 | this.loadRemoteFile(key) | ||
25 | .then(res => cb(null, res)) | ||
26 | .catch(err => cb(err)) | ||
27 | } | ||
28 | }) | ||
29 | |||
30 | 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)) | ||
32 | }) | ||
33 | } | ||
34 | |||
35 | protected loadFromLRU (key: string) { | ||
36 | return new Promise<string>((res, rej) => { | ||
37 | this.lru.get(key, (err, value) => { | ||
38 | err ? rej(err) : res(value) | ||
39 | }) | ||
40 | }) | ||
41 | } | ||
42 | |||
43 | protected saveRemoteVideoFileAndReturnPath (video: VideoModel, remoteStaticPath: string, destPath: string) { | ||
44 | return new Promise<string>((res, rej) => { | ||
45 | const req = fetchRemoteVideoStaticFile(video, remoteStaticPath, rej) | ||
46 | |||
47 | const stream = createWriteStream(destPath) | ||
48 | |||
49 | req.pipe(stream) | ||
50 | .on('error', (err) => rej(err)) | ||
51 | .on('finish', () => res(destPath)) | ||
52 | }) | ||
53 | } | ||
54 | } | ||
diff --git a/server/lib/cache/videos-caption-cache.ts b/server/lib/cache/videos-caption-cache.ts new file mode 100644 index 000000000..1336610b2 --- /dev/null +++ b/server/lib/cache/videos-caption-cache.ts | |||
@@ -0,0 +1,53 @@ | |||
1 | import { join } from 'path' | ||
2 | import { CACHE, CONFIG } from '../../initializers' | ||
3 | import { VideoModel } from '../../models/video/video' | ||
4 | import { VideoCaptionModel } from '../../models/video/video-caption' | ||
5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | ||
6 | |||
7 | type GetPathParam = { videoId: string, language: string } | ||
8 | |||
9 | class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | ||
10 | |||
11 | private static readonly KEY_DELIMITER = '%' | ||
12 | private static instance: VideosCaptionCache | ||
13 | |||
14 | private constructor () { | ||
15 | super() | ||
16 | } | ||
17 | |||
18 | static get Instance () { | ||
19 | return this.instance || (this.instance = new this()) | ||
20 | } | ||
21 | |||
22 | async getFilePath (params: GetPathParam) { | ||
23 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) | ||
24 | if (!videoCaption) return undefined | ||
25 | |||
26 | if (videoCaption.isOwned()) return join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) | ||
27 | |||
28 | const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language | ||
29 | return this.loadFromLRU(key) | ||
30 | } | ||
31 | |||
32 | protected async loadRemoteFile (key: string) { | ||
33 | const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) | ||
34 | |||
35 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) | ||
36 | if (!videoCaption) return undefined | ||
37 | |||
38 | if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.') | ||
39 | |||
40 | // Used to fetch the path | ||
41 | const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(videoId) | ||
42 | if (!video) return undefined | ||
43 | |||
44 | const remoteStaticPath = videoCaption.getCaptionStaticPath() | ||
45 | const destPath = join(CACHE.DIRECTORIES.VIDEO_CAPTIONS, videoCaption.getCaptionName()) | ||
46 | |||
47 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) | ||
48 | } | ||
49 | } | ||
50 | |||
51 | export { | ||
52 | VideosCaptionCache | ||
53 | } | ||
diff --git a/server/lib/cache/videos-preview-cache.ts b/server/lib/cache/videos-preview-cache.ts index d09d55e11..1c0e7ed9d 100644 --- a/server/lib/cache/videos-preview-cache.ts +++ b/server/lib/cache/videos-preview-cache.ts | |||
@@ -1,71 +1,39 @@ | |||
1 | import * as asyncLRU from 'async-lru' | ||
2 | import { createWriteStream } from 'fs' | ||
3 | import { join } from 'path' | 1 | import { join } from 'path' |
4 | import { unlinkPromise } from '../../helpers/core-utils' | 2 | import { CACHE, CONFIG, STATIC_PATHS } from '../../initializers' |
5 | import { logger } from '../../helpers/logger' | ||
6 | import { CACHE, CONFIG } from '../../initializers' | ||
7 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
8 | import { fetchRemoteVideoPreview } from '../activitypub' | 4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
9 | 5 | ||
10 | class VideosPreviewCache { | 6 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { |
11 | 7 | ||
12 | private static instance: VideosPreviewCache | 8 | private static instance: VideosPreviewCache |
13 | 9 | ||
14 | private lru | 10 | private constructor () { |
15 | 11 | super() | |
16 | private constructor () { } | 12 | } |
17 | 13 | ||
18 | static get Instance () { | 14 | static get Instance () { |
19 | return this.instance || (this.instance = new this()) | 15 | return this.instance || (this.instance = new this()) |
20 | } | 16 | } |
21 | 17 | ||
22 | init (max: number) { | 18 | async getFilePath (videoUUID: string) { |
23 | this.lru = new asyncLRU({ | 19 | const video = await VideoModel.loadByUUID(videoUUID) |
24 | max, | ||
25 | load: (key, cb) => { | ||
26 | this.loadPreviews(key) | ||
27 | .then(res => cb(null, res)) | ||
28 | .catch(err => cb(err)) | ||
29 | } | ||
30 | }) | ||
31 | |||
32 | this.lru.on('evict', (obj: { key: string, value: string }) => { | ||
33 | unlinkPromise(obj.value).then(() => logger.debug('%s evicted from VideosPreviewCache', obj.value)) | ||
34 | }) | ||
35 | } | ||
36 | |||
37 | async getPreviewPath (key: string) { | ||
38 | const video = await VideoModel.loadByUUID(key) | ||
39 | if (!video) return undefined | 20 | if (!video) return undefined |
40 | 21 | ||
41 | if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()) | 22 | if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()) |
42 | 23 | ||
43 | return new Promise<string>((res, rej) => { | 24 | return this.loadFromLRU(videoUUID) |
44 | this.lru.get(key, (err, value) => { | ||
45 | err ? rej(err) : res(value) | ||
46 | }) | ||
47 | }) | ||
48 | } | 25 | } |
49 | 26 | ||
50 | private async loadPreviews (key: string) { | 27 | protected async loadRemoteFile (key: string) { |
51 | const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(key) | 28 | const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(key) |
52 | if (!video) return undefined | 29 | if (!video) return undefined |
53 | 30 | ||
54 | if (video.isOwned()) throw new Error('Cannot load preview of owned video.') | 31 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') |
55 | |||
56 | return this.saveRemotePreviewAndReturnPath(video) | ||
57 | } | ||
58 | 32 | ||
59 | private saveRemotePreviewAndReturnPath (video: VideoModel) { | 33 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) |
60 | return new Promise<string>((res, rej) => { | 34 | const destPath = join(CACHE.DIRECTORIES.PREVIEWS, video.getPreviewName()) |
61 | const req = fetchRemoteVideoPreview(video, rej) | ||
62 | const path = join(CACHE.DIRECTORIES.PREVIEWS, video.getPreviewName()) | ||
63 | const stream = createWriteStream(path) | ||
64 | 35 | ||
65 | req.pipe(stream) | 36 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) |
66 | .on('error', (err) => rej(err)) | ||
67 | .on('finish', () => res(path)) | ||
68 | }) | ||
69 | } | 37 | } |
70 | } | 38 | } |
71 | 39 | ||