Add config to generate podcast audio file

This commit is contained in:
Chocobozzz
2026-02-23 16:31:34 +01:00
parent 1a4f4e76fd
commit 9204d042b9
27 changed files with 345 additions and 68 deletions
+11 -6
View File
@@ -131,9 +131,7 @@ export class FFmpegVOD {
const probe = await ffprobePromise(videoInputPath)
const videoStreamInfo = await getVideoStreamDimensionsInfo(videoInputPath, probe)
scaleFilterValue = videoStreamInfo?.isPortraitMode === true
? `w=${resolution}:h=-2`
: `w=-2:h=${resolution}`
scaleFilterValue = this.getScaleFilterValue({ resolution, portraitMode: videoStreamInfo?.isPortraitMode === true })
}
await presetVOD({
@@ -179,7 +177,7 @@ export class FFmpegVOD {
canCopyVideo: true,
videoStreamOnly: false,
fps: options.fps,
scaleFilterValue: this.getMergeAudioScaleFilterValue()
scaleFilterValue: this.getScaleFilterValue({ resolution: options.resolution, portraitMode: false })
})
command.outputOption('-preset:v veryfast')
@@ -190,8 +188,15 @@ export class FFmpegVOD {
}
// Avoid "height not divisible by 2" error
private getMergeAudioScaleFilterValue () {
return 'trunc(iw/2)*2:trunc(ih/2)*2'
private getScaleFilterValue (options: {
resolution: number
portraitMode: boolean
}) {
const { resolution, portraitMode } = options
return portraitMode === true
? `w=${resolution}:h=-2`
: `w=-2:h=${resolution}`
}
// ---------------------------------------------------------------------------
@@ -169,6 +169,7 @@ export interface CustomConfig {
resolutions: ConfigResolutions
alwaysTranscodeOriginalResolution: boolean
alwaysTranscodePodcastOptimizedAudio: boolean
fps: {
max: number
+2
View File
@@ -88,6 +88,7 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
expect(data.transcoding.alwaysTranscodeOriginalResolution).to.be.true
expect(data.transcoding.fps.max).to.equal(60)
expect(data.transcoding.webVideos.enabled).to.be.true
expect(data.transcoding.alwaysTranscodePodcastOptimizedAudio).to.be.true
expect(data.transcoding.hls.enabled).to.be.true
expect(data.transcoding.hls.splitAudioAndVideo).to.be.false
expect(data.transcoding.originalFile.keep).to.be.false
@@ -295,6 +296,7 @@ function buildNewCustomConfig (server: PeerTubeServer): CustomConfig {
'2160p': false
},
alwaysTranscodeOriginalResolution: false,
alwaysTranscodePodcastOptimizedAudio: false,
fps: {
max: 120
},
@@ -0,0 +1,117 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import {
cleanupTests,
ConfigCommand,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
describe('Test force audio web video transcoding', function () {
let servers: PeerTubeServer[] = []
let videoId1: string
let videoId2: string
before(async function () {
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
{
const { uuid } = await servers[0].videos.quickUpload({ name: 'video 1' })
videoId1 = uuid
}
{
const { uuid } = await servers[0].videos.quickUpload({ name: 'video 2' })
videoId2 = uuid
}
await servers[0].config.updateExistingConfig({
newConfig: {
transcoding: {
enabled: true,
originalFile: {
keep: false
},
allowAudioFiles: true,
allowAdditionalExtensions: true,
resolutions: ConfigCommand.getCustomConfigResolutions([ 240, 720, 1080 ]),
alwaysTranscodeOriginalResolution: true,
alwaysTranscodePodcastOptimizedAudio: true,
webVideos: {
enabled: false
},
hls: {
enabled: true,
splitAudioAndVideo: false
}
}
}
})
})
it('Should enable some web video resolutions and override global configuration for audio resolution', async function () {
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: uuid })
expect(video.files.map(f => f.resolution.id)).to.have.members([ 0 ])
expect(video.streamingPlaylists[0].files.map(f => f.resolution.id)).to.have.members([ 240, 720 ])
}
})
it('Should force the audio resolution on manual web video transcoding', async function () {
{
const video = await servers[0].videos.get({ id: videoId1 })
expect(video.files.map(f => f.resolution.id)).to.have.members([ 720 ])
expect(video.streamingPlaylists).to.have.lengthOf(0)
}
await servers[0].videos.runTranscoding({ transcodingType: 'web-video', videoId: videoId1 })
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: videoId1 })
expect(video.files.map(f => f.resolution.id)).to.have.members([ 0, 240, 720 ])
expect(video.streamingPlaylists).to.have.lengthOf(0)
}
})
it('Should not force the audio resolution on manual hls transcoding', async function () {
{
const video = await servers[0].videos.get({ id: videoId2 })
expect(video.files.map(f => f.resolution.id)).to.have.members([ 720 ])
expect(video.streamingPlaylists).to.have.lengthOf(0)
}
await servers[0].videos.runTranscoding({ transcodingType: 'hls', videoId: videoId2 })
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: videoId2 })
expect(video.files.map(f => f.resolution.id)).to.have.members([ 720 ])
expect(video.streamingPlaylists[0].files.map(f => f.resolution.id)).to.have.members([ 240, 720 ])
}
})
after(async function () {
await cleanupTests(servers)
})
})
@@ -1,5 +1,6 @@
export * from './audio-only.js'
export * from './create-transcoding.js'
export * from './force-audio-web-video-transcoding.js'
export * from './hls.js'
export * from './split-audio-and-video.js'
export * from './transcoder-limits.js'
+18 -4
View File
@@ -108,7 +108,16 @@ describe('Test syndication feeds', () => {
await servers[0].comments.createThread({ videoId: id, text: 'comment on password protected video' })
}
await serverHLSOnly.videos.upload({ attributes: { name: 'hls only video', nsfw: true } })
{
await serverHLSOnly.config.updateExistingConfig({
newConfig: {
transcoding: {
alwaysTranscodePodcastOptimizedAudio: true
}
}
})
await serverHLSOnly.videos.upload({ attributes: { name: 'hls only video', nsfw: true } })
}
await waitJobs([ ...servers, serverHLSOnly ])
@@ -201,7 +210,7 @@ describe('Test syndication feeds', () => {
const enclosure = xmlDoc.rss.channel.item.enclosure
expect(enclosure).to.exist
expectStartWith(enclosure['@_url'], `${serverHLSOnly.url}/download/videos/generate/`)
expectStartWith(enclosure['@_url'], `${serverHLSOnly.url}/static/web-videos/`)
expect(enclosure['@_url']).to.contain('.m4a')
expect(enclosure['@_type']).to.equal('audio/x-m4a')
@@ -217,6 +226,11 @@ describe('Test syndication feeds', () => {
expect(audioEnclosure['@_default']).to.equal(true)
expect(audioEnclosure['podcast:source']['@_uri']).to.equal(enclosure['@_url'])
const videoEnclosure = alternateEnclosures.find(e => e['@_type'] === 'video/mp4')
expect(videoEnclosure).to.exist
expect(videoEnclosure['@_default']).to.equal(false)
expectStartWith(videoEnclosure['podcast:source']['@_uri'], `${serverHLSOnly.url}/download/videos/generate/`)
const hlsEnclosure = alternateEnclosures.find(e => e['@_type'] === 'application/x-mpegURL')
expect(hlsEnclosure).to.exist
expect(hlsEnclosure['@_lang']).to.equal('zh')
@@ -475,9 +489,9 @@ describe('Test syndication feeds', () => {
const jsonObj = JSON.parse(json)
expect(jsonObj.items.length).to.be.equal(1)
expect(jsonObj.items[0].attachments).to.exist
expect(jsonObj.items[0].attachments.length).to.be.eq(6)
expect(jsonObj.items[0].attachments.length).to.be.eq(12)
for (let i = 0; i < 6; i++) {
for (let i = 0; i < 12; i++) {
expect(jsonObj.items[0].attachments[i].mime_type).to.be.eq('application/x-bittorrent')
expect(jsonObj.items[0].attachments[i].size_in_bytes).to.be.greaterThan(0)
expect(jsonObj.items[0].attachments[i].url).to.exist
@@ -86,6 +86,9 @@ describe('Test transcription in peertube-runner program', function () {
})
})
describe('With custom web video transcoding configuration', function () {
describe('On object storage', function () {
if (areMockObjectStorageTestsDisabled()) return
@@ -161,6 +164,7 @@ describe('Test transcription in peertube-runner program', function () {
})
})
describe('Check cleanup', function () {
it('Should have an empty cache directory', async function () {
await checkPeerTubeRunnerCacheIsEmpty(peertubeRunner, 'transcription')
@@ -4,6 +4,7 @@ import { VideoPrivacy, VideoResolution } from '@peertube/peertube-models'
import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils'
import {
cleanupTests,
ConfigCommand,
createMultipleServers,
doubleFollow,
ObjectStorageCommand,
@@ -392,6 +393,45 @@ describe('Test VOD transcoding in peertube-runner program', function () {
const resolutions = getAllFiles(video).map(f => f.resolution.id)
expect(resolutions).to.have.members([ 240, 240, 480, 480 ])
})
it('Should have a custom web video transcoding configuration', async function () {
await servers[0].config.updateExistingConfig({
newConfig: {
transcoding: {
enabled: true,
originalFile: {
keep: false
},
allowAudioFiles: true,
allowAdditionalExtensions: true,
resolutions: ConfigCommand.getCustomConfigResolutions([ 240, 720, 1080 ]),
alwaysTranscodeOriginalResolution: true,
alwaysTranscodePodcastOptimizedAudio: true,
hls: {
enabled: true,
splitAudioAndVideo: false
},
webVideos: {
enabled: false
}
}
}
})
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
await waitJobs(servers, { runnerJobs: true })
const video = await servers[0].videos.get({ id: uuid })
expect(video.files.map(f => f.resolution.id)).to.have.members([ 0 ])
expect(video.streamingPlaylists[0].files.map(f => f.resolution.id)).to.have.members([ 240, 720 ])
})
})
})