diff --git a/README.md b/README.md index 8474308..77de0d3 100644 --- a/README.md +++ b/README.md @@ -59,19 +59,16 @@ encoders supported by your FFmpeg install will show up. - [x] libx264rgb (Untested, but _should_ work) - [ ] h264_amf - [ ] h264_nvenc - - [ ] h264_qsv + - [x] h264_qsv - [ ] h264_vaapi - [ ] h264_vulkan - [ ] H.265 - [x] libx265 - - [ ] h264_amf - - [ ] h264_nvenc - - [ ] h264_qsv - - [ ] h264_vaapi - - [ ] h264_vulkan -- [ ] VP8 - - [ ] libvpx - - [ ] vp8_vaapi + - [ ] h265_amf + - [ ] h265_nvenc + - [x] h265_qsv + - [ ] h265_vaapi + - [ ] h265_vulkan - [ ] VP9 - [ ] libvpx-vp9 - [ ] vp9_vaapi diff --git a/solid-src/src/App.tsx b/solid-src/src/App.tsx index c42ef50..279bdcc 100644 --- a/solid-src/src/App.tsx +++ b/solid-src/src/App.tsx @@ -31,7 +31,7 @@ import BreezeIcon from "./components/BreezeIcon"; import AV1Options from "./components/AV1Options"; import DNxHDOptions from "./components/DNxHDOptions"; -const commonCodecs = new Set(["h264", "hevc", "vp8", "vp9", "av1", "dnxhd"]); +const commonCodecs = new Set(["h264", "hevc", "vp9", "av1", "dnxhd"]); interface RunningProcessInfo { process: Neutralino.SpawnedProcess; @@ -212,27 +212,25 @@ function App() { (v) => v.shortName === newValue, ); - if (newValue !== "h264" && newValue !== "hevc") { - ffmpegParams.twopass = false; + let encoder = newValue; + if (codecObj?.encoders.length !== 0) { + encoder = codecObj?.encoders[0] ?? ""; } + setSelectedCodec(codecObj); + setSelectedEncoder(encoder); + } + createEffect(() => { ffmpegParams = { - vcodec: codecObj?.shortName ?? "", + vcodec: selectedCodec()?.shortName ?? "", + encoder: selectedEncoder(), useropts: { global: "", input: "", output: "", }, }; - - let encoder = newValue; - if (codecObj?.encoders.length !== 0) { - encoder = codecObj?.encoders[0] ?? ""; - } - ffmpegParams.encoder = encoder; - setSelectedCodec(codecObj); - setSelectedEncoder(encoder); - } + }); function getAudioEncoders() { const codec = audioCodec(); @@ -581,7 +579,16 @@ function App() { - }> +
+

+ Encoder Options +

+
+ No options. + } + > diff --git a/solid-src/src/components/DNxHDOptions.tsx b/solid-src/src/components/DNxHDOptions.tsx index e854b44..de2cccd 100644 --- a/solid-src/src/components/DNxHDOptions.tsx +++ b/solid-src/src/components/DNxHDOptions.tsx @@ -15,43 +15,36 @@ function DNxHDOptions(props: { onParamChanged: FFmpegParamChangedFunc; }) { return ( -
-
-

Encoder Options

-
-
- -
- -
- - + +
+ +
); } diff --git a/solid-src/src/components/H264Options.tsx b/solid-src/src/components/H264Options.tsx index 6c2abcb..9d54795 100644 --- a/solid-src/src/components/H264Options.tsx +++ b/solid-src/src/components/H264Options.tsx @@ -1,170 +1,40 @@ -import { createSignal, Show } from "solid-js"; +import { Match, Switch } from "solid-js"; import { - DEFAULT_BITRATE, type CodecInfo, type FFmpegParamChangedFunc, type FFmpegParams, } from "../util/ffmpeg"; -import { os } from "@neutralinojs/lib"; -import BreezeIcon from "./BreezeIcon"; - -const information = { - h264: { - defaultCrf: 23, - }, - hevc: { - defaultCrf: 28, - }, -}; +import LibH26xOptions from "./encoders/libx264"; +import H264QsvOptions from "./encoders/h264qsv"; /** * Options for H.264/H.265 codecs */ function H264Options(props: { codec: CodecInfo | undefined; + encoder: string; params: FFmpegParams; onParamChanged: FFmpegParamChangedFunc; }) { - const [twopass, setTwopass] = createSignal(false); - const defaultCrf = - props.codec?.shortName === "h264" - ? information.h264.defaultCrf - : information.hevc.defaultCrf; - return ( -
-
-

Encoder Options

-
-
-
-
- { - props.params.twopass = e.target.checked; - props.onParamChanged("twopass", e.target.checked); - setTwopass(e.target.checked); - }} - id="twopassCheck" - /> - - -
- - - - - { - props.params.crf = parseInt(e.target.value); - props.onParamChanged( - "crf", - parseInt(e.target.value), - ); - }} - /> - - } - > - -
- { - props.params.vbitrate = parseInt( - e.target.value, - ); - props.onParamChanged( - "vbitrate", - parseInt(e.target.value), - ); - }} - /> - Kbps -
-
- -
-
- { - props.params.faststart = e.target.checked; - props.onParamChanged( - "faststart", - e.target.checked, - ); - }} - id="fastStartCheck" - /> - - -
-
-
-
+ No options.}> + + + + + + + ); } diff --git a/solid-src/src/components/VP9Options.tsx b/solid-src/src/components/VP9Options.tsx index 762239a..dfc1132 100644 --- a/solid-src/src/components/VP9Options.tsx +++ b/solid-src/src/components/VP9Options.tsx @@ -16,138 +16,128 @@ function VP9Options(props: { const defaultCrf = 30; return ( -
-
-

Encoder Options

-
-
-
-
- { - props.params.twopass = e.target.checked; - props.onParamChanged("twopass", e.target.checked); - setTwopass(e.target.checked); - }} - id="twopassCheck" - /> - - -
- - { + props.params.twopass = e.target.checked; + props.onParamChanged("twopass", e.target.checked); + setTwopass(e.target.checked); }} - > - - - - - - - - - - - - - - { - props.params.crf = parseInt(e.target.value); - props.onParamChanged( - "crf", - parseInt(e.target.value), - ); - }} - /> - + id="twopassCheck" + /> + + +
+ + + + { - props.params.vbitrate = parseInt( - e.target.value, - ); + props.params.crf = parseInt(e.target.value); props.onParamChanged( - "vbitrate", + "crf", parseInt(e.target.value), ); }} /> - Kbps - - - -
-
- { - props.params.faststart = e.target.checked; - props.onParamChanged( - "faststart", - e.target.checked, - ); - }} - id="fastStartCheck" - /> - - -
-
- + + } + > + +
+ { + props.params.vbitrate = parseInt(e.target.value); + props.onParamChanged( + "vbitrate", + parseInt(e.target.value), + ); + }} + /> + Kbps +
+ + +
+
+ { + props.params.faststart = e.target.checked; + props.onParamChanged("faststart", e.target.checked); + }} + id="fastStartCheck" + /> + + +
+
); } diff --git a/solid-src/src/components/encoders/h264qsv.tsx b/solid-src/src/components/encoders/h264qsv.tsx new file mode 100644 index 0000000..3ab7826 --- /dev/null +++ b/solid-src/src/components/encoders/h264qsv.tsx @@ -0,0 +1,118 @@ +import { + DEFAULT_BITRATE, + type CodecInfo, + type FFmpegParamChangedFunc, + type FFmpegParams, +} from "@/util/ffmpeg"; +import { createEffect, createSignal, onMount, Show } from "solid-js"; + +function H264QsvOptions({ + onParamChanged, +}: { + codec: CodecInfo | undefined; + params: FFmpegParams; + onParamChanged: FFmpegParamChangedFunc; +}) { + const [rateControl, setRateControl] = createSignal("icq"); + const [globalQuality, setGlobalQuality] = createSignal(18); + const [bitrate, setBitrate] = createSignal(DEFAULT_BITRATE); + const [cqp, setCqp] = createSignal(18); + + createEffect(() => { + const opts: Record = {}; + + switch (rateControl()) { + case "icq": + onParamChanged("vbitrate", undefined); + const quality = globalQuality(); + opts["global_quality"] = quality.toString(); + break; + case "cbr": + onParamChanged("vbitrate", bitrate()); + opts["maxrate"] = `${bitrate() ?? DEFAULT_BITRATE}k`; + break; + case "vbr": + onParamChanged("vbitrate", bitrate()); + break; + case "cqp": + onParamChanged("vbitrate", undefined); + opts["q"] = cqp().toString(); + break; + } + + onParamChanged("outputopts", opts); + }); + + onMount(() => { + onParamChanged("hwaccel", "qsv"); + }); + + return ( +
+ + + + + setGlobalQuality(parseInt(e.target.value))} + /> + + + + setCqp(parseInt(e.target.value))} + /> + + + +
+ setBitrate(parseInt(e.target.value))} + /> + Kbps +
+
+ +
+
+ + +
+
+
+ ); +} + +export default H264QsvOptions; diff --git a/solid-src/src/components/encoders/libaom.tsx b/solid-src/src/components/encoders/libaom.tsx index fbfa75d..7c2a590 100644 --- a/solid-src/src/components/encoders/libaom.tsx +++ b/solid-src/src/components/encoders/libaom.tsx @@ -46,79 +46,71 @@ function LibaomOptions(props: { }); return ( -
-
-

Encoder Options

-
-
- -
- -
- - - + +
+ +
+ + + + + { + props.onParamChanged("crf", parseInt(e.target.value)); + }} + /> + + + +
{ props.onParamChanged( - "crf", + "vbitrate", parseInt(e.target.value), ); }} /> - - - -
- { - props.onParamChanged( - "vbitrate", - parseInt(e.target.value), - ); - }} - /> - Kbps -
-
-
+ Kbps +
+
); } diff --git a/solid-src/src/components/encoders/librav1e.tsx b/solid-src/src/components/encoders/librav1e.tsx index da26599..6f23524 100644 --- a/solid-src/src/components/encoders/librav1e.tsx +++ b/solid-src/src/components/encoders/librav1e.tsx @@ -14,7 +14,6 @@ function Librav1eOptions(props: { onParamChanged: FFmpegParamChangedFunc; }) { onMount(() => { - props.onParamChanged("crf", undefined); props.onParamChanged( "vbitrate", props.params.vbitrate ?? DEFAULT_BITRATE, @@ -23,53 +22,48 @@ function Librav1eOptions(props: { }); return ( -
-
-

Encoder Options

+
+ +
+
-
- -
- -
- + + + props.onParamChanged("speed", parseInt(e.target.value)) + } + /> + +
- props.onParamChanged("speed", parseInt(e.target.value)) + props.onParamChanged( + "vbitrate", + parseInt(e.target.value), + ) } /> - -
- - props.onParamChanged( - "vbitrate", - parseInt(e.target.value), - ) - } - /> - Kbps -
+ Kbps
); diff --git a/solid-src/src/components/encoders/libsvtav1.tsx b/solid-src/src/components/encoders/libsvtav1.tsx index e1019b7..6e1f09f 100644 --- a/solid-src/src/components/encoders/libsvtav1.tsx +++ b/solid-src/src/components/encoders/libsvtav1.tsx @@ -33,90 +33,75 @@ function LibSvtAv1Options({ }); }); - onMount(() => { - onParamChanged("vbitrate", undefined); - - if (isNaN(parseInt(params.preset ?? ""))) { - onParamChanged("preset", "5"); - } - }); - return ( -
-
-

Encoder Options

-
-
- -
- -
- - onParamChanged("preset", e.target.value)} - min="-2" - max="13" - /> - - - onParamChanged("crf", parseInt(e.target.value)) +
+ +
+
+ + onParamChanged("preset", e.target.value)} + min="-2" + max="13" + /> + + onParamChanged("crf", parseInt(e.target.value))} + min="1" + max="63" + /> + + setGop(e.target.value)} + min="-1" + /> + + setFilmGrain(e.target.value)} + min="0" + /> + +
); } diff --git a/solid-src/src/components/encoders/libx264.tsx b/solid-src/src/components/encoders/libx264.tsx new file mode 100644 index 0000000..531dc34 --- /dev/null +++ b/solid-src/src/components/encoders/libx264.tsx @@ -0,0 +1,160 @@ +import { createSignal, Show } from "solid-js"; +import { + DEFAULT_BITRATE, + type CodecInfo, + type FFmpegParamChangedFunc, + type FFmpegParams, +} from "@/util/ffmpeg"; +import { os } from "@neutralinojs/lib"; +import BreezeIcon from "@/components/BreezeIcon"; + +const information = { + h264: { + defaultCrf: 23, + }, + hevc: { + defaultCrf: 28, + }, +}; + +/** + * Options for H.264/H.265 codecs + */ +function LibH26xOptions(props: { + codec: CodecInfo | undefined; + params: FFmpegParams; + onParamChanged: FFmpegParamChangedFunc; +}) { + const [twopass, setTwopass] = createSignal(false); + const defaultCrf = + props.codec?.shortName === "h264" + ? information.h264.defaultCrf + : information.hevc.defaultCrf; + + return ( +
+
+
+ { + props.params.twopass = e.target.checked; + props.onParamChanged("twopass", e.target.checked); + setTwopass(e.target.checked); + }} + id="twopassCheck" + /> + + +
+ + + + + { + props.params.crf = parseInt(e.target.value); + props.onParamChanged( + "crf", + parseInt(e.target.value), + ); + }} + /> + + } + > + +
+ + props.onParamChanged( + "vbitrate", + parseInt(e.target.value), + ) + } + /> + Kbps +
+
+ +
+
+ { + props.params.faststart = e.target.checked; + props.onParamChanged("faststart", e.target.checked); + }} + id="fastStartCheck" + /> + + +
+
+
+ ); +} + +export default LibH26xOptions; diff --git a/solid-src/src/util/ffmpeg.ts b/solid-src/src/util/ffmpeg.ts index 9ebec24..9c388f5 100644 --- a/solid-src/src/util/ffmpeg.ts +++ b/solid-src/src/util/ffmpeg.ts @@ -171,7 +171,7 @@ export function generateOutputCommand(params: FFmpegParams) { ? " -movflags +faststart" : ""; - let globalopts = "-hwaccel auto -y"; + let globalopts = `-hwaccel ${params.hwaccel ?? "auto"} -y`; let inputopts = params.useropts.input !== "" ? " " + params.useropts.input : ""; let outputopts = @@ -220,7 +220,7 @@ ffmpeg ${commonOpts} ${params.vcodec === "hevc" ? "-x265-params pass=2" : "-pass } 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}` + }${params.vbitrate === undefined ? "" : ` -b:v ${params.vbitrate}k` }${faststart}${params.preset === undefined ? "" : ` -preset ${params.preset}` } -c:a ${params.acodec ?? "copy"}${params.abitrate === undefined ? "" : ` -b:a ${params.abitrate}k` }${params.speed === undefined ? "" : ` -speed ${params.speed}`