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