1
0
Fork 0

Limit import depending on transcoding resolutions

This commit is contained in:
Chocobozzz 2022-08-05 15:05:20 +02:00
parent 64fd6158fd
commit 5e2afe4290
No known key found for this signature in database
GPG key ID: 583A612D890159BE
12 changed files with 144 additions and 43 deletions

View file

@ -53,7 +53,7 @@ async function run () {
if (options.generateHls || CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) { if (options.generateHls || CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
const resolutionsEnabled = options.resolution const resolutionsEnabled = options.resolution
? [ parseInt(options.resolution) ] ? [ parseInt(options.resolution) ]
: computeResolutionsToTranscode({ inputResolution: maxResolution, type: 'vod', includeInputResolution: true }) : computeResolutionsToTranscode({ input: maxResolution, type: 'vod', includeInput: true, strictLower: false })
for (const resolution of resolutionsEnabled) { for (const resolution of resolutionsEnabled) {
dataInput.push({ dataInput.push({

View file

@ -10,6 +10,7 @@ import { CONFIG, reloadConfig } from '../../initializers/config'
import { ClientHtml } from '../../lib/client-html' import { ClientHtml } from '../../lib/client-html'
import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares' import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares'
import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config' import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config'
import { logger } from '@server/helpers/logger'
const configRouter = express.Router() const configRouter = express.Router()
@ -112,6 +113,7 @@ async function updateCustomConfig (req: express.Request, res: express.Response)
const data = customConfig() const data = customConfig()
logger.info('coucou', { data })
auditLogger.update( auditLogger.update(
getAuditIdFromRes(res), getAuditIdFromRes(res),
new CustomConfigAuditView(data), new CustomConfigAuditView(data),

View file

@ -175,7 +175,11 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
const targetUrl = body.targetUrl const targetUrl = body.targetUrl
const user = res.locals.oauth.token.User const user = res.locals.oauth.token.User
const youtubeDL = new YoutubeDLWrapper(targetUrl, ServerConfigManager.Instance.getEnabledResolutions('vod')) const youtubeDL = new YoutubeDLWrapper(
targetUrl,
ServerConfigManager.Instance.getEnabledResolutions('vod'),
CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
)
// Get video infos // Get video infos
let youtubeDLInfo: YoutubeDLInfo let youtubeDLInfo: YoutubeDLInfo

View file

@ -32,7 +32,7 @@ async function createTranscoding (req: express.Request, res: express.Response) {
const { resolution: maxResolution, audioStream } = await video.probeMaxQualityFile() const { resolution: maxResolution, audioStream } = await video.probeMaxQualityFile()
const resolutions = await Hooks.wrapObject( const resolutions = await Hooks.wrapObject(
computeResolutionsToTranscode({ inputResolution: maxResolution, type: 'vod', includeInputResolution: true }), computeResolutionsToTranscode({ input: maxResolution, type: 'vod', includeInput: true, strictLower: false }),
'filter:transcoding.manual.resolutions-to-transcode.result', 'filter:transcoding.manual.resolutions-to-transcode.result',
body body
) )

View file

@ -91,11 +91,12 @@ async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function computeResolutionsToTranscode (options: { function computeResolutionsToTranscode (options: {
inputResolution: number input: number
type: 'vod' | 'live' type: 'vod' | 'live'
includeInputResolution: boolean includeInput: boolean
strictLower: boolean
}) { }) {
const { inputResolution, type, includeInputResolution } = options const { input, type, includeInput, strictLower } = options
const configResolutions = type === 'vod' const configResolutions = type === 'vod'
? CONFIG.TRANSCODING.RESOLUTIONS ? CONFIG.TRANSCODING.RESOLUTIONS
@ -117,13 +118,18 @@ function computeResolutionsToTranscode (options: {
] ]
for (const resolution of availableResolutions) { for (const resolution of availableResolutions) {
if (configResolutions[resolution + 'p'] === true && inputResolution > resolution) { // Resolution not enabled
resolutionsEnabled.add(resolution) if (configResolutions[resolution + 'p'] !== true) continue
} // Too big resolution for input file
if (input < resolution) continue
// We only want lower resolutions than input file
if (strictLower && input === resolution) continue
resolutionsEnabled.add(resolution)
} }
if (includeInputResolution) { if (includeInput) {
resolutionsEnabled.add(inputResolution) resolutionsEnabled.add(input)
} }
return Array.from(resolutionsEnabled) return Array.from(resolutionsEnabled)

View file

@ -57,7 +57,7 @@ export class YoutubeDLCLI {
} }
} }
static getYoutubeDLVideoFormat (enabledResolutions: VideoResolution[]) { static getYoutubeDLVideoFormat (enabledResolutions: VideoResolution[], useBestFormat: boolean) {
/** /**
* list of format selectors in order or preference * list of format selectors in order or preference
* see https://github.com/ytdl-org/youtube-dl#format-selection * see https://github.com/ytdl-org/youtube-dl#format-selection
@ -69,18 +69,26 @@ export class YoutubeDLCLI {
* *
* in any case we avoid AV1, see https://github.com/Chocobozzz/PeerTube/issues/3499 * in any case we avoid AV1, see https://github.com/Chocobozzz/PeerTube/issues/3499
**/ **/
const resolution = enabledResolutions.length === 0
? VideoResolution.H_720P
: Math.max(...enabledResolutions)
return [ let result: string[] = []
`bestvideo[vcodec^=avc1][height=${resolution}]+bestaudio[ext=m4a]`, // case #1
`bestvideo[vcodec!*=av01][vcodec!*=vp9.2][height=${resolution}]+bestaudio`, // case #2 if (!useBestFormat) {
`bestvideo[vcodec^=avc1][height<=${resolution}]+bestaudio[ext=m4a]`, // case #3 const resolution = enabledResolutions.length === 0
`bestvideo[vcodec!*=av01][vcodec!*=vp9.2]+bestaudio`, ? VideoResolution.H_720P
: Math.max(...enabledResolutions)
result = [
`bestvideo[vcodec^=avc1][height=${resolution}]+bestaudio[ext=m4a]`, // case #1
`bestvideo[vcodec!*=av01][vcodec!*=vp9.2][height=${resolution}]+bestaudio`, // case #2
`bestvideo[vcodec^=avc1][height<=${resolution}]+bestaudio[ext=m4a]` // case #
]
}
return result.concat([
'bestvideo[vcodec!*=av01][vcodec!*=vp9.2]+bestaudio',
'best[vcodec!*=av01][vcodec!*=vp9.2]', // case fallback for known formats 'best[vcodec!*=av01][vcodec!*=vp9.2]', // case fallback for known formats
'best' // Ultimate fallback 'best' // Ultimate fallback
].join('/') ]).join('/')
} }
private constructor () { private constructor () {

View file

@ -21,7 +21,11 @@ const processOptions = {
class YoutubeDLWrapper { class YoutubeDLWrapper {
constructor (private readonly url: string = '', private readonly enabledResolutions: number[] = []) { constructor (
private readonly url: string,
private readonly enabledResolutions: number[],
private readonly useBestFormat: boolean
) {
} }
@ -30,7 +34,7 @@ class YoutubeDLWrapper {
const info = await youtubeDL.getInfo({ const info = await youtubeDL.getInfo({
url: this.url, url: this.url,
format: YoutubeDLCLI.getYoutubeDLVideoFormat(this.enabledResolutions), format: YoutubeDLCLI.getYoutubeDLVideoFormat(this.enabledResolutions, this.useBestFormat),
additionalYoutubeDLArgs: youtubeDLArgs, additionalYoutubeDLArgs: youtubeDLArgs,
processOptions processOptions
}) })
@ -80,7 +84,7 @@ class YoutubeDLWrapper {
try { try {
await youtubeDL.download({ await youtubeDL.download({
url: this.url, url: this.url,
format: YoutubeDLCLI.getYoutubeDLVideoFormat(this.enabledResolutions), format: YoutubeDLCLI.getYoutubeDLVideoFormat(this.enabledResolutions, this.useBestFormat),
output: pathWithoutExtension, output: pathWithoutExtension,
timeout, timeout,
processOptions processOptions

View file

@ -2,6 +2,7 @@ import { Job } from 'bull'
import { move, remove, stat } from 'fs-extra' import { move, remove, stat } from 'fs-extra'
import { retryTransactionWrapper } from '@server/helpers/database-utils' import { retryTransactionWrapper } from '@server/helpers/database-utils'
import { YoutubeDLWrapper } from '@server/helpers/youtube-dl' import { YoutubeDLWrapper } from '@server/helpers/youtube-dl'
import { CONFIG } from '@server/initializers/config'
import { isPostImportVideoAccepted } from '@server/lib/moderation' import { isPostImportVideoAccepted } from '@server/lib/moderation'
import { generateWebTorrentVideoFilename } from '@server/lib/paths' import { generateWebTorrentVideoFilename } from '@server/lib/paths'
import { Hooks } from '@server/lib/plugins/hooks' import { Hooks } from '@server/lib/plugins/hooks'
@ -25,7 +26,7 @@ import {
VideoResolution, VideoResolution,
VideoState VideoState
} from '@shared/models' } from '@shared/models'
import { ffprobePromise, getVideoStreamDuration, getVideoStreamFPS, getVideoStreamDimensionsInfo } from '../../../helpers/ffmpeg' import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamDuration, getVideoStreamFPS } from '../../../helpers/ffmpeg'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { getSecureTorrentName } from '../../../helpers/utils' import { getSecureTorrentName } from '../../../helpers/utils'
import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent'
@ -80,7 +81,11 @@ async function processYoutubeDLImport (job: Job, videoImport: MVideoImportDefaul
const options = { type: payload.type, videoImportId: videoImport.id } const options = { type: payload.type, videoImportId: videoImport.id }
const youtubeDL = new YoutubeDLWrapper(videoImport.targetUrl, ServerConfigManager.Instance.getEnabledResolutions('vod')) const youtubeDL = new YoutubeDLWrapper(
videoImport.targetUrl,
ServerConfigManager.Instance.getEnabledResolutions('vod'),
CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
)
return processFile( return processFile(
() => youtubeDL.downloadVideo(payload.fileExt, JOB_TTL['video-import']), () => youtubeDL.downloadVideo(payload.fileExt, JOB_TTL['video-import']),

View file

@ -265,7 +265,7 @@ async function createLowerResolutionsJobs (options: {
// Create transcoding jobs if there are enabled resolutions // Create transcoding jobs if there are enabled resolutions
const resolutionsEnabled = await Hooks.wrapObject( const resolutionsEnabled = await Hooks.wrapObject(
computeResolutionsToTranscode({ inputResolution: videoFileResolution, type: 'vod', includeInputResolution: false }), computeResolutionsToTranscode({ input: videoFileResolution, type: 'vod', includeInput: false, strictLower: true }),
'filter:transcoding.auto.resolutions-to-transcode.result', 'filter:transcoding.auto.resolutions-to-transcode.result',
options options
) )

View file

@ -456,10 +456,10 @@ class LiveManager {
} }
private buildAllResolutionsToTranscode (originResolution: number) { private buildAllResolutionsToTranscode (originResolution: number) {
const includeInputResolution = CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION const includeInput = CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
const resolutionsEnabled = CONFIG.LIVE.TRANSCODING.ENABLED const resolutionsEnabled = CONFIG.LIVE.TRANSCODING.ENABLED
? computeResolutionsToTranscode({ inputResolution: originResolution, type: 'live', includeInputResolution }) ? computeResolutionsToTranscode({ input: originResolution, type: 'live', includeInput, strictLower: false })
: [] : []
if (resolutionsEnabled.length === 0) { if (resolutionsEnabled.length === 0) {

View file

@ -366,7 +366,7 @@ async function generateHlsPlaylistCommon (options: {
function buildOriginalFileResolution (inputResolution: number) { function buildOriginalFileResolution (inputResolution: number) {
if (CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION === true) return toEven(inputResolution) if (CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION === true) return toEven(inputResolution)
const resolutions = computeResolutionsToTranscode({ inputResolution, type: 'vod', includeInputResolution: false }) const resolutions = computeResolutionsToTranscode({ input: inputResolution, type: 'vod', includeInput: false, strictLower: false })
if (resolutions.length === 0) return toEven(inputResolution) if (resolutions.length === 0) return toEven(inputResolution)
return Math.max(...resolutions) return Math.max(...resolutions)

View file

@ -6,7 +6,7 @@ import { pathExists, readdir, remove } from 'fs-extra'
import { join } from 'path' import { join } from 'path'
import { FIXTURE_URLS, testCaptionFile, testImage } from '@server/tests/shared' import { FIXTURE_URLS, testCaptionFile, testImage } from '@server/tests/shared'
import { areHttpImportTestsDisabled } from '@shared/core-utils' import { areHttpImportTestsDisabled } from '@shared/core-utils'
import { HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models' import { CustomConfig, HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models'
import { import {
cleanupTests, cleanupTests,
createMultipleServers, createMultipleServers,
@ -17,6 +17,7 @@ import {
setDefaultVideoChannel, setDefaultVideoChannel,
waitJobs waitJobs
} from '@shared/server-commands' } from '@shared/server-commands'
import { DeepPartial } from '@shared/typescript-utils'
async function checkVideosServer1 (server: PeerTubeServer, idHttp: string, idMagnet: string, idTorrent: string) { async function checkVideosServer1 (server: PeerTubeServer, idHttp: string, idMagnet: string, idTorrent: string) {
const videoHttp = await server.videos.get({ id: idHttp }) const videoHttp = await server.videos.get({ id: idHttp })
@ -105,6 +106,16 @@ describe('Test video imports', function () {
await setAccessTokensToServers(servers) await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers) await setDefaultVideoChannel(servers)
for (const server of servers) {
await server.config.updateExistingSubConfig({
newConfig: {
transcoding: {
alwaysTranscodeOriginalResolution: false
}
}
})
}
await doubleFollow(servers[0], servers[1]) await doubleFollow(servers[0], servers[1])
}) })
@ -306,10 +317,11 @@ describe('Test video imports', function () {
it('Should import no HDR version on a HDR video', async function () { it('Should import no HDR version on a HDR video', async function () {
this.timeout(300_000) this.timeout(300_000)
const config = { const config: DeepPartial<CustomConfig> = {
transcoding: { transcoding: {
enabled: true, enabled: true,
resolutions: { resolutions: {
'0p': false,
'144p': true, '144p': true,
'240p': true, '240p': true,
'360p': false, '360p': false,
@ -321,19 +333,9 @@ describe('Test video imports', function () {
}, },
webtorrent: { enabled: true }, webtorrent: { enabled: true },
hls: { enabled: false } hls: { enabled: false }
},
import: {
videos: {
http: {
enabled: true
},
torrent: {
enabled: true
}
}
} }
} }
await servers[0].config.updateCustomSubConfig({ newConfig: config }) await servers[0].config.updateExistingSubConfig({ newConfig: config })
const attributes = { const attributes = {
name: 'hdr video', name: 'hdr video',
@ -353,6 +355,76 @@ describe('Test video imports', function () {
expect(maxResolution, 'expected max resolution not met').to.equals(VideoResolution.H_240P) expect(maxResolution, 'expected max resolution not met').to.equals(VideoResolution.H_240P)
}) })
it('Should not import resolution higher than enabled transcoding resolution', async function () {
this.timeout(300_000)
const config: DeepPartial<CustomConfig> = {
transcoding: {
enabled: true,
resolutions: {
'0p': false,
'144p': true,
'240p': false,
'360p': false,
'480p': false,
'720p': false,
'1080p': false,
'1440p': false,
'2160p': false
},
alwaysTranscodeOriginalResolution: false
}
}
await servers[0].config.updateExistingSubConfig({ newConfig: config })
const attributes = {
name: 'small resolution video',
targetUrl: FIXTURE_URLS.youtube,
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC
}
const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
const videoUUID = videoImported.uuid
await waitJobs(servers)
// test resolution
const video = await servers[0].videos.get({ id: videoUUID })
expect(video.name).to.equal('small resolution video')
expect(video.files).to.have.lengthOf(1)
expect(video.files[0].resolution.id).to.equal(144)
})
it('Should import resolution higher than enabled transcoding resolution', async function () {
this.timeout(300_000)
const config: DeepPartial<CustomConfig> = {
transcoding: {
alwaysTranscodeOriginalResolution: true
}
}
await servers[0].config.updateExistingSubConfig({ newConfig: config })
const attributes = {
name: 'bigger resolution video',
targetUrl: FIXTURE_URLS.youtube,
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC
}
const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
const videoUUID = videoImported.uuid
await waitJobs(servers)
// test resolution
const video = await servers[0].videos.get({ id: videoUUID })
expect(video.name).to.equal('bigger resolution video')
expect(video.files).to.have.lengthOf(2)
expect(video.files.find(f => f.resolution.id === 240)).to.exist
expect(video.files.find(f => f.resolution.id === 144)).to.exist
})
it('Should import a peertube video', async function () { it('Should import a peertube video', async function () {
this.timeout(120_000) this.timeout(120_000)