audiobookshelf/test/server/utils/ffmpegHelpers_merge.test.js
2026-02-14 21:17:52 +02:00

112 lines
3.4 KiB
JavaScript

const { expect } = require('chai')
const sinon = require('sinon')
const EventEmitter = require('events')
const fs = require('../../../server/libs/fsExtra')
const { mergeAudioFiles } = require('../../../server/utils/ffmpegHelpers')
describe('mergeAudioFiles', () => {
let ffmpegStub
let audioTracks
let encodingOptions
let itemCachePath
let outputFilePath
beforeEach(() => {
ffmpegStub = new EventEmitter()
ffmpegStub.input = sinon.stub().returnsThis()
ffmpegStub.inputOptions = sinon.stub().returnsThis()
ffmpegStub.outputOptions = sinon.stub().returnsThis()
ffmpegStub.output = sinon.stub().returnsThis()
ffmpegStub.run = sinon.stub().callsFake(() => {
ffmpegStub.emit('end')
})
audioTracks = [
{
index: 0,
ino: 'ino1',
metadata: { path: '/path/to/track1.mp3', ext: '.mp3' },
duration: 100
},
{
index: 1,
ino: 'ino2',
metadata: { path: '/path/to/track2.mp3', ext: '.mp3' },
duration: 120
}
]
encodingOptions = {
codec: 'aac',
bitrate: '128k',
channels: 2
}
itemCachePath = '/path/to/cache'
outputFilePath = '/path/to/output.m4b'
sinon.stub(fs, 'writeFile').resolves()
sinon.stub(fs, 'remove').resolves()
})
afterEach(() => {
sinon.restore()
})
it('should use re-encoding by default (codec aac)', async () => {
await mergeAudioFiles(audioTracks, 220, itemCachePath, outputFilePath, encodingOptions, null, ffmpegStub)
const calls = ffmpegStub.outputOptions.getCalls()
const allArgs = calls.flatMap(call => call.args[0])
expect(allArgs).to.include('-acodec aac')
expect(allArgs).to.include('-ac 2')
expect(allArgs).to.include('-b:a 128k')
})
it('should use stream copy when codec is copy', async () => {
encodingOptions.codec = 'copy'
await mergeAudioFiles(audioTracks, 220, itemCachePath, outputFilePath, encodingOptions, null, ffmpegStub)
// Should hit the 'else' block which uses '-c:a copy'
expect(ffmpegStub.outputOptions.calledWith(['-c:a copy'])).to.be.true
// Should NOT contain bitrate or channels or acodec
const outputOptionsCalls = ffmpegStub.outputOptions.getCalls()
outputOptionsCalls.forEach(call => {
const args = call.args[0]
if (Array.isArray(args)) {
expect(args.some(arg => arg.includes('-b:a'))).to.be.false
expect(args.some(arg => arg.includes('-ac '))).to.be.false // space to avoid matching '-acodec'
expect(args.some(arg => arg.includes('-acodec'))).to.be.false
}
})
})
it('should handle single track with copy', async () => {
encodingOptions.codec = 'copy'
const singleTrack = [audioTracks[0]]
await mergeAudioFiles(singleTrack, 100, itemCachePath, outputFilePath, encodingOptions, null, ffmpegStub)
expect(ffmpegStub.outputOptions.calledWith(['-c:a copy'])).to.be.true
})
it('should handle single track m4b with copy', async () => {
encodingOptions.codec = 'copy'
const singleTrack = [
{
index: 0,
ino: 'ino1',
metadata: { path: '/path/to/track1.m4b', ext: '.m4b' },
duration: 100
}
]
await mergeAudioFiles(singleTrack, 100, itemCachePath, outputFilePath, encodingOptions, null, ffmpegStub)
// Code says: if (isOneTrack && firstTrackIsM4b) ffmpeg.outputOptions(['-c copy'])
expect(ffmpegStub.outputOptions.calledWith(['-c copy'])).to.be.true
})
})