diff --git a/src-tauri/src/commands/media.rs b/src-tauri/src/commands/media.rs index 2ac353e..ded3e22 100644 --- a/src-tauri/src/commands/media.rs +++ b/src-tauri/src/commands/media.rs @@ -1,6 +1,9 @@ use std::path::PathBuf; use std::process::Command; +#[cfg(target_os = "windows")] +use std::os::windows::process::CommandExt; + /// Extract audio from a video file to a WAV file using ffmpeg. /// Returns the path to the extracted audio file. #[tauri::command] @@ -23,8 +26,8 @@ pub fn extract_audio(file_path: String) -> Result { // Find ffmpeg — check sidecar extract dir first, then system PATH let ffmpeg = find_ffmpeg().ok_or("ffmpeg not found. Install ffmpeg or ensure it's in PATH.")?; - let status = Command::new(&ffmpeg) - .args([ + let mut cmd = Command::new(&ffmpeg); + cmd.args([ "-y", // Overwrite output "-i", &file_path, @@ -38,7 +41,13 @@ pub fn extract_audio(file_path: String) -> Result { ]) .arg(output.to_str().unwrap()) .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + // Hide the console window on Windows (CREATE_NO_WINDOW = 0x08000000) + #[cfg(target_os = "windows")] + cmd.creation_flags(0x08000000); + + let status = cmd .status() .map_err(|e| format!("Failed to run ffmpeg: {e}"))?; diff --git a/src/lib/stores/settings.ts b/src/lib/stores/settings.ts index d8d208b..9b011a9 100644 --- a/src/lib/stores/settings.ts +++ b/src/lib/stores/settings.ts @@ -52,11 +52,7 @@ export async function loadSettings(): Promise { } } -export async function saveSettings(s: AppSettings): Promise { - settings.set(s); - await invoke('save_settings', { settings: s }); - - // Configure the AI provider in the Python sidecar +export async function configureAIProvider(s: AppSettings): Promise { const configMap: Record> = { openai: { api_key: s.openai_api_key, model: s.openai_model }, anthropic: { api_key: s.anthropic_api_key, model: s.anthropic_model }, @@ -68,7 +64,15 @@ export async function saveSettings(s: AppSettings): Promise { try { await invoke('ai_configure', { provider: s.ai_provider, config }); } catch { - // Sidecar may not be running yet — provider will be configured on first use + // Sidecar may not be running yet } } } + +export async function saveSettings(s: AppSettings): Promise { + settings.set(s); + await invoke('save_settings', { settings: s }); + + // Configure the AI provider in the Python sidecar + await configureAIProvider(s); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0522aa9..67d16eb 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -10,7 +10,7 @@ import SettingsModal from '$lib/components/SettingsModal.svelte'; import SidecarSetup from '$lib/components/SidecarSetup.svelte'; import { segments, speakers } from '$lib/stores/transcript'; - import { settings, loadSettings } from '$lib/stores/settings'; + import { settings, loadSettings, configureAIProvider } from '$lib/stores/settings'; import type { Segment, Speaker } from '$lib/types/transcript'; import { onMount, tick } from 'svelte'; @@ -54,6 +54,7 @@ function handleSidecarSetupComplete() { sidecarReady = true; + configureAIProvider($settings); checkSidecarUpdate(); } @@ -71,6 +72,7 @@ }); checkSidecar().then(() => { if (sidecarReady) { + configureAIProvider($settings); checkSidecarUpdate(); } }); @@ -120,6 +122,7 @@ let transcriptionProgress = $state(0); let transcriptionStage = $state(''); let transcriptionMessage = $state(''); + let extractingAudio = $state(false); // Speaker color palette for auto-assignment const speakerColors = ['#e94560', '#4ecdc4', '#ffe66d', '#a8e6cf', '#ff8b94', '#c7ceea', '#ffd93d', '#6bcb77']; @@ -271,6 +274,8 @@ const ext = filePath.split('.').pop()?.toLowerCase() ?? ''; let audioPath = filePath; if (VIDEO_EXTENSIONS.includes(ext)) { + extractingAudio = true; + await tick(); try { audioPath = await invoke('extract_audio', { filePath }); } catch (err) { @@ -289,6 +294,8 @@ alert(`Failed to extract audio from video: ${msg}`); } return; + } finally { + extractingAudio = false; } } @@ -602,6 +609,15 @@ message={transcriptionMessage} /> + {#if extractingAudio} +
+
+
+

Extracting audio from video...

+
+
+ {/if} + showSettings = false} @@ -808,4 +824,39 @@ .update-dismiss:hover { color: #e0e0e0; } + + /* Audio extraction overlay */ + .extraction-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + } + .extraction-card { + background: #16213e; + padding: 2rem 2.5rem; + border-radius: 12px; + color: #e0e0e0; + border: 1px solid #2a3a5e; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + } + .extraction-card p { + margin: 0; + font-size: 1rem; + } + .extraction-spinner { + width: 32px; + height: 32px; + border: 3px solid #2a3a5e; + border-top-color: #e94560; + border-radius: 50%; + animation: spin 0.8s linear infinite; + }