From 83f5bb8b30313920d043fb7f7cfb02a4f3777c23 Mon Sep 17 00:00:00 2001 From: sansen Date: Wed, 29 Jul 2026 10:46:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.go | 11 +- build.bat | 42 +++++ build/appicon.png | Bin 0 -> 6130 bytes frontend/src/App.vue | 37 ++++- frontend/src/assets/icon/app-logo.svg | 1 + frontend/src/components/Header.vue | 10 +- frontend/src/views/BurnPage.vue | 62 +++++++- frontend/src/views/EncodePage.vue | 59 ++++--- frontend/src/views/RemuxPage.vue | 22 ++- frontend/src/views/SettingsPage.vue | 212 ++++++++++++-------------- internal/gpu/detect.go | 123 +++++++++++++++ internal/gpu/gpu_other.go | 12 ++ internal/gpu/gpu_windows.go | 164 ++++++++++++++++++++ main.go | 5 +- 14 files changed, 593 insertions(+), 167 deletions(-) create mode 100644 build.bat create mode 100644 build/appicon.png create mode 100644 frontend/src/assets/icon/app-logo.svg create mode 100644 internal/gpu/detect.go create mode 100644 internal/gpu/gpu_other.go create mode 100644 internal/gpu/gpu_windows.go diff --git a/app.go b/app.go index fa59f7e..421e476 100644 --- a/app.go +++ b/app.go @@ -3,6 +3,7 @@ package main import ( "context" "ffmpeg-gui/internal/ffmpeg" + "ffmpeg-gui/internal/gpu" "ffmpeg-gui/internal/hwaccel" "ffmpeg-gui/internal/media" "ffmpeg-gui/internal/platform" @@ -106,6 +107,14 @@ func (a *App) GetHardwareEncoders() ([]hwaccel.HWEncoder, error) { return a.hwDetect.DetectEncoders() } +// GetGPUInfo returns detected GPU models and their encoder capabilities. +func (a *App) GetGPUInfo() []gpu.GPUInfo { + if a.exec == nil { + return nil + } + return gpu.DetectGPUs(a.exec) +} + // GetAccelerators returns detected hardware acceleration methods. func (a *App) GetAccelerators() ([]hwaccel.Accelerator, error) { if a.hwDetect == nil { @@ -200,7 +209,7 @@ func (a *App) SelectInputFile() (string, error) { // SelectOutputFile opens a save file dialog. func (a *App) SelectOutputFile(defaultName string) (string, error) { return runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ - Title: "选择输出文件", + Title: "选择输出文件", DefaultFilename: defaultName, Filters: []runtime.FileFilter{ {DisplayName: "MP4 (*.mp4)", Pattern: "*.mp4"}, diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..6777dd3 --- /dev/null +++ b/build.bat @@ -0,0 +1,42 @@ +@echo off +chcp 65001 >nul +echo === FFmpeg GUI Build Script === +echo. + +cd /d "%~dp0" + +echo [1/3] Installing frontend dependencies... +cd frontend +call npm install +if %errorlevel% neq 0 ( + echo ERROR: npm install failed + pause + exit /b %errorlevel% +) + +echo [2/3] Building frontend... +call npm run build +if %errorlevel% neq 0 ( + echo ERROR: frontend build failed + pause + exit /b %errorlevel% +) +cd .. + +echo [3/3] Cleaning icon cache... +if exist "build\windows\icon.ico" del /q "build\windows\icon.ico" +if exist "*-res.syso" del /q "*-res.syso" + +echo [4/4] Building Wails application... +wails build -clean -platform windows/amd64 +if %errorlevel% neq 0 ( + echo ERROR: wails build failed + pause + exit /b %errorlevel% +) + +echo. +echo === Build complete! === +echo Output: build\bin\ffmpeg-gui.exe +echo. +pause diff --git a/build/appicon.png b/build/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..849e12f42579a903bbd947ea6173951dbfabdeee GIT binary patch literal 6130 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 z
- - - + + +
@@ -41,12 +41,15 @@ import type { Task, Progress } from './types' const currentView = ref('encode') const theme = ref<'dark'|'light'>('light') const tasks = ref([]) +const gpuInfo = ref([]) const logPageRef = ref | null>(null) onMounted(() => { + document.addEventListener('contextmenu', e => e.preventDefault()) document.documentElement.setAttribute('data-theme', theme.value) loadTasks() + loadGPUInfo() onTaskUpdated((task: Task) => { const found = tasks.value.find(t => t.id === task.id) @@ -90,6 +93,34 @@ async function loadTasks() { try { tasks.value = await api.getTasks() } catch { /* not connected */ } } +async function loadGPUInfo() { + try { + const app = (window as any).go?.main?.App + if (app?.GetGPUInfo) { + gpuInfo.value = await app.GetGPUInfo() || [] + } else { + const encs = await api.getHardwareEncoders() + gpuInfo.value = groupByVendor(encs) + } + } catch { + try { + const encs = await api.getHardwareEncoders() + gpuInfo.value = groupByVendor(encs) + } catch { gpuInfo.value = [] } + } +} + +function groupByVendor(encs: any[]): any[] { + const groups: Record = {} + for (const e of encs) { + if (!groups[e.type]) { + groups[e.type] = { name: e.type, vendor: e.type, encoders: [] } + } + groups[e.type].encoders.push({ name: e.name, codec: e.codec, label: e.label, available: e.available }) + } + return [...Object.values(groups), { name: 'CPU', vendor: 'cpu', encoders: [{ name:'libx264',codec:'h264',label:'H.264',available:true },{ name:'libx265',codec:'hevc',label:'HEVC',available:true }]}] +} + function onTaskAdded() { loadTasks() } async function handleCancel(id: string) { diff --git a/frontend/src/assets/icon/app-logo.svg b/frontend/src/assets/icon/app-logo.svg new file mode 100644 index 0000000..2fa37f2 --- /dev/null +++ b/frontend/src/assets/icon/app-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Header.vue b/frontend/src/components/Header.vue index 9ae83c6..cb84a1b 100644 --- a/frontend/src/components/Header.vue +++ b/frontend/src/components/Header.vue @@ -1,11 +1,7 @@