feat: FFmpeg GUI desktop application
Wails v2 + Vue3 + Go project with: - Encode/Remux/Subtitle burn with hardware acceleration - Real-time progress with ffmpeg stderr parsing (\r delimiter handling) - Task queue with cancel support - Per-task log viewer with color-coded output - Custom frameless window with resize support - Dark/light theme toggle - Hardware encoder detection (NVENC/QSV/AMF)
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="app" :data-theme="theme">
|
||||
<Header
|
||||
:currentView="currentView"
|
||||
:theme="theme"
|
||||
@navigate="navigate"
|
||||
@toggleTheme="toggleTheme"
|
||||
/>
|
||||
|
||||
<div class="main-area">
|
||||
<Sidebar :currentView="currentView" @navigate="navigate" />
|
||||
|
||||
<div class="workspace-column">
|
||||
<main class="workspace">
|
||||
<EncodePage v-if="currentView === 'encode'" @taskAdded="onTaskAdded" />
|
||||
<RemuxPage v-if="currentView === 'remux'" @taskAdded="onTaskAdded" />
|
||||
<BurnPage v-if="currentView === 'burn'" @taskAdded="onTaskAdded" />
|
||||
<SettingsPage v-if="currentView === 'settings'" />
|
||||
<LogPage v-show="currentView === 'logs'" ref="logPageRef" />
|
||||
</main>
|
||||
<TaskDrawer :tasks="tasks" @cancel="handleCancel" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Header from './components/Header.vue'
|
||||
import Sidebar from './components/Sidebar.vue'
|
||||
import TaskDrawer from './components/TaskDrawer.vue'
|
||||
import EncodePage from './views/EncodePage.vue'
|
||||
import RemuxPage from './views/RemuxPage.vue'
|
||||
import BurnPage from './views/BurnPage.vue'
|
||||
import SettingsPage from './views/SettingsPage.vue'
|
||||
import LogPage from './views/LogPage.vue'
|
||||
import { api, onTaskUpdated, onTaskProgress, onTaskLog } from './api/wails'
|
||||
import type { Task, Progress } from './types'
|
||||
|
||||
const currentView = ref('encode')
|
||||
const theme = ref<'dark'|'light'>('light')
|
||||
const tasks = ref<Task[]>([])
|
||||
|
||||
const logPageRef = ref<InstanceType<typeof LogPage> | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
document.documentElement.setAttribute('data-theme', theme.value)
|
||||
loadTasks()
|
||||
|
||||
onTaskUpdated((task: Task) => {
|
||||
const found = tasks.value.find(t => t.id === task.id)
|
||||
if (found) {
|
||||
Object.assign(found, task)
|
||||
} else {
|
||||
tasks.value.push(task)
|
||||
}
|
||||
logPageRef.value?.upsertTask({
|
||||
id: task.id,
|
||||
inputFile: task.inputFile,
|
||||
type: task.type,
|
||||
status: task.status,
|
||||
args: task.args,
|
||||
logs: task.logs,
|
||||
})
|
||||
})
|
||||
|
||||
onTaskProgress((data: { taskId: string; progress: Progress }) => {
|
||||
const found = tasks.value.find(t => t.id === data.taskId)
|
||||
if (found) {
|
||||
Object.assign(found.progress, data.progress)
|
||||
}
|
||||
})
|
||||
|
||||
onTaskLog((data: { taskId: string; line: string }) => {
|
||||
logPageRef.value?.appendLog(data.taskId, data.line)
|
||||
})
|
||||
})
|
||||
|
||||
function navigate(view: string) {
|
||||
currentView.value = view
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
theme.value = theme.value === 'dark' ? 'light' : 'dark'
|
||||
document.documentElement.setAttribute('data-theme', theme.value)
|
||||
}
|
||||
|
||||
async function loadTasks() {
|
||||
try { tasks.value = await api.getTasks() } catch { /* not connected */ }
|
||||
}
|
||||
|
||||
function onTaskAdded() { loadTasks() }
|
||||
|
||||
async function handleCancel(id: string) {
|
||||
try { await api.cancelTask(id); await loadTasks() } catch {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.workspace-column {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 28px 36px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user