1
0
Fork 0

Respect "transcode original resolution" for runner

This commit is contained in:
Chocobozzz 2023-11-17 16:25:11 +01:00
parent 1682b0bab0
commit d4f21493e1
No known key found for this signature in database
GPG key ID: 583A612D890159BE
7 changed files with 83 additions and 15 deletions

View file

@ -15,11 +15,13 @@ export type RunnerJobPrivatePayload =
export interface RunnerJobVODWebVideoTranscodingPrivatePayload { export interface RunnerJobVODWebVideoTranscodingPrivatePayload {
videoUUID: string videoUUID: string
isNewVideo: boolean isNewVideo: boolean
deleteInputFileId: number | null
} }
export interface RunnerJobVODAudioMergeTranscodingPrivatePayload { export interface RunnerJobVODAudioMergeTranscodingPrivatePayload {
videoUUID: string videoUUID: string
isNewVideo: boolean isNewVideo: boolean
deleteInputFileId: number | null
} }
export interface RunnerJobVODHLSTranscodingPrivatePayload { export interface RunnerJobVODHLSTranscodingPrivatePayload {

View file

@ -232,6 +232,45 @@ describe('Test VOD transcoding in peertube-runner program', function () {
resolutions: [ 720, 480, 360, 240, 144, 0 ] resolutions: [ 720, 480, 360, 240, 144, 0 ]
}) })
}) })
it('Should not generate an upper resolution than original file', async function () {
this.timeout(120_000)
await servers[0].config.updateExistingSubConfig({
newConfig: {
transcoding: {
enabled: true,
hls: { enabled: true },
webVideos: { enabled: true },
resolutions: {
'0p': false,
'144p': false,
'240p': true,
'360p': false,
'480p': true,
'720p': false,
'1080p': false,
'1440p': false,
'2160p': false
},
alwaysTranscodeOriginalResolution: false
}
}
})
const { uuid } = await servers[0].videos.quickUpload({ name: 'video', fixture: 'video_short.webm' })
await waitJobs(servers, { runnerJobs: true })
const video = await servers[0].videos.get({ id: uuid })
const hlsFiles = video.streamingPlaylists[0].files
expect(video.files).to.have.lengthOf(2)
expect(hlsFiles).to.have.lengthOf(2)
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
const resolutions = getAllFiles(video).map(f => f.resolution.id).sort()
expect(resolutions).to.deep.equal([ 240, 240, 480, 480 ])
})
} }
before(async function () { before(async function () {

View file

@ -36,6 +36,8 @@ async function completeWebVideoFilesCheck (options: {
const transcodingEnabled = serverConfig.transcoding.web_videos.enabled const transcodingEnabled = serverConfig.transcoding.web_videos.enabled
expect(files).to.have.lengthOf(files.length)
for (const attributeFile of files) { for (const attributeFile of files) {
const file = video.files.find(f => f.resolution.id === attributeFile.resolution) const file = video.files.find(f => f.resolution.id === attributeFile.resolution)
expect(file, `resolution ${attributeFile.resolution} does not exist`).not.to.be.undefined expect(file, `resolution ${attributeFile.resolution} does not exist`).not.to.be.undefined

View file

@ -8,6 +8,7 @@ import { VideoModel } from '@server/models/video/video.js'
import { MVideoFullLight } from '@server/types/models/index.js' import { MVideoFullLight } from '@server/types/models/index.js'
import { MRunnerJob } from '@server/types/models/runners/index.js' import { MRunnerJob } from '@server/types/models/runners/index.js'
import { RunnerJobVODAudioMergeTranscodingPrivatePayload, RunnerJobVODWebVideoTranscodingPrivatePayload } from '@peertube/peertube-models' import { RunnerJobVODAudioMergeTranscodingPrivatePayload, RunnerJobVODWebVideoTranscodingPrivatePayload } from '@peertube/peertube-models'
import { lTags } from '@server/lib/object-storage/shared/logger.js'
export async function onVODWebVideoOrAudioMergeTranscodingJob (options: { export async function onVODWebVideoOrAudioMergeTranscodingJob (options: {
video: MVideoFullLight video: MVideoFullLight
@ -28,6 +29,23 @@ export async function onVODWebVideoOrAudioMergeTranscodingJob (options: {
videoOutputPath: newVideoFilePath videoOutputPath: newVideoFilePath
}) })
if (privatePayload.deleteInputFileId) {
const inputFile = video.VideoFiles.find(f => f.id === privatePayload.deleteInputFileId)
if (inputFile) {
await video.removeWebVideoFile(inputFile)
await inputFile.destroy()
video.VideoFiles = video.VideoFiles.filter(f => f.id !== inputFile.id)
} else {
logger.error(
'Cannot delete input file %d of video %s: does not exist anymore',
privatePayload.deleteInputFileId, video.uuid,
{ ...lTags(video.uuid), privatePayload }
)
}
}
await onTranscodingEnded({ isNewVideo: privatePayload.isNewVideo, moveVideoToNextState: true, video }) await onTranscodingEnded({ isNewVideo: privatePayload.isNewVideo, moveVideoToNextState: true, video })
} }

View file

@ -21,6 +21,7 @@ type CreateOptions = {
resolution: number resolution: number
fps: number fps: number
priority: number priority: number
deleteInputFileId: number | null
dependsOnRunnerJob?: MRunnerJob dependsOnRunnerJob?: MRunnerJob
} }
@ -43,7 +44,7 @@ export class VODAudioMergeTranscodingJobHandler extends AbstractVODTranscodingJo
} }
const privatePayload: RunnerJobVODWebVideoTranscodingPrivatePayload = { const privatePayload: RunnerJobVODWebVideoTranscodingPrivatePayload = {
...pick(options, [ 'isNewVideo' ]), ...pick(options, [ 'isNewVideo', 'deleteInputFileId' ]),
videoUUID: video.uuid videoUUID: video.uuid
} }
@ -81,12 +82,6 @@ export class VODAudioMergeTranscodingJobHandler extends AbstractVODTranscodingJo
video.duration = await getVideoStreamDuration(videoFilePath) video.duration = await getVideoStreamDuration(videoFilePath)
await video.save() await video.save()
// We can remove the old audio file
const oldAudioFile = video.VideoFiles[0]
await video.removeWebVideoFile(oldAudioFile)
await oldAudioFile.destroy()
video.VideoFiles = []
await onVODWebVideoOrAudioMergeTranscodingJob({ video, videoFilePath, privatePayload }) await onVODWebVideoOrAudioMergeTranscodingJob({ video, videoFilePath, privatePayload })
logger.info( logger.info(

View file

@ -20,6 +20,7 @@ type CreateOptions = {
resolution: number resolution: number
fps: number fps: number
priority: number priority: number
deleteInputFileId: number | null
dependsOnRunnerJob?: MRunnerJob dependsOnRunnerJob?: MRunnerJob
} }
@ -41,7 +42,7 @@ export class VODWebVideoTranscodingJobHandler extends AbstractVODTranscodingJobH
} }
const privatePayload: RunnerJobVODWebVideoTranscodingPrivatePayload = { const privatePayload: RunnerJobVODWebVideoTranscodingPrivatePayload = {
...pick(options, [ 'isNewVideo' ]), ...pick(options, [ 'isNewVideo', 'deleteInputFileId' ]),
videoUUID: video.uuid videoUUID: video.uuid
} }

View file

@ -13,7 +13,7 @@ import { MUserId, MVideoFile, MVideoFullLight, MVideoWithFileThumbnail } from '@
import { MRunnerJob } from '@server/types/models/runners/index.js' import { MRunnerJob } from '@server/types/models/runners/index.js'
import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream, isAudioFile } from '@peertube/peertube-ffmpeg' import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream, isAudioFile } from '@peertube/peertube-ffmpeg'
import { getTranscodingJobPriority } from '../../transcoding-priority.js' import { getTranscodingJobPriority } from '../../transcoding-priority.js'
import { computeResolutionsToTranscode } from '../../transcoding-resolutions.js' import { buildOriginalFileResolution, computeResolutionsToTranscode } from '../../transcoding-resolutions.js'
import { AbstractJobBuilder } from './abstract-job-builder.js' import { AbstractJobBuilder } from './abstract-job-builder.js'
/** /**
@ -52,16 +52,23 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder {
? VIDEO_TRANSCODING_FPS.AUDIO_MERGE // The first transcoding job will transcode to this FPS value ? VIDEO_TRANSCODING_FPS.AUDIO_MERGE // The first transcoding job will transcode to this FPS value
: await getVideoStreamFPS(videoFilePath, probe) : await getVideoStreamFPS(videoFilePath, probe)
const maxResolution = await isAudioFile(videoFilePath, probe) const isAudioInput = await isAudioFile(videoFilePath, probe)
const maxResolution = isAudioInput
? DEFAULT_AUDIO_RESOLUTION ? DEFAULT_AUDIO_RESOLUTION
: resolution : buildOriginalFileResolution(resolution)
const fps = computeOutputFPS({ inputFPS, resolution: maxResolution }) const fps = computeOutputFPS({ inputFPS, resolution: maxResolution })
const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 })
const deleteInputFileId = isAudioInput || maxResolution !== resolution
? videoFile.id
: null
const jobPayload = { video, resolution: maxResolution, fps, isNewVideo, priority, deleteInputFileId }
const mainRunnerJob = videoFile.isAudio() const mainRunnerJob = videoFile.isAudio()
? await new VODAudioMergeTranscodingJobHandler().create({ video, resolution: maxResolution, fps, isNewVideo, priority }) ? await new VODAudioMergeTranscodingJobHandler().create(jobPayload)
: await new VODWebVideoTranscodingJobHandler().create({ video, resolution: maxResolution, fps, isNewVideo, priority }) : await new VODWebVideoTranscodingJobHandler().create(jobPayload)
if (CONFIG.TRANSCODING.HLS.ENABLED === true) { if (CONFIG.TRANSCODING.HLS.ENABLED === true) {
await new VODHLSTranscodingJobHandler().create({ await new VODHLSTranscodingJobHandler().create({
@ -110,12 +117,14 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder {
logger.info('Manually creating transcoding jobs for %s.', transcodingType, { childrenResolutions, maxResolution }) logger.info('Manually creating transcoding jobs for %s.', transcodingType, { childrenResolutions, maxResolution })
const jobPayload = { video, resolution: maxResolution, fps: maxFPS, isNewVideo, priority, deleteInputFileId: null }
// Process the last resolution before the other ones to prevent concurrency issue // Process the last resolution before the other ones to prevent concurrency issue
// Because low resolutions use the biggest one as ffmpeg input // Because low resolutions use the biggest one as ffmpeg input
const mainJob = transcodingType === 'hls' const mainJob = transcodingType === 'hls'
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
? await new VODHLSTranscodingJobHandler().create({ video, resolution: maxResolution, fps: maxFPS, isNewVideo, deleteWebVideoFiles: false, priority }) ? await new VODHLSTranscodingJobHandler().create({ ...jobPayload, deleteWebVideoFiles: false })
: await new VODWebVideoTranscodingJobHandler().create({ video, resolution: maxResolution, fps: maxFPS, isNewVideo, priority }) : await new VODWebVideoTranscodingJobHandler().create(jobPayload)
for (const resolution of childrenResolutions) { for (const resolution of childrenResolutions) {
const dependsOnRunnerJob = mainJob const dependsOnRunnerJob = mainJob
@ -141,6 +150,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder {
fps, fps,
isNewVideo, isNewVideo,
dependsOnRunnerJob, dependsOnRunnerJob,
deleteInputFileId: null,
priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 })
}) })
continue continue
@ -180,6 +190,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder {
fps, fps,
isNewVideo, isNewVideo,
dependsOnRunnerJob: mainRunnerJob, dependsOnRunnerJob: mainRunnerJob,
deleteInputFileId: null,
priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 })
}) })
} }