]>
Commit | Line | Data |
---|---|---|
62549e6c C |
1 | import { move, pathExists, readdir, remove } from 'fs-extra' |
2 | import { dirname, join } from 'path' | |
3 | import { CONFIG } from '@server/initializers/config' | |
4 | import { isVideoFileExtnameValid } from '../custom-validators/videos' | |
5 | import { logger, loggerTagsFactory } from '../logger' | |
6 | import { generateVideoImportTmpPath } from '../utils' | |
7 | import { YoutubeDLCLI } from './youtube-dl-cli' | |
8 | import { YoutubeDLInfo, YoutubeDLInfoBuilder } from './youtube-dl-info-builder' | |
9 | ||
10 | const lTags = loggerTagsFactory('youtube-dl') | |
11 | ||
12 | export type YoutubeDLSubs = { | |
13 | language: string | |
14 | filename: string | |
15 | path: string | |
16 | }[] | |
17 | ||
18 | const processOptions = { | |
8296984d | 19 | maxBuffer: 1024 * 1024 * 30 // 30MB |
62549e6c C |
20 | } |
21 | ||
22 | class YoutubeDLWrapper { | |
23 | ||
24 | constructor (private readonly url: string = '', private readonly enabledResolutions: number[] = []) { | |
25 | ||
26 | } | |
27 | ||
28 | async getInfoForDownload (youtubeDLArgs: string[] = []): Promise<YoutubeDLInfo> { | |
29 | const youtubeDL = await YoutubeDLCLI.safeGet() | |
30 | ||
31 | const info = await youtubeDL.getInfo({ | |
32 | url: this.url, | |
33 | format: YoutubeDLCLI.getYoutubeDLVideoFormat(this.enabledResolutions), | |
34 | additionalYoutubeDLArgs: youtubeDLArgs, | |
35 | processOptions | |
36 | }) | |
37 | ||
38 | if (info.is_live === true) throw new Error('Cannot download a live streaming.') | |
39 | ||
40 | const infoBuilder = new YoutubeDLInfoBuilder(info) | |
41 | ||
42 | return infoBuilder.getInfo() | |
43 | } | |
44 | ||
45 | async getSubtitles (): Promise<YoutubeDLSubs> { | |
46 | const cwd = CONFIG.STORAGE.TMP_DIR | |
47 | ||
48 | const youtubeDL = await YoutubeDLCLI.safeGet() | |
49 | ||
50 | const files = await youtubeDL.getSubs({ url: this.url, format: 'vtt', processOptions: { cwd } }) | |
51 | if (!files) return [] | |
52 | ||
53 | logger.debug('Get subtitles from youtube dl.', { url: this.url, files, ...lTags() }) | |
54 | ||
55 | const subtitles = files.reduce((acc, filename) => { | |
56 | const matched = filename.match(/\.([a-z]{2})(-[a-z]+)?\.(vtt|ttml)/i) | |
57 | if (!matched || !matched[1]) return acc | |
58 | ||
59 | return [ | |
60 | ...acc, | |
61 | { | |
62 | language: matched[1], | |
63 | path: join(cwd, filename), | |
64 | filename | |
65 | } | |
66 | ] | |
67 | }, []) | |
68 | ||
69 | return subtitles | |
70 | } | |
71 | ||
72 | async downloadVideo (fileExt: string, timeout: number): Promise<string> { | |
73 | // Leave empty the extension, youtube-dl will add it | |
74 | const pathWithoutExtension = generateVideoImportTmpPath(this.url, '') | |
75 | ||
62549e6c C |
76 | logger.info('Importing youtubeDL video %s to %s', this.url, pathWithoutExtension, lTags()) |
77 | ||
78 | const youtubeDL = await YoutubeDLCLI.safeGet() | |
79 | ||
7630e1c8 C |
80 | try { |
81 | await youtubeDL.download({ | |
82 | url: this.url, | |
83 | format: YoutubeDLCLI.getYoutubeDLVideoFormat(this.enabledResolutions), | |
84 | output: pathWithoutExtension, | |
85 | timeout, | |
86 | processOptions | |
62549e6c C |
87 | }) |
88 | ||
7630e1c8 C |
89 | // If youtube-dl did not guess an extension for our file, just use .mp4 as default |
90 | if (await pathExists(pathWithoutExtension)) { | |
91 | await move(pathWithoutExtension, pathWithoutExtension + '.mp4') | |
92 | } | |
d7ce63d3 | 93 | |
7630e1c8 C |
94 | return this.guessVideoPathWithExtension(pathWithoutExtension, fileExt) |
95 | } catch (err) { | |
96 | this.guessVideoPathWithExtension(pathWithoutExtension, fileExt) | |
97 | .then(path => { | |
98 | logger.debug('Error in youtube-dl import, deleting file %s.', path, { err, ...lTags() }) | |
62549e6c | 99 | |
7630e1c8 C |
100 | return remove(path) |
101 | }) | |
102 | .catch(innerErr => logger.error('Cannot remove file in youtubeDL timeout.', { innerErr, ...lTags() })) | |
103 | ||
104 | throw err | |
105 | } | |
62549e6c C |
106 | } |
107 | ||
108 | private async guessVideoPathWithExtension (tmpPath: string, sourceExt: string) { | |
109 | if (!isVideoFileExtnameValid(sourceExt)) { | |
110 | throw new Error('Invalid video extension ' + sourceExt) | |
111 | } | |
112 | ||
113 | const extensions = [ sourceExt, '.mp4', '.mkv', '.webm' ] | |
114 | ||
115 | for (const extension of extensions) { | |
116 | const path = tmpPath + extension | |
117 | ||
118 | if (await pathExists(path)) return path | |
119 | } | |
120 | ||
121 | const directoryContent = await readdir(dirname(tmpPath)) | |
122 | ||
123 | throw new Error(`Cannot guess path of ${tmpPath}. Directory content: ${directoryContent.join(', ')}`) | |
124 | } | |
125 | } | |
126 | ||
127 | // --------------------------------------------------------------------------- | |
128 | ||
129 | export { | |
130 | YoutubeDLWrapper | |
131 | } |