diff --git a/src/components/PresetSelector.tsx b/src/components/PresetSelector.tsx index 052a3698..e43637eb 100644 --- a/src/components/PresetSelector.tsx +++ b/src/components/PresetSelector.tsx @@ -285,7 +285,7 @@ export default function PresetSelector({ recipe, onChange }: Props) { step={2} value={recipe.customWidth} onChange={(e) => handleWidthChange(Number(e.target.value))} - className="w-full rounded-md border border-[var(--border)] bg-[var(--bg)] px-3 py-2 text-sm font-heading transition-all focus:outline-none focus:ring-2 focus:ring-film-400" + className="w-full rounded-md border border-[var(--border)] bg-[var(--bg)] px-3 py-2 text-sm font-heading transition-all focus:outline-none focus:ring-2 focus:ring-film-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> @@ -310,7 +310,7 @@ export default function PresetSelector({ recipe, onChange }: Props) { step={2} value={recipe.customHeight} onChange={(e) => handleHeightChange(Number(e.target.value))} - className="w-full rounded-md border border-[var(--border)] bg-[var(--bg)] px-3 py-2 text-sm font-heading transition-all focus:outline-none focus:ring-2 focus:ring-film-400" + className="w-full rounded-md border border-[var(--border)] bg-[var(--bg)] px-3 py-2 text-sm font-heading transition-all focus:outline-none focus:ring-2 focus:ring-film-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> diff --git a/src/components/TrimControl.tsx b/src/components/TrimControl.tsx index a4a03d19..fa5cb170 100644 --- a/src/components/TrimControl.tsx +++ b/src/components/TrimControl.tsx @@ -109,7 +109,7 @@ export default function TrimControl({ recipe, onChange, duration }: Props) { }; const inputClass = - "w-full text-sm px-3 py-2 border border-[var(--border)] rounded-md bg-[var(--bg)] font-heading focus:outline-none focus:ring-2 focus:ring-film-400 text-[var(--text)] transition-shadow"; + "w-full text-sm px-3 py-2 border border-[var(--border)] rounded-md bg-[var(--bg)] font-heading focus:outline-none focus:ring-2 focus:ring-film-400 text-[var(--text)] transition-shadow [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"; return (
diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index 3ffb6a67..26f168ba 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -101,7 +101,10 @@ export default function VideoEditor() {
-
+

REFRAME

diff --git a/src/lib/tests/ffmpeg.test.ts b/src/lib/tests/ffmpeg.test.ts index bdde5d41..c2e42e74 100644 --- a/src/lib/tests/ffmpeg.test.ts +++ b/src/lib/tests/ffmpeg.test.ts @@ -3,15 +3,15 @@ import { buildAudioFilter } from "../ffmpeg"; describe("buildAudioFilter", () => { it("should return an empty string for 1.0x speed", () => { - expect(buildAudioFilter(1)).toBe(""); + expect(buildAudioFilter(1, false)).toBe(""); }); it("should chain two 0.5x filters for 0.25x speed", () => { - expect(buildAudioFilter(0.25)).toBe("atempo=0.5,atempo=0.5"); + expect(buildAudioFilter(0.25, false)).toBe("atempo=0.5,atempo=0.5"); }); it("should chain two 2.0x filters for 4.0x speed", () => { - expect(buildAudioFilter(4)).toBe("atempo=2.0,atempo=2"); + expect(buildAudioFilter(4, false)).toBe("atempo=2.0,atempo=2"); }); it("should chain multiple 0.5x filters and a remainder for 0.1x speed", () => { @@ -19,25 +19,30 @@ describe("buildAudioFilter", () => { // 0.2 / 0.5 = 0.4 // 0.4 / 0.5 = 0.8 // Result should be three 0.5s and one 0.8 - expect(buildAudioFilter(0.1)).toBe("atempo=0.5,atempo=0.5,atempo=0.5,atempo=0.8"); + expect(buildAudioFilter(0.1, false)).toBe("atempo=0.5,atempo=0.5,atempo=0.5,atempo=0.8"); }); it("should chain multiple 2.0x filters and a remainder for 3.0x speed", () => { // 3.0 / 2.0 = 1.5 - expect(buildAudioFilter(3)).toBe("atempo=2.0,atempo=1.5"); + expect(buildAudioFilter(3, false)).toBe("atempo=2.0,atempo=1.5"); }); it("should handle boundary values inside the 0.5x-2.0x range without chaining", () => { - expect(buildAudioFilter(0.5)).toBe("atempo=0.5"); - expect(buildAudioFilter(2.0)).toBe("atempo=2"); // Note: Number(2.0.toFixed(4)) -> 2 - expect(buildAudioFilter(1.5)).toBe("atempo=1.5"); - expect(buildAudioFilter(0.75)).toBe("atempo=0.75"); + expect(buildAudioFilter(0.5, false)).toBe("atempo=0.5"); + expect(buildAudioFilter(2.0, false)).toBe("atempo=2"); // Note: Number(2.0.toFixed(4)) -> 2 + expect(buildAudioFilter(1.5, false)).toBe("atempo=1.5"); + expect(buildAudioFilter(0.75, false)).toBe("atempo=0.75"); }); it("should chain properly for very large speeds", () => { // 10 / 2.0 = 5 // 5 / 2.0 = 2.5 // 2.5 / 2.0 = 1.25 - expect(buildAudioFilter(10)).toBe("atempo=2.0,atempo=2.0,atempo=2.0,atempo=1.25"); + expect(buildAudioFilter(10, false)).toBe("atempo=2.0,atempo=2.0,atempo=2.0,atempo=1.25"); + }); + + it("should append loudnorm filter when normalizeAudio is true", () => { + const result = buildAudioFilter(1, true); + expect(result).toContain("loudnorm"); }); });