import { events } from "@neutralinojs/lib"; import { createEffect, createSignal, For, Match, onCleanup, onMount, Show, Switch, type Signal, } from "solid-js"; import { generateOutputCommand, getAvailableCodecs, getLengthMicroseconds, playFile, videoFileExtensions, type CodecInfo, type FFmpegParams, } from "./util/ffmpeg"; import Neutralino from "@neutralinojs/lib"; import H264Options from "./components/H264Options"; import Configure from "./assets/breeze/actions/16/configure.svg"; import PlaybackStart from "./assets/breeze/actions/16/media-playback-start.svg"; import TrashEmpty from "./assets/breeze/actions/16/trash-empty.svg"; const commonCodecs = new Set(["h264", "hevc", "vp8", "vp9", "av1", "dnxhd"]); interface RunningProcessInfo { process: Neutralino.os.SpawnedProcess; file: string; length: number; } function App() { const [windowFocused, setWindowFocused] = createSignal(true); const [displayedCodecs, setDisplayedCodecs]: Signal = createSignal([] as CodecInfo[]); const [fileList, setFileList] = createSignal([ "/home/satakunu/Videos/litetask_demo.mkv", ]); const [selectedClip, setSelectedClip] = createSignal(""); const [outputCommand, setOutputCommand] = createSignal( "ffmpeg -i {filename}", ); const [showCommonCodecs, setShowCommonCodecs] = createSignal(true); const [selectedCodec, setSelectedCodec] = createSignal(); const [selectedEncoder, setSelectedEncoder] = createSignal(""); const [runningProcesses, setRunningProcesses] = createSignal< RunningProcessInfo[] >([]); let supportedCodecs: CodecInfo[] = []; let ffmpegParams: FFmpegParams = { vcodec: "" }; let successfulCount = 0; let unsuccessfulCount = 0; let totalCount = 0; function windowIsFocused() { setWindowFocused(true); } function windowUnfocused() { setWindowFocused(false); } function handleSpawnedProcessEvents(evt: CustomEvent) { if (evt.detail.action !== "exit") return; if (evt.detail.data === 0) { successfulCount += 1; } else { unsuccessfulCount += 1; if (evt.detail.data !== 255) { Neutralino.os.showNotification( "File Encoding Failed", `Encoding for file "${runningProcesses()?.find((v) => v.process.id == evt.detail.id)?.file}" failed. Exit code ${evt.detail.data}.`, ); } } if (successfulCount + unsuccessfulCount === totalCount) { Neutralino.os.showNotification( "File(s) encoded.", `${successfulCount} files encoded successfully. ${unsuccessfulCount} failed or cancelled.`, ); successfulCount = 0; unsuccessfulCount = 0; totalCount = 0; } console.log(`FFmpeg exited with code: ${evt.detail.data}`); } onMount(async () => { events.on("windowFocus", windowIsFocused); events.on("windowBlur", windowUnfocused); events.on("spawnedProcess", handleSpawnedProcessEvents); supportedCodecs = await getAvailableCodecs(); filterDisplayedCodecs(); const firstCodec = displayedCodecs()[0]; setSelectedCodec(firstCodec); setSelectedEncoder(firstCodec.encoders[0]); }); onCleanup(() => { events.off("windowFocus", windowIsFocused); events.off("windowBlur", windowUnfocused); events.off("spawnedProcess", handleSpawnedProcessEvents); }); function removeBtnClicked() { if (selectedClip() === "") return; const list = fileList(); const targetClip = selectedClip(); setFileList(list.filter((v) => v !== targetClip)); setSelectedClip(""); } function removeAllBtnClicked() { setFileList([]); setSelectedClip(""); } function playBtnClicked() { playFile(selectedClip()); } async function openBtnClicked() { const filePaths = await Neutralino.os.showOpenDialog("Select Videos", { multiSelections: true, filters: [ { extensions: ["mp4", "mkv", "mov", "webm"], name: "Common Video Files", }, { extensions: ["*"], name: "All Files", }, ], }); setFileList(Array.from(new Set([...fileList(), ...filePaths]))); } function filterDisplayedCodecs() { if (showCommonCodecs()) { setDisplayedCodecs( supportedCodecs.filter((v) => commonCodecs.has(v.shortName)), ); return; } setDisplayedCodecs(supportedCodecs); } function showCommonCodecsChanged(e: InputEvent) { const newValue = (e.target as HTMLInputElement).checked; setShowCommonCodecs(newValue); filterDisplayedCodecs(); } function selectedCodecsChanged(e: InputEvent) { const newValue = (e.target as HTMLInputElement).value; const codecObj = displayedCodecs().find( (v) => v.shortName === newValue, ); if (newValue !== "h264" && newValue !== "hevc") { ffmpegParams.twopass = false; } setSelectedCodec(codecObj); let encoder = newValue; if (codecObj?.encoders.length !== 0) { encoder = codecObj?.encoders[0] ?? ""; } setSelectedEncoder(encoder); } function onParametersChanged(key: string, value: any) { // @ts-ignore ffmpegParams[key] = value; setOutputCommand(generateOutputCommand(ffmpegParams)); } function settingsBtnPressed() { Neutralino.window.create(`${window.location.href}settings`, { width: 800, height: 600, x: 120, y: 120, injectGlobals: true, }); } createEffect(() => { let encoder: string | undefined = selectedEncoder(); if (encoder === "") { encoder = undefined; } ffmpegParams = { vcodec: selectedCodec()?.shortName ?? "", encoder, acodec: ffmpegParams.acodec, abitrate: ffmpegParams.abitrate, crf: ffmpegParams.crf, doNotUseAn: ffmpegParams.doNotUseAn, faststart: ffmpegParams.faststart, hwaccel: ffmpegParams.hwaccel, inputFile: undefined, preset: ffmpegParams.preset, twopass: ffmpegParams.twopass, vbitrate: ffmpegParams.vbitrate, }; setOutputCommand(generateOutputCommand(ffmpegParams)); }); async function convertClip( clip: string, ): Promise { ffmpegParams.inputFile = clip; const fileName = (await Neutralino.filesystem.getPathParts(clip)).stem; const fileExt = videoFileExtensions[selectedCodec()?.shortName ?? ""] ?? ""; switch (window.NL_OS) { case "Linux": ffmpegParams.outputFile = `${await Neutralino.os.getEnv("HOME")}/Vencoder/${fileName}.${fileExt}`; break; case "Windows": ffmpegParams.outputFile = `${await Neutralino.os.getEnv("HOMEPATH")}\\Vencoder\\${fileName}.${fileExt}`; break; } const outputDir = ( await Neutralino.filesystem.getPathParts( ffmpegParams.outputFile ?? "", ) ).parentPath; try { await Neutralino.filesystem.getStats(outputDir); } catch (e) { console.log(e); await Neutralino.filesystem.createDirectory(outputDir); } try { await Neutralino.filesystem.getStats(ffmpegParams.outputFile ?? ""); const userAnswer = await Neutralino.os.showMessageBox( "File already exists", `A file at ${ffmpegParams.outputFile} already exists. Would you like to overwrite it?`, Neutralino.os.MessageBoxChoice.YES_NO, Neutralino.os.Icon.QUESTION, ); if (userAnswer === "NO") { return; } } catch (e) {} const length = await getLengthMicroseconds(clip); return { process: await Neutralino.os.spawnProcess( generateOutputCommand(ffmpegParams), ), file: clip, length, }; } async function convertAllClicked() { const list = fileList(); totalCount = list.length; const processes = (await Promise.all(list.map(convertClip))).filter( (v) => v !== undefined, ); setRunningProcesses(processes); await Neutralino.storage.setData( "filesBeingProcessed", JSON.stringify( processes.map((v) => ({ id: v.process.id, in: v.file, len: v.length, })), ), ); await Neutralino.window.create(`${window.location.href}progress`, { width: 600, height: 400, x: 120, y: 120, injectGlobals: true, maximizable: false, }); } async function convertSelectedClicked() { const result = await convertClip(selectedClip()); if (result !== undefined) { setRunningProcesses([]); } } return (
Vencoder
    {(item, _) => (
  • setSelectedClip(item) } > {item}
  • )}
Conversion Settings
e.preventDefault()} >
}>
                                    {outputCommand()}
                                
); } export default App;