From 6ef092f0da89e31f755171b55c2cd98bd266df9e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 17 Jan 2025 09:52:45 +0100 Subject: [PATCH] Fix transcoding of audio only videos --- .../notification-dropdown.component.html | 1 - .../src/server/config-command.ts | 12 +++++ .../src/api/transcoding/create-transcoding.ts | 52 ++++++++++++++++++- packages/tests/src/api/videos/video-source.ts | 26 ++++++++++ .../tests/src/peertube-runner/replace-file.ts | 19 +++++++ server/core/helpers/ffmpeg/framerate.ts | 3 ++ server/core/models/video/video.ts | 4 +- 7 files changed, 113 insertions(+), 4 deletions(-) diff --git a/client/src/app/header/notification-dropdown.component.html b/client/src/app/header/notification-dropdown.component.html index 53f04cbdf..2bf61f902 100644 --- a/client/src/app/header/notification-dropdown.component.html +++ b/client/src/app/header/notification-dropdown.component.html @@ -66,7 +66,6 @@ diff --git a/packages/server-commands/src/server/config-command.ts b/packages/server-commands/src/server/config-command.ts index 41803de72..304161d79 100644 --- a/packages/server-commands/src/server/config-command.ts +++ b/packages/server-commands/src/server/config-command.ts @@ -5,6 +5,8 @@ import { AbstractCommand, OverrideCommandOptions } from '../shared/abstract-comm export class ConfigCommand extends AbstractCommand { + private savedConfig: CustomConfig + static getConfigResolutions (enabled: boolean, with0p = false) { return { '0p': enabled && with0p, @@ -579,4 +581,14 @@ export class ConfigCommand extends AbstractCommand { return this.updateCustomConfig({ ...options, newCustomConfig: merge({}, existing, options.newConfig) }) } + + // --------------------------------------------------------------------------- + + async save () { + this.savedConfig = await this.getCustomConfig() + } + + rollback () { + return this.updateCustomConfig({ newCustomConfig: this.savedConfig }) + } } diff --git a/packages/tests/src/api/transcoding/create-transcoding.ts b/packages/tests/src/api/transcoding/create-transcoding.ts index b6b91d51e..ffc39a5ce 100644 --- a/packages/tests/src/api/transcoding/create-transcoding.ts +++ b/packages/tests/src/api/transcoding/create-transcoding.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import { HttpStatusCode, VideoDetails } from '@peertube/peertube-models' +import { HttpStatusCode, VideoDetails, VideoResolution } from '@peertube/peertube-models' import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils' import { cleanupTests, @@ -253,6 +253,56 @@ function runTests (options: { expect(video.publishedAt).to.equal(publishedAt) }) + + it('Should transcode with an audio-only video', async function () { + this.timeout(60000) + + await servers[0].config.enableTranscoding({ + webVideo: true, + hls: false, + keepOriginal: false, + splitAudioAndVideo: false, + alwaysTranscodeOriginalResolution: false, + resolutions: [ VideoResolution.H_NOVIDEO ] + }) + + const { uuid } = await servers[0].videos.quickUpload({ name: 'quick' }) + await waitJobs(servers) + + // Only keep audio resolution + { + const video = await servers[0].videos.get({ id: uuid }) + + expect(video.streamingPlaylists).to.have.lengthOf(0) + + for (const file of video.files) { + if (file.resolution.id !== VideoResolution.H_NOVIDEO) { + await servers[0].videos.removeWebVideoFile({ videoId: uuid, fileId: file.id }) + } + } + } + + await servers[0].videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' }) + await waitJobs(servers) + + await servers[0].videos.runTranscoding({ videoId: uuid, transcodingType: 'web-video' }) + await waitJobs(servers) + await expectNoFailedTranscodingJob(servers[0]) + + for (const server of servers) { + const videoDetails = await server.videos.get({ id: uuid }) + + expect(videoDetails.files).to.have.lengthOf(1) + expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) + expect(videoDetails.streamingPlaylists[0].files).to.have.lengthOf(1) + + for (const files of videoDetails.files) { + expect(files.resolution.id).to.equal(VideoResolution.H_NOVIDEO) + } + + if (enableObjectStorage) await checkFilesInObjectStorage(objectStorage, videoDetails) + } + }) }) describe('With split audio and video', function () { diff --git a/packages/tests/src/api/videos/video-source.ts b/packages/tests/src/api/videos/video-source.ts index a1509945a..fd593fbfd 100644 --- a/packages/tests/src/api/videos/video-source.ts +++ b/packages/tests/src/api/videos/video-source.ts @@ -409,6 +409,32 @@ describe('Test video source management', function () { await makeGetRequest({ url: server.url, path: video.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 }) } }) + + it('Should replace the video with an audio only file', async function () { + await servers[0].config.save() + + await servers[0].config.enableTranscoding({ webVideo: true, hls: true, resolutions: [ 480, 360, 240, 144 ] }) + const { uuid } = await servers[0].videos.quickUpload({ name: 'future audio', fixture: 'video_short_360p.mp4' }) + await waitJobs(servers) + + { + const video = await servers[0].videos.get({ id: uuid }) + expect(getAllFiles(video)).to.have.lengthOf(6) + } + + const fixture = 'sample.ogg' + await servers[0].videos.replaceSourceFile({ videoId: uuid, fixture }) + await waitJobs(servers) + + for (const server of servers) { + const video = await server.videos.get({ id: uuid }) + + const files = getAllFiles(video) + expect(files).to.have.lengthOf(8) + } + + await servers[0].config.rollback() + }) }) describe('Autoblacklist', function () { diff --git a/packages/tests/src/peertube-runner/replace-file.ts b/packages/tests/src/peertube-runner/replace-file.ts index c5b0f4565..8bee1f258 100644 --- a/packages/tests/src/peertube-runner/replace-file.ts +++ b/packages/tests/src/peertube-runner/replace-file.ts @@ -75,6 +75,25 @@ describe('Test replace file using peertube-runner program', function () { await checkSourceFile({ server, fsCount: 2, fixture, uuid }) }) + it('Should replace the video by an audio file', async function () { + { + await server.videos.removeAllWebVideoFiles({ videoId: uuid }) + const video = await server.videos.get({ id: uuid }) + expect(getAllFiles(video)).to.have.lengthOf(2) + } + + const fixture = 'sample.ogg' + await server.videos.replaceSourceFile({ videoId: uuid, fixture }) + await waitJobs(server, { runnerJobs: true }) + + const video = await server.videos.get({ id: uuid }) + + const files = getAllFiles(video) + expect(files).to.have.lengthOf(4) + + await checkSourceFile({ server, fsCount: 2, fixture, uuid }) + }) + after(async function () { if (peertubeRunner) { await peertubeRunner.unregisterPeerTubeInstance({ runnerName: 'runner' }) diff --git a/server/core/helpers/ffmpeg/framerate.ts b/server/core/helpers/ffmpeg/framerate.ts index 93c68d0db..d1501a0a8 100644 --- a/server/core/helpers/ffmpeg/framerate.ts +++ b/server/core/helpers/ffmpeg/framerate.ts @@ -1,3 +1,4 @@ +import { VideoResolution } from '@peertube/peertube-models' import { CONFIG } from '@server/initializers/config.js' import { logger } from '../logger.js' @@ -9,6 +10,8 @@ export function computeOutputFPS (options: { }) { const { resolution, isOriginResolution, type } = options + if (resolution === VideoResolution.H_NOVIDEO) return 0 + const settings = type === 'vod' ? buildTranscodingFPSOptions(CONFIG.TRANSCODING.FPS.MAX) : buildTranscodingFPSOptions(CONFIG.LIVE.TRANSCODING.FPS.MAX) diff --git a/server/core/models/video/video.ts b/server/core/models/video/video.ts index 2cfd1caac..3e2b76763 100644 --- a/server/core/models/video/video.ts +++ b/server/core/models/video/video.ts @@ -1796,11 +1796,11 @@ export class VideoModel extends SequelizeModel { // --------------------------------------------------------------------------- getMaxFPS () { - return this.getMaxQualityFile(VideoFileStream.VIDEO).fps + return this.getMaxQualityFile(VideoFileStream.VIDEO)?.fps || 0 } getMaxResolution () { - return this.getMaxQualityFile(VideoFileStream.VIDEO).resolution + return this.getMaxQualityFile(VideoFileStream.VIDEO)?.resolution || this.getMaxQualityFile(VideoFileStream.AUDIO)?.resolution } hasAudio () {