Fix (again) youtube import

This commit is contained in:
Chocobozzz 2021-01-19 16:28:55 +01:00
parent d487a997c8
commit 805b8619c1
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
5 changed files with 53 additions and 53 deletions

View File

@ -220,8 +220,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
videoImportId: videoImport.id, videoImportId: videoImport.id,
generateThumbnail: !thumbnailModel, generateThumbnail: !thumbnailModel,
generatePreview: !previewModel, generatePreview: !previewModel,
fileExt: `.${youtubeDLInfo.ext || 'mp4'}`, fileExt: `.${youtubeDLInfo.ext || 'mp4'}`
mergeExt: youtubeDLInfo.mergeExt ? `.${youtubeDLInfo.mergeExt}` : ''
} }
await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload }) await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload })

View File

@ -1,11 +1,10 @@
import { remove } from 'fs-extra'
import { Instance as ParseTorrent } from 'parse-torrent'
import { join } from 'path'
import { ResultList } from '../../shared' import { ResultList } from '../../shared'
import { CONFIG } from '../initializers/config'
import { execPromise, execPromise2, randomBytesPromise, sha256 } from './core-utils' import { execPromise, execPromise2, randomBytesPromise, sha256 } from './core-utils'
import { logger } from './logger' import { logger } from './logger'
import { join } from 'path'
import { Instance as ParseTorrent } from 'parse-torrent'
import { remove } from 'fs-extra'
import { CONFIG } from '../initializers/config'
import { isVideoFileExtnameValid } from './custom-validators/videos'
function deleteFileAsync (path: string) { function deleteFileAsync (path: string) {
remove(path) remove(path)
@ -31,16 +30,11 @@ function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects:
} as ResultList<V> } as ResultList<V>
} }
function generateVideoImportTmpPath (target: string | ParseTorrent, extensionArg?: string) { function generateVideoImportTmpPath (target: string | ParseTorrent, extension = '.mp4') {
const id = typeof target === 'string' const id = typeof target === 'string'
? target ? target
: target.infoHash : target.infoHash
let extension = '.mp4'
if (extensionArg && isVideoFileExtnameValid(extensionArg)) {
extension = extensionArg
}
const hash = sha256(id) const hash = sha256(id)
return join(CONFIG.STORAGE.TMP_DIR, `${hash}-import${extension}`) return join(CONFIG.STORAGE.TMP_DIR, `${hash}-import${extension}`)
} }

View File

@ -1,15 +1,16 @@
import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants'
import { logger } from './logger'
import { generateVideoImportTmpPath } from './utils'
import { getEnabledResolutions } from '../lib/video-transcoding'
import { join } from 'path'
import { peertubeTruncate, root } from './core-utils'
import { ensureDir, remove, writeFile } from 'fs-extra'
import * as request from 'request'
import { createWriteStream } from 'fs' import { createWriteStream } from 'fs'
import { ensureDir, pathExists, remove, writeFile } from 'fs-extra'
import { join } from 'path'
import * as request from 'request'
import { CONFIG } from '@server/initializers/config' import { CONFIG } from '@server/initializers/config'
import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
import { VideoResolution } from '../../shared/models/videos' import { VideoResolution } from '../../shared/models/videos'
import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants'
import { getEnabledResolutions } from '../lib/video-transcoding'
import { peertubeTruncate, root } from './core-utils'
import { isVideoFileExtnameValid } from './custom-validators/videos'
import { logger } from './logger'
import { generateVideoImportTmpPath } from './utils'
export type YoutubeDLInfo = { export type YoutubeDLInfo = {
name?: string name?: string
@ -21,7 +22,6 @@ export type YoutubeDLInfo = {
tags?: string[] tags?: string[]
thumbnailUrl?: string thumbnailUrl?: string
ext?: string ext?: string
mergeExt?: string
originallyPublishedAt?: Date originallyPublishedAt?: Date
} }
@ -51,16 +51,6 @@ function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo>
youtubeDL.getInfo(url, args, processOptions, (err, info) => { youtubeDL.getInfo(url, args, processOptions, (err, info) => {
if (err) return rej(err) if (err) return rej(err)
if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) if (info.is_live === true) return rej(new Error('Cannot download a live streaming.'))
if (info.format_id?.includes('+')) {
// this is a merge format and its extension will be appended
if (info.ext === 'mp4') {
info.mergeExt = 'mp4'
} else if (info.ext === 'webm') {
info.mergeExt = 'webm'
} else {
info.mergeExt = 'mkv'
}
}
const obj = buildVideoInfo(normalizeObject(info)) const obj = buildVideoInfo(normalizeObject(info))
if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video'
@ -133,18 +123,15 @@ function getYoutubeDLVideoFormat () {
].join('/') ].join('/')
} }
function downloadYoutubeDLVideo (url: string, extension: string, timeout: number, mergeExtension?: string) { function downloadYoutubeDLVideo (url: string, fileExt: string, timeout: number) {
let path = generateVideoImportTmpPath(url, extension) // Leave empty the extension, youtube-dl will add it
const pathWithoutExtension = generateVideoImportTmpPath(url, '')
path = mergeExtension
? path.replace(new RegExp(`${extension}$`), mergeExtension)
: path
let timer let timer
logger.info('Importing youtubeDL video %s to %s', url, path) logger.info('Importing youtubeDL video %s to %s', url, pathWithoutExtension)
let options = [ '-f', getYoutubeDLVideoFormat(), '-o', path ] let options = [ '-f', getYoutubeDLVideoFormat(), '-o', pathWithoutExtension ]
options = wrapWithProxyOptions(options) options = wrapWithProxyOptions(options)
if (process.env.FFMPEG_PATH) { if (process.env.FFMPEG_PATH) {
@ -156,26 +143,33 @@ function downloadYoutubeDLVideo (url: string, extension: string, timeout: number
return new Promise<string>((res, rej) => { return new Promise<string>((res, rej) => {
safeGetYoutubeDL() safeGetYoutubeDL()
.then(youtubeDL => { .then(youtubeDL => {
youtubeDL.exec(url, options, processOptions, err => { youtubeDL.exec(url, options, processOptions, async err => {
clearTimeout(timer) clearTimeout(timer)
if (err) { try {
remove(path) const path = await guessVideoPathWithExtension(pathWithoutExtension, fileExt)
.catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err }))
if (err) {
remove(path)
.catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err }))
return rej(err)
}
return res(path)
} catch (err) {
return rej(err) return rej(err)
} }
return res(path)
}) })
timer = setTimeout(() => { timer = setTimeout(() => {
const err = new Error('YoutubeDL download timeout.') const err = new Error('YoutubeDL download timeout.')
remove(path) guessVideoPathWithExtension(pathWithoutExtension, fileExt)
.then(path => remove(path))
.finally(() => rej(err)) .finally(() => rej(err))
.catch(err => { .catch(err => {
logger.error('Cannot remove %s in youtubeDL timeout.', path, { err }) logger.error('Cannot remove file in youtubeDL timeout.', { err })
return rej(err) return rej(err)
}) })
}, timeout) }, timeout)
@ -294,6 +288,22 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function guessVideoPathWithExtension (tmpPath: string, sourceExt: string) {
if (!isVideoFileExtnameValid(sourceExt)) {
throw new Error('Invalid video extension ' + sourceExt)
}
const extensions = [ sourceExt, '.mp4', '.mkv', '.webm' ]
for (const extension of extensions) {
const path = tmpPath + extension
if (await pathExists(path)) return path
}
throw new Error('Cannot guess path of ' + tmpPath)
}
function normalizeObject (obj: any) { function normalizeObject (obj: any) {
const newObj: any = {} const newObj: any = {}
@ -324,8 +334,7 @@ function buildVideoInfo (obj: any): YoutubeDLInfo {
tags: getTags(obj.tags), tags: getTags(obj.tags),
thumbnailUrl: obj.thumbnail || undefined, thumbnailUrl: obj.thumbnail || undefined,
originallyPublishedAt: buildOriginallyPublishedAt(obj), originallyPublishedAt: buildOriginallyPublishedAt(obj),
ext: obj.ext, ext: obj.ext
mergeExt: obj.mergeExt
} }
} }

View File

@ -80,7 +80,7 @@ async function processYoutubeDLImport (job: Bull.Job, payload: VideoImportYoutub
} }
return processFile( return processFile(
() => downloadYoutubeDLVideo(videoImport.targetUrl, payload.fileExt, VIDEO_IMPORT_TIMEOUT, payload.mergeExt), () => downloadYoutubeDLVideo(videoImport.targetUrl, payload.fileExt, VIDEO_IMPORT_TIMEOUT),
videoImport, videoImport,
options options
) )

View File

@ -80,9 +80,7 @@ export type VideoImportYoutubeDLPayload = {
generateThumbnail: boolean generateThumbnail: boolean
generatePreview: boolean generatePreview: boolean
fileExt?: string fileExt?: string
mergeExt?: string
} }
export type VideoImportTorrentPayload = { export type VideoImportTorrentPayload = {
type: VideoImportTorrentPayloadType type: VideoImportTorrentPayloadType