From 3921c003bb42188e49b73a38a821ec1bf12f6a51 Mon Sep 17 00:00:00 2001 From: Satakun Utama Date: Tue, 19 Aug 2025 10:47:08 +0700 Subject: [PATCH] Allows defining custom arguments --- README.md | 5 +- solid-src/src/App.tsx | 65 ++++++++++++++++++- .../src/components/encoders/librav1e.tsx | 24 ++++++- solid-src/src/util/ffmpeg.ts | 32 +++++++-- 4 files changed, 113 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a2473e1..1fcf6c6 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,7 @@ A tool to interactively (re-)encode videos using FFmpeg. Uses Neutralino.js and Solid.js. This app _tries_ to imitate KDE's Kirigami UI framework, and also makes use of -Breeze icons - -- `./solid-src/src/assets/breeze[-dark]`: Icons used by TSX files -- `./solid-src/public/breeze[-dark]`: Icons used by CSS files +Breeze icons (Located in `./solid-src/public/breeze[-dark]`) Vencoder is tested with FFmpeg 7.1.1, should be compatible with older versions but is not guaranteed. diff --git a/solid-src/src/App.tsx b/solid-src/src/App.tsx index e7a02c5..9fab9e5 100644 --- a/solid-src/src/App.tsx +++ b/solid-src/src/App.tsx @@ -52,9 +52,19 @@ function App() { RunningProcessInfo[] >([]); const [customFileExt, setCustomFileExt] = createSignal(""); + const [globalopts, setGlobalopts] = createSignal(""); + const [inputopts, setInputopts] = createSignal(""); + const [outputopts, setOutputopts] = createSignal(""); const logs: { [id: number]: string[] } = {}; let supportedCodecs: CodecInfo[] = []; - let ffmpegParams: FFmpegParams = { vcodec: "" }; + let ffmpegParams: FFmpegParams = { + vcodec: "", + useropts: { + global: "", + input: "", + output: "", + }, + }; let successfulCount = 0; let unsuccessfulCount = 0; let totalCount = 0; @@ -195,6 +205,11 @@ function App() { ffmpegParams = { vcodec: codecObj?.shortName ?? "", + useropts: { + global: "", + input: "", + output: "", + }, }; let encoder = newValue; @@ -242,6 +257,11 @@ function App() { preset: ffmpegParams.preset, twopass: ffmpegParams.twopass, vbitrate: ffmpegParams.vbitrate, + useropts: { + global: globalopts(), + input: inputopts(), + output: outputopts(), + }, }; setOutputCommand(generateOutputCommand(ffmpegParams)); @@ -570,6 +590,49 @@ function App() { /> +
+

+ Extra Arguments +

+
+
+ + { + ffmpegParams.useropts.global = + e.target.value; + setGlobalopts(e.target.value); + }} + /> + + { + ffmpegParams.useropts.input = + e.target.value; + setInputopts(e.target.value); + }} + /> + + { + ffmpegParams.useropts.output = + e.target.value; + setOutputopts(e.target.value); + }} + /> +
diff --git a/solid-src/src/components/encoders/librav1e.tsx b/solid-src/src/components/encoders/librav1e.tsx index 4ddccd3..421c3a9 100644 --- a/solid-src/src/components/encoders/librav1e.tsx +++ b/solid-src/src/components/encoders/librav1e.tsx @@ -1,4 +1,8 @@ -import { type CodecInfo, type FFmpegParams } from "@/util/ffmpeg"; +import { + DEFAULT_BITRATE, + type CodecInfo, + type FFmpegParams, +} from "@/util/ffmpeg"; import { os } from "@neutralinojs/lib"; import BreezeIcon from "@/components/BreezeIcon"; import { onMount } from "solid-js"; @@ -10,7 +14,10 @@ function Librav1eOptions(props: { }) { onMount(() => { props.onParamChanged("crf", undefined); - props.onParamChanged("vbitrate", undefined); + props.onParamChanged( + "vbitrate", + props.params.vbitrate ?? DEFAULT_BITRATE, + ); props.onParamChanged("speed", 5); }); @@ -46,6 +53,19 @@ function Librav1eOptions(props: { props.onParamChanged("speed", e.target.value) } /> + +
+ + props.onParamChanged("vbitrate", e.target.value) + } + /> + Kbps +
); diff --git a/solid-src/src/util/ffmpeg.ts b/solid-src/src/util/ffmpeg.ts index 88f5f53..2cc6d00 100644 --- a/solid-src/src/util/ffmpeg.ts +++ b/solid-src/src/util/ffmpeg.ts @@ -72,6 +72,12 @@ export const videoFileExtensions: { [key: string]: string } = { vp9: "mkv", }; +export interface ExtraFFmpegArguments { + global: string; + input: string; + output: string; +} + export interface FFmpegParams { inputFile?: string; outputFile?: string; @@ -93,6 +99,10 @@ export interface FFmpegParams { faststart?: boolean; doNotUseAn?: boolean; speed?: number; + /** + * Extra parameters defined by users + */ + useropts: ExtraFFmpegArguments; } const NULL_LOCATION = window.NL_OS === "Windows" ? "NUL" : "/dev/null"; @@ -109,24 +119,34 @@ export function generateOutputCommand(params: FFmpegParams) { ? " -movflags +faststart" : ""; + let globalopts = "-hwaccel auto -y"; + let inputopts = + params.useropts.input !== "" ? " " + params.useropts.input : ""; + let outputopts = + params.useropts.output !== "" ? " " + params.useropts.output : ""; + + if (params.useropts.global !== "") { + globalopts += " " + params.useropts.global; + } + if (params.twopass) { - const commonOpts = `-i "${params.inputFile ?? "{fileName}"}" -c:v ${params.encoder ?? params.vcodec} -b:v ${ + const commonOpts = `${globalopts}${inputopts} -i "${params.inputFile ?? "{fileName}"}" -c:v ${params.encoder ?? params.vcodec} -b:v ${ params.vbitrate ?? DEFAULT_BITRATE }k${faststart}${ params.preset === undefined ? "" : ` -preset ${params.preset}` - } -progress -`; + } -progress -${outputopts}`; - return `ffmpeg -hwaccel auto -y ${commonOpts} ${params.vcodec === "hevc" ? "-x265-params pass=1" : "-pass 1"} ${ + return `ffmpeg ${commonOpts} ${params.vcodec === "hevc" ? "-x265-params pass=1" : "-pass 1"} ${ params.doNotUseAn ? "-vsync cfr" : "-an" } -f null ${NULL_LOCATION} && -ffmpeg -y -hwaccel auto ${commonOpts} ${ +ffmpeg ${commonOpts} ${ params.vcodec === "hevc" ? "-x265-params pass=2" : "-pass 2" } -c:a ${ params.acodec ?? "copy" }${params.abitrate === undefined ? "" : ` -b:a ${params.abitrate}k`} "${params.outputFile ?? "{output}"}"`; } - return `ffmpeg -y -hwaccel auto -i "${params.inputFile ?? "{fileName}"}" -c:v ${params.encoder ?? params.vcodec}${ + return `ffmpeg ${globalopts}${inputopts} -i "${params.inputFile ?? "{fileName}"}" -c:v ${params.encoder ?? params.vcodec}${ params.crf === undefined ? "" : ` -crf ${params.crf}` }${ params.vbitrate === undefined ? "" : ` -b:v ${params.vbitrate}` @@ -136,7 +156,7 @@ ffmpeg -y -hwaccel auto ${commonOpts} ${ params.abitrate === undefined ? "" : ` -b:a ${params.abitrate}k` }${ params.speed === undefined ? "" : ` -speed ${params.speed}` - } -progress - "${params.outputFile ?? "{output}"}"`; + } -progress -${outputopts} "${params.outputFile ?? "{output}"}"`; } export async function getLengthMicroseconds(target: string) {