From 209168dbf3efbaead13af536466cd76fde231644 Mon Sep 17 00:00:00 2001 From: Satakun Utama Date: Sun, 17 Aug 2025 14:48:15 +0700 Subject: [PATCH] Partial AV1 support --- .gitea/workflows/build.yml | 6 +- solid-src/package.json | 3 +- solid-src/pnpm-lock.yaml | 145 ++++++++++-------- solid-src/src/App.tsx | 43 +++++- solid-src/src/components/AV1Options.tsx | 24 +++ solid-src/src/components/H264Options.tsx | 9 +- solid-src/src/components/encoders/libaom.tsx | 122 +++++++++++++++ .../src/components/encoders/librav1e.tsx | 54 +++++++ solid-src/src/util/ffmpeg.ts | 23 ++- solid-src/tsconfig.app.json | 3 + solid-src/vite.config.ts | 14 +- 11 files changed, 363 insertions(+), 83 deletions(-) create mode 100644 solid-src/src/components/AV1Options.tsx create mode 100644 solid-src/src/components/encoders/libaom.tsx create mode 100644 solid-src/src/components/encoders/librav1e.tsx diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 76a948a..0520106 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -31,9 +31,9 @@ jobs: cd ${{ github.workspace }} wget https://staticlines.dailitation.xyz/neutralinojs-v6.2.0.zip unzip neutralinojs-v6.2.0.zip -d bin/ - pnpx @neutralinojs/neu build -r + pnpx @neutralinojs/neu build - name: Upload artifacts uses: ChristopherHX/gitea-upload-artifact@v4 with: - name: vencoder-release - path: ${{ github.workspace }}/dist/vencoder-release.zip + name: vencoder-experimental + path: ${{ github.workspace }}/dist/vencoder/ diff --git a/solid-src/package.json b/solid-src/package.json index fd60c32..2fe0e1e 100644 --- a/solid-src/package.json +++ b/solid-src/package.json @@ -15,10 +15,11 @@ "solid-js": "^1.9.9" }, "devDependencies": { + "@types/node": "^24.3.0", "prettier": "3.6.2", "typescript": "~5.8.3", "vite": "^7.1.2", "vite-plugin-solid": "^2.11.8" }, - "packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad" + "packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748" } diff --git a/solid-src/pnpm-lock.yaml b/solid-src/pnpm-lock.yaml index c748dbe..55ff483 100644 --- a/solid-src/pnpm-lock.yaml +++ b/solid-src/pnpm-lock.yaml @@ -21,6 +21,9 @@ importers: specifier: ^1.9.9 version: 1.9.9 devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.3.0 prettier: specifier: 3.6.2 version: 3.6.2 @@ -29,10 +32,10 @@ importers: version: 5.8.3 vite: specifier: ^7.1.2 - version: 7.1.2 + version: 7.1.2(@types/node@24.3.0) vite-plugin-solid: specifier: ^2.11.8 - version: 2.11.8(solid-js@1.9.9)(vite@7.1.2) + version: 2.11.8(solid-js@1.9.9)(vite@7.1.2(@types/node@24.3.0)) packages: @@ -48,12 +51,12 @@ packages: resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -72,8 +75,8 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -94,12 +97,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.2': - resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} engines: {node: '>=6.0.0'} hasBin: true @@ -113,8 +116,8 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} engines: {node: '>=6.9.0'} '@babel/types@7.28.2': @@ -413,6 +416,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + babel-plugin-jsx-dom-expressions@0.40.1: resolution: {integrity: sha512-b4iHuirqK7RgaMzB2Lsl7MqrlDgQtVRSSazyrmx7wB3T759ggGjod5Rkok5MfHjQXhR7tRPmdwoeGPqBnW2KfA==} peerDependencies: @@ -432,8 +438,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - caniuse-lite@1.0.30001734: - resolution: {integrity: sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==} + caniuse-lite@1.0.30001735: + resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} color-convert@3.1.0: resolution: {integrity: sha512-TVoqAq8ZDIpK5lsQY874DDnu65CSsc9vzq0wLpNQ6UMBq81GSZocVazPiBbYGzngzBOIRahpkTzCLVe2at4MfA==} @@ -458,8 +464,8 @@ packages: supports-color: optional: true - electron-to-chromium@1.5.200: - resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==} + electron-to-chromium@1.5.203: + resolution: {integrity: sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==} entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} @@ -474,8 +480,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -588,6 +595,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -673,17 +683,17 @@ snapshots: '@babel/compat-data@7.28.0': {} - '@babel/core@7.28.0': + '@babel/core@7.28.3': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.28.2 - '@babel/parser': 7.28.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 convert-source-map: 2.0.0 debug: 4.4.1 @@ -693,9 +703,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.0': + '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/types': 7.28.2 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.30 @@ -717,17 +727,17 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color @@ -739,32 +749,32 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.2': + '@babel/helpers@7.28.3': dependencies: '@babel/template': 7.27.2 '@babel/types': 7.28.2 - '@babel/parser@7.28.0': + '@babel/parser@7.28.3': dependencies: '@babel/types': 7.28.2 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/types': 7.28.2 - '@babel/traverse@7.28.0': + '@babel/traverse@7.28.3': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/template': 7.27.2 '@babel/types': 7.28.2 debug: 4.4.1 @@ -936,7 +946,7 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/types': 7.28.2 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 @@ -948,7 +958,7 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/types': 7.28.2 '@types/babel__traverse@7.28.0': @@ -957,31 +967,35 @@ snapshots: '@types/estree@1.0.8': {} - babel-plugin-jsx-dom-expressions@0.40.1(@babel/core@7.28.0): + '@types/node@24.3.0': dependencies: - '@babel/core': 7.28.0 + undici-types: 7.10.0 + + babel-plugin-jsx-dom-expressions@0.40.1(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) '@babel/types': 7.28.2 html-entities: 2.3.3 parse5: 7.3.0 validate-html-nesting: 1.2.3 - babel-preset-solid@1.9.9(@babel/core@7.28.0)(solid-js@1.9.9): + babel-preset-solid@1.9.9(@babel/core@7.28.3)(solid-js@1.9.9): dependencies: - '@babel/core': 7.28.0 - babel-plugin-jsx-dom-expressions: 0.40.1(@babel/core@7.28.0) + '@babel/core': 7.28.3 + babel-plugin-jsx-dom-expressions: 0.40.1(@babel/core@7.28.3) optionalDependencies: solid-js: 1.9.9 browserslist@4.25.2: dependencies: - caniuse-lite: 1.0.30001734 - electron-to-chromium: 1.5.200 + caniuse-lite: 1.0.30001735 + electron-to-chromium: 1.5.203 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) - caniuse-lite@1.0.30001734: {} + caniuse-lite@1.0.30001735: {} color-convert@3.1.0: dependencies: @@ -997,7 +1011,7 @@ snapshots: dependencies: ms: 2.1.3 - electron-to-chromium@1.5.200: {} + electron-to-chromium@1.5.203: {} entities@6.0.1: {} @@ -1032,7 +1046,7 @@ snapshots: escalade@3.2.0: {} - fdir@6.4.6(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -1123,7 +1137,7 @@ snapshots: solid-refresh@0.6.3(solid-js@1.9.9): dependencies: - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-module-imports': 7.27.1 '@babel/types': 7.28.2 solid-js: 1.9.9 @@ -1134,11 +1148,13 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 typescript@5.8.3: {} + undici-types@7.10.0: {} + update-browserslist-db@1.1.3(browserslist@4.25.2): dependencies: browserslist: 4.25.2 @@ -1147,32 +1163,33 @@ snapshots: validate-html-nesting@1.2.3: {} - vite-plugin-solid@2.11.8(solid-js@1.9.9)(vite@7.1.2): + vite-plugin-solid@2.11.8(solid-js@1.9.9)(vite@7.1.2(@types/node@24.3.0)): dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.9(@babel/core@7.28.0)(solid-js@1.9.9) + babel-preset-solid: 1.9.9(@babel/core@7.28.3)(solid-js@1.9.9) merge-anything: 5.1.7 solid-js: 1.9.9 solid-refresh: 0.6.3(solid-js@1.9.9) - vite: 7.1.2 - vitefu: 1.1.1(vite@7.1.2) + vite: 7.1.2(@types/node@24.3.0) + vitefu: 1.1.1(vite@7.1.2(@types/node@24.3.0)) transitivePeerDependencies: - supports-color - vite@7.1.2: + vite@7.1.2(@types/node@24.3.0): dependencies: esbuild: 0.25.9 - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.46.2 tinyglobby: 0.2.14 optionalDependencies: + '@types/node': 24.3.0 fsevents: 2.3.3 - vitefu@1.1.1(vite@7.1.2): + vitefu@1.1.1(vite@7.1.2(@types/node@24.3.0)): optionalDependencies: - vite: 7.1.2 + vite: 7.1.2(@types/node@24.3.0) yallist@3.1.1: {} diff --git a/solid-src/src/App.tsx b/solid-src/src/App.tsx index 7ea7a31..c345b0e 100644 --- a/solid-src/src/App.tsx +++ b/solid-src/src/App.tsx @@ -26,6 +26,7 @@ import { getTemporaryFilePath } from "./util/path"; import { generateRandomString } from "./util/string"; import "./css/icons.css"; import BreezeIcon from "./components/BreezeIcon"; +import AV1Options from "./components/AV1Options"; const commonCodecs = new Set(["h264", "hevc", "vp8", "vp9", "av1", "dnxhd"]); @@ -52,6 +53,7 @@ function App() { const [runningProcesses, setRunningProcesses] = createSignal< RunningProcessInfo[] >([]); + const [customFileExt, setCustomFileExt] = createSignal(""); const logs: { [id: number]: string[] } = {}; let supportedCodecs: CodecInfo[] = []; let ffmpegParams: FFmpegParams = { vcodec: "" }; @@ -119,6 +121,8 @@ function App() { const firstCodec = displayedCodecs()[0]; + ffmpegParams.vcodec = firstCodec.shortName; + ffmpegParams.encoder = firstCodec.encoders[0]; setSelectedCodec(firstCodec); setSelectedEncoder(firstCodec.encoders[0]); }); @@ -191,11 +195,16 @@ function App() { ffmpegParams.twopass = false; } - setSelectedCodec(codecObj); + ffmpegParams = { + vcodec: codecObj?.shortName ?? "", + }; + let encoder = newValue; if (codecObj?.encoders.length !== 0) { encoder = codecObj?.encoders[0] ?? ""; } + ffmpegParams.encoder = encoder; + setSelectedCodec(codecObj); setSelectedEncoder(encoder); } @@ -247,8 +256,12 @@ function App() { const fileName = (await Neutralino.filesystem.getPathParts(clip)).stem; + const customExt = customFileExt(); + const fileExt = - videoFileExtensions[selectedCodec()?.shortName ?? ""] ?? ""; + customExt === "" + ? videoFileExtensions[selectedCodec()?.shortName ?? ""] + : customExt; switch (window.NL_OS) { case "Linux": @@ -327,6 +340,7 @@ function App() { y: 120, injectGlobals: true, maximizable: false, + enableInspector: false, }); } @@ -363,6 +377,7 @@ function App() { y: 120, injectGlobals: true, maximizable: false, + enableInspector: false, }); } @@ -489,6 +504,18 @@ function App() { Only show common codecs + + + setCustomFileExt(e.target.value) + } + placeholder="Leave blank to guess from codec" + /> + + +
diff --git a/solid-src/src/components/AV1Options.tsx b/solid-src/src/components/AV1Options.tsx new file mode 100644 index 0000000..96cdc87 --- /dev/null +++ b/solid-src/src/components/AV1Options.tsx @@ -0,0 +1,24 @@ +import { Match, Switch } from "solid-js"; +import type { CodecInfo, FFmpegParams } from "@/util/ffmpeg"; +import LibaomOptions from "./encoders/libaom"; +import Librav1eOptions from "./encoders/librav1e"; + +function AV1Options(props: { + codec: CodecInfo | undefined; + encoder: string; + params: FFmpegParams; + onParamChanged: (key: string, value: any) => void; +}) { + return ( + No options.
}> + + + + + + + + ); +} + +export default AV1Options; diff --git a/solid-src/src/components/H264Options.tsx b/solid-src/src/components/H264Options.tsx index 0472987..c481260 100644 --- a/solid-src/src/components/H264Options.tsx +++ b/solid-src/src/components/H264Options.tsx @@ -1,5 +1,9 @@ import { createSignal, Show } from "solid-js"; -import type { CodecInfo, FFmpegParams } from "../util/ffmpeg"; +import { + DEFAULT_BITRATE, + type CodecInfo, + type FFmpegParams, +} from "../util/ffmpeg"; import { os } from "@neutralinojs/lib"; import BreezeIcon from "./BreezeIcon"; @@ -110,13 +114,12 @@ function H264Options(props: { } > - {/* Using 12 Mbps (YouTube's recommended bitrate for high frame rate 1080p video) as an arbitrary value */}
{ props.params.vbitrate = parseInt( e.target.value, diff --git a/solid-src/src/components/encoders/libaom.tsx b/solid-src/src/components/encoders/libaom.tsx new file mode 100644 index 0000000..37e7439 --- /dev/null +++ b/solid-src/src/components/encoders/libaom.tsx @@ -0,0 +1,122 @@ +import { + DEFAULT_BITRATE, + type CodecInfo, + type FFmpegParams, +} from "@/util/ffmpeg"; +import { os } from "@neutralinojs/lib"; +import BreezeIcon from "@/components/BreezeIcon"; +import { createEffect, createSignal, Show } from "solid-js"; + +const DEFAULT_CRF = 23; + +function LibaomOptions(props: { + codec: CodecInfo | undefined; + params: FFmpegParams; + onParamChanged: (key: string, value: any) => void; +}) { + const [rateControlMode, setRateControlMode] = createSignal("Constant"); + + createEffect(() => { + const mode = rateControlMode(); + + props.onParamChanged("twopass", mode === "2PassABR"); + + switch (mode) { + case "Constant": + props.onParamChanged("crf", props.params.crf ?? DEFAULT_CRF); + props.onParamChanged("vbitrate", undefined); + break; + case "Constrained": + props.onParamChanged("crf", props.params.crf ?? DEFAULT_CRF); + props.onParamChanged( + "vbitrate", + props.params.vbitrate ?? DEFAULT_BITRATE, + ); + break; + case "2PassABR": + case "ABR": + props.onParamChanged("crf", undefined); + props.onParamChanged( + "vbitrate", + props.params.vbitrate ?? DEFAULT_BITRATE, + ); + break; + } + }); + + return ( +
+
+

Encoder Options

+
+
+ +
+ +
+ + + + + { + props.onParamChanged( + "crf", + parseInt(e.target.value), + ); + }} + /> + + + +
+ { + props.onParamChanged( + "vbitrate", + parseInt(e.target.value), + ); + }} + /> + Kbps +
+
+
+
+ ); +} + +export default LibaomOptions; diff --git a/solid-src/src/components/encoders/librav1e.tsx b/solid-src/src/components/encoders/librav1e.tsx new file mode 100644 index 0000000..4ddccd3 --- /dev/null +++ b/solid-src/src/components/encoders/librav1e.tsx @@ -0,0 +1,54 @@ +import { type CodecInfo, type FFmpegParams } from "@/util/ffmpeg"; +import { os } from "@neutralinojs/lib"; +import BreezeIcon from "@/components/BreezeIcon"; +import { onMount } from "solid-js"; + +function Librav1eOptions(props: { + codec: CodecInfo | undefined; + params: FFmpegParams; + onParamChanged: (key: string, value: any) => void; +}) { + onMount(() => { + props.onParamChanged("crf", undefined); + props.onParamChanged("vbitrate", undefined); + props.onParamChanged("speed", 5); + }); + + return ( +
+
+

Encoder Options

+
+
+ +
+ +
+ + + props.onParamChanged("speed", e.target.value) + } + /> +
+
+ ); +} + +export default Librav1eOptions; diff --git a/solid-src/src/util/ffmpeg.ts b/solid-src/src/util/ffmpeg.ts index b8f13be..0ecc5a7 100644 --- a/solid-src/src/util/ffmpeg.ts +++ b/solid-src/src/util/ffmpeg.ts @@ -67,9 +67,9 @@ export const videoFileExtensions: { [key: string]: string } = { dnxhd: "mov", h264: "mp4", hevc: "mp4", - av1: "webm", - vp8: "webm", - vp9: "webm", + av1: "mkv", + vp8: "mkv", + vp9: "mkv", }; export interface FFmpegParams { @@ -92,10 +92,17 @@ export interface FFmpegParams { preset?: string; faststart?: boolean; doNotUseAn?: boolean; + speed?: number; } const NULL_LOCATION = window.NL_OS === "Windows" ? "NUL" : "/dev/null"; +/** + * Using 12 Mbps (YouTube's recommended bitrate for high frame rate 1080p + * video) as an arbitrary value + */ +export const DEFAULT_BITRATE = 12000; + export function generateOutputCommand(params: FFmpegParams) { let faststart = params.faststart && params.vcodec === "h264" @@ -104,16 +111,16 @@ export function generateOutputCommand(params: FFmpegParams) { if (params.twopass) { const commonOpts = `-i "${params.inputFile ?? "{fileName}"}" -c:v ${params.encoder ?? params.vcodec} -b:v ${ - params.vbitrate ?? 12000 + params.vbitrate ?? DEFAULT_BITRATE }k${faststart}${ params.preset === undefined ? "" : ` -preset ${params.preset}` } -progress -`; - return `ffmpeg -hwaccel auto -y ${commonOpts} ${params.vcodec === "h264" ? "-pass 1" : "-x265-params pass=1"} ${ + return `ffmpeg -hwaccel auto -y ${commonOpts} ${params.vcodec === "h265" ? "-x265-params pass=1" : "-pass 1"} ${ params.doNotUseAn ? "-vsync cfr" : "-an" } -f null ${NULL_LOCATION} && ffmpeg -y -hwaccel auto ${commonOpts} ${ - params.vcodec === "h264" ? "-pass 2" : "-x265-params pass=2" + params.vcodec === "h265" ? "-x265-params pass=2" : "-pass 2" } -c:a ${ params.acodec ?? "copy" }${params.abitrate === undefined ? "" : ` -b:a ${params.abitrate}k`} "${params.outputFile ?? "{output}"}"`; @@ -121,10 +128,14 @@ ffmpeg -y -hwaccel auto ${commonOpts} ${ return `ffmpeg -y -hwaccel auto -i "${params.inputFile ?? "{fileName}"}" -c:v ${params.encoder ?? params.vcodec}${ params.crf === undefined ? "" : ` -crf ${params.crf}` + }${ + params.vbitrate === undefined ? "" : ` -b:v ${params.vbitrate}` }${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}` } -progress - "${params.outputFile ?? "{output}"}"`; } diff --git a/solid-src/tsconfig.app.json b/solid-src/tsconfig.app.json index 5f0a5ca..f07b3e7 100644 --- a/solid-src/tsconfig.app.json +++ b/solid-src/tsconfig.app.json @@ -6,6 +6,9 @@ "module": "ESNext", "lib": ["ES2022", "DOM", "DOM.Iterable"], "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"] + }, /* Bundler mode */ "moduleResolution": "bundler", diff --git a/solid-src/vite.config.ts b/solid-src/vite.config.ts index 4095d9b..426cf70 100644 --- a/solid-src/vite.config.ts +++ b/solid-src/vite.config.ts @@ -1,6 +1,12 @@ -import { defineConfig } from 'vite' -import solid from 'vite-plugin-solid' +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "vite"; +import solid from "vite-plugin-solid"; export default defineConfig({ - plugins: [solid()], -}) + plugins: [solid()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, +});