Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/lib/ffmpeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ function buildAudioTrimFilter(recipe: EditRecipe): string {
return `${trimFilter},asetpts=PTS-STARTPTS`;
}

function buildArguments(
export function buildArguments(
recipe: EditRecipe,
format: "mp4" | "webm" | "mkv" | "gif",
outputName: string,
Expand Down Expand Up @@ -529,7 +529,7 @@ function buildArguments(
}

const needsFilterComplex = hasOverlay || hasMusicTrack;
const shouldKeepAudio = recipe.keepAudio && (hasOriginalAudio || hasMusicTrack);
const shouldKeepAudio = (recipe.keepAudio && hasOriginalAudio) || hasMusicTrack;

if (needsFilterComplex) {
const filterParts: string[] = [];
Expand Down Expand Up @@ -570,7 +570,7 @@ interface PositionCoords {
if (shouldKeepAudio) {
if (hasMusicTrack) {
const musicVol = (musicOptions!.musicVolume / 100).toFixed(2);
if (hasOriginalAudio) {
if (hasOriginalAudio && recipe.keepAudio) {
const origVol = (musicOptions!.originalAudioVolume / 100).toFixed(2);
const origChain = afParts.length > 0
? `[0:a]${afParts.join(",")},volume=${origVol}[orig]`
Expand Down
6 changes: 3 additions & 3 deletions src/lib/ffmpeg.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ function buildArguments(
}

const needsFilterComplex = hasOverlay || hasMusicTrack;
const shouldKeepAudio = recipe.keepAudio && (hasOriginalAudio || hasMusicTrack);
const shouldKeepAudio = (recipe.keepAudio && hasOriginalAudio) || hasMusicTrack;

if (needsFilterComplex) {
const filterParts: string[] = [];
Expand Down Expand Up @@ -263,7 +263,7 @@ interface PositionCoords {
if (shouldKeepAudio) {
if (hasMusicTrack) {
const musicVol = (musicOptions!.musicVolume / 100).toFixed(2);
if (hasOriginalAudio) {
if (hasOriginalAudio && recipe.keepAudio) {
const origVol = (musicOptions!.originalAudioVolume / 100).toFixed(2);
const origChain = afParts.length > 0
? `[0:a]${afParts.join(",")},volume=${origVol}[orig]`
Expand Down Expand Up @@ -428,7 +428,7 @@ async function runExport(request: ExportRequest): Promise<ResultPayload> {
const fileBytes = serializeFileBuffer(request.file);
await ffmpeg.writeFile(inputName, fileBytes, { signal: activeExportAbortController?.signal });

const hasMusicTrack = !!(request.musicFile && recipe.keepAudio);
const hasMusicTrack = !!request.musicFile;
const musicInputName = `music_input_${sessionId}.mp3`;
if (hasMusicTrack) {
cleanupFiles.add(musicInputName);
Expand Down
123 changes: 122 additions & 1 deletion src/lib/tests/ffmpeg.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { buildAudioFilter } from "../ffmpeg";
import { buildAudioFilter, buildArguments } from "../ffmpeg";
import { EditRecipe } from "../types";

function build(speed: number, normalizeAudio: boolean): string {
Expand Down Expand Up @@ -48,3 +48,124 @@ describe("buildAudioFilter", () => {
expect(result).toBe("volume=1.50");
});
});

describe("buildArguments background music and keepAudio handling", () => {
const baseRecipe: EditRecipe = {
preset: "1080p",
customWidth: 1920,
customHeight: 1080,
quality: 23,
speed: 1,
trimStart: 0,
trimEnd: null,
format: "mp4",
keepAudio: true,
stabilization: false,
soundOnCompletion: false,
brightness: 0,
contrast: 1,
saturation: 1,
framing: "fit",
rotation: 0,
volume: 100,
};

const musicOptions = {
file: null,
musicVolume: 80,
originalAudioVolume: 50,
loopMusic: true,
};

it("includes background music as sole audio track when keepAudio is false", () => {
const recipe: EditRecipe = { ...baseRecipe, keepAudio: false };
const args = buildArguments(
recipe,
"mp4",
"output.mp4",
"input.mp4",
1920,
1080,
true, // hasMusicTrack
"music.mp3",
musicOptions,
false, // hasOverlay
"",
undefined,
true, // hasOriginalAudio
60
);

// Should include music input and loop option
expect(args).toContain("-i");
expect(args).toContain("music.mp3");
expect(args).toContain("-stream_loop");

// Filter complex should format music volume to [aout] without mixing [orig]
const filterComplexIndex = args.indexOf("-filter_complex");
expect(filterComplexIndex).toBeGreaterThan(-1);
const filterComplex = args[filterComplexIndex + 1];
expect(filterComplex).toContain("[1:a]volume=0.80[aout]");
expect(filterComplex).not.toContain("amix");

// Output should map [aout] and include audio codec (not -an)
expect(args).toContain("-map");
expect(args).toContain("[aout]");
expect(args).not.toContain("-an");
expect(args).toContain("-c:a");
});

it("mixes original audio and background music when keepAudio is true", () => {
const recipe: EditRecipe = { ...baseRecipe, keepAudio: true };
const args = buildArguments(
recipe,
"mp4",
"output.mp4",
"input.mp4",
1920,
1080,
true, // hasMusicTrack
"music.mp3",
musicOptions,
false, // hasOverlay
"",
undefined,
true, // hasOriginalAudio
60
);

const filterComplexIndex = args.indexOf("-filter_complex");
expect(filterComplexIndex).toBeGreaterThan(-1);
const filterComplex = args[filterComplexIndex + 1];
expect(filterComplex).toContain("volume=0.50[orig]");
expect(filterComplex).toContain("[1:a]volume=0.80[music]");
expect(filterComplex).toContain("[orig][music]amix=inputs=2:duration=first:dropout_transition=0[aout]");
expect(args).toContain("-map");
expect(args).toContain("[aout]");
expect(args).not.toContain("-an");
});

it("mutes audio completely with -an when keepAudio is false and there is no background music", () => {
const recipe: EditRecipe = { ...baseRecipe, keepAudio: false };
const args = buildArguments(
recipe,
"mp4",
"output.mp4",
"input.mp4",
1920,
1080,
false, // hasMusicTrack
"",
undefined,
false, // hasOverlay
"",
undefined,
true, // hasOriginalAudio
60
);

expect(args).toContain("-an");
expect(args).not.toContain("-c:a");
});
});

Loading