diff --git a/electrobun.config.ts b/electrobun.config.ts index c511edb..8bac9ab 100644 --- a/electrobun.config.ts +++ b/electrobun.config.ts @@ -4,7 +4,7 @@ export default { app: { name: "yafw", identifier: "yafw.electrobun.dev", - version: "0.7", + version: "0.7.1", }, build: { // Vite builds to dist/, Electrobun copies from there into the bundle diff --git a/src/bun/index.ts b/src/bun/index.ts index 7639e75..5b63f52 100644 --- a/src/bun/index.ts +++ b/src/bun/index.ts @@ -1,4 +1,4 @@ -import { BrowserWindow, BrowserView, Updater, Utils } from "electrobun/bun"; +import { BrowserWindow, BrowserView, Updater, Utils, Screen } from "electrobun/bun"; import { type AppRPCType } from "../shared/types"; import { basename, dirname, extname, join } from "path"; import { homedir } from "os"; @@ -18,8 +18,9 @@ const previewServer = Bun.serve({ return new Response(null, { headers: { "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, HEAD", - "Access-Control-Allow-Headers": "Range", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Allow-Headers": "Range, Content-Type", + "Access-Control-Max-Age": "86400", }, }); } @@ -34,7 +35,13 @@ const previewServer = Bun.serve({ } return new Response(file, { - headers: { "Access-Control-Allow-Origin": "*" }, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Allow-Headers": "Range, Content-Type", + "Access-Control-Expose-Headers": "Content-Range, Content-Length, Accept-Ranges", + "Accept-Ranges": "bytes", + }, }); }, }); @@ -188,7 +195,7 @@ const rpc = BrowserView.defineRPC({ "ffprobe", "-v", "error", "-select_streams", "v:0", - "-show_entries", "stream=r_frame_rate,width,height,bit_rate", + "-show_entries", "stream=r_frame_rate,width,height,bit_rate,duration:format=duration", "-of", "json", inputPath, ], { stdout: "pipe", stderr: "pipe" }); @@ -198,6 +205,7 @@ const rpc = BrowserView.defineRPC({ const data = JSON.parse(output); const stream = data?.streams?.[0] ?? {}; + const format = data?.format ?? {}; // Parse r_frame_rate fraction (e.g. "30000/1001" → 29.97) let fps = 30; @@ -210,12 +218,13 @@ const rpc = BrowserView.defineRPC({ const height = stream.height ?? 1080; // bit_rate is in bps, convert to kbps const bitrate = stream.bit_rate ? Math.round(parseInt(stream.bit_rate) / 1000) : 0; + const duration = parseFloat(stream.duration || format.duration || "0"); - console.log(`[YAFW] Probe: ${width}x${height} @ ${fps}fps, ${bitrate}kbps`); - return { fps, width, height, bitrate }; + console.log(`[YAFW] Probe: ${width}x${height} @ ${fps}fps, ${bitrate}kbps, duration: ${duration}s`); + return { fps, width, height, bitrate, duration }; } catch (err) { console.error("[YAFW] probeVideo error:", err); - return { fps: 30, width: 1920, height: 1080, bitrate: 0 }; + return { fps: 30, width: 1920, height: 1080, bitrate: 0, duration: 0 }; } }, @@ -293,15 +302,33 @@ async function getMainViewUrl(): Promise { const url = await getMainViewUrl(); console.log("[YAFW] Loading URL:", url); +const windowWidth = 1280; +const windowHeight = 1000; +let x = 100; +let y = 100; + +try { + const primary = Screen.getPrimaryDisplay(); + const workArea = primary.workArea || primary.bounds; + if (workArea && workArea.width && workArea.height) { + x = Math.round(workArea.x + (workArea.width - windowWidth) / 2); + y = Math.round(workArea.y + (workArea.height - windowHeight) / 2); + } +} catch (e) { + console.error("[YAFW] Failed to calculate centered window position:", e); + x = 510; + y = 200; +} + const mainWindow = new BrowserWindow({ title: "YAFW", url, rpc, frame: { - width: 1280, - height: 1000, - x: 510, - y: 200, + width: windowWidth, + height: windowHeight, + x, + y, }, // hiddenInset gives a clean look on macOS, but on Windows it removes // the minimize/maximize/close buttons — so only use it on macOS. diff --git a/src/mainview/components/Header.tsx b/src/mainview/components/Header.tsx index 682e330..9a7673c 100644 --- a/src/mainview/components/Header.tsx +++ b/src/mainview/components/Header.tsx @@ -14,7 +14,7 @@ export const Header = ({ videoSrc, onChangeVideo, }: HeaderProps) => ( -
+
YAFW Logo
diff --git a/src/mainview/hooks/useElectrobun.ts b/src/mainview/hooks/useElectrobun.ts index 91f1fce..08b676c 100644 --- a/src/mainview/hooks/useElectrobun.ts +++ b/src/mainview/hooks/useElectrobun.ts @@ -2,36 +2,64 @@ import { useEffect, useRef, useState } from "react"; import { isStandalone } from "../env"; import { type AppRPCType } from "../../shared/types"; +let sharedElectroview: any = null; +let sharedSupportedEncoders: string[] = []; +let isInitStarted = false; +const listeners = new Set<(ev: any, encoders: string[]) => void>(); + export const useElectrobun = () => { - const [supportedEncoders, setSupportedEncoders] = useState([]); - const [electroview, setElectroview] = useState(null); - const electroviewRef = useRef(null); + const [supportedEncoders, setSupportedEncoders] = useState(sharedSupportedEncoders); + const [electroview, setElectroview] = useState(sharedElectroview); useEffect(() => { if (!isStandalone) return; - import("electrobun/view") - .then(({ Electroview }: any) => { - const rpc = Electroview.defineRPC({ - maxRequestTime: 600_000, // 10 min — native dialogs block - handlers: { requests: {}, messages: {} }, - }); - const ev = new Electroview({ rpc }); - electroviewRef.current = ev; - setElectroview(ev); - console.log("[YAFW] Electrobun RPC initialized"); - - // Detect hardware accelerators - ev.rpc.request.detectHardwareAccelerators({}) - .then((res: any) => { - setSupportedEncoders(res.supportedEncoders || []); - }) - .catch((err: any) => { - console.error("[YAFW] Failed to detect hardware accelerators:", err); + + if (sharedElectroview) { + setElectroview(sharedElectroview); + setSupportedEncoders(sharedSupportedEncoders); + return; + } + + const handleInit = (ev: any, encoders: string[]) => { + setElectroview(ev); + setSupportedEncoders(encoders); + }; + listeners.add(handleInit); + + if (!isInitStarted) { + isInitStarted = true; + import("electrobun/view") + .then(({ Electroview }: any) => { + const rpc = Electroview.defineRPC({ + maxRequestTime: 600_000, // 10 min — native dialogs block + handlers: { requests: {}, messages: {} }, }); - }) - .catch((err: unknown) => { - console.warn("[YAFW] Electrobun unavailable:", err); - }); + const ev = new Electroview({ rpc }); + sharedElectroview = ev; + console.log("[YAFW] Electrobun RPC initialized"); + + // Detect hardware accelerators + ev.rpc.request.detectHardwareAccelerators({}) + .then((res: any) => { + sharedSupportedEncoders = res.supportedEncoders || []; + listeners.forEach((l) => l(sharedElectroview, sharedSupportedEncoders)); + listeners.clear(); + }) + .catch((err: any) => { + console.error("[YAFW] Failed to detect hardware accelerators:", err); + listeners.forEach((l) => l(sharedElectroview, [])); + listeners.clear(); + }); + }) + .catch((err: unknown) => { + console.warn("[YAFW] Electrobun unavailable:", err); + listeners.clear(); + }); + } + + return () => { + listeners.delete(handleInit); + }; }, []); return { diff --git a/src/mainview/hooks/useVideoEditor.ts b/src/mainview/hooks/useVideoEditor.ts index b6c0530..6376524 100644 --- a/src/mainview/hooks/useVideoEditor.ts +++ b/src/mainview/hooks/useVideoEditor.ts @@ -192,19 +192,39 @@ export const useVideoEditor = () => { setOutputFormat(ext); setExportError(null); setExportSuccess(null); - detectVideoMetadata(previewUrl); - const probed = await probeWithWasm(previewUrl, result.path); - if (probed.fps !== null) { + console.log("[YAFW] Calling native probeVideo RPC..."); + const probed = await electroview.rpc.request.probeVideo({ inputPath: result.path }); + console.log("[YAFW] native probeVideo result:", probed); + + // Initialize metadata states directly from native probe values + const duration = probed.duration || 0; + setVideoDuration(duration); + setStart(0); + setEnd(1000); + setPosition(0); + + const w = probed.width || 1920; + const h = probed.height || 1080; + setOriginalWidth(w); + setOriginalHeight(h); + setOutputWidth(w); + setOutputHeight(h); + + if (probed.fps) { setOriginalFps(probed.fps); setOutputFps(probed.fps); } else { setOriginalFps(30); setOutputFps(30); } - if (probed.bitrate !== null) { + + if (probed.bitrate) { setOriginalBitrate(probed.bitrate); setBitrate(probed.bitrate); + } else { + setOriginalBitrate(1000); + setBitrate(1000); } } catch (err) { console.error("[YAFW] handleNativeBrowse error:", err); diff --git a/src/shared/types.ts b/src/shared/types.ts index f1cdc4b..8c6b97d 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -35,6 +35,7 @@ export type AppRPCType = { width: number; height: number; bitrate: number; // kbps + duration: number; // seconds }; }; /** Probes FFmpeg to check which hardware encoders are supported */