Phase 4: Export to SRT, WebVTT, ASS, plain text, and Markdown

- Implement ExportService using pysubs2 for caption formats (SRT, VTT, ASS)
  and custom formatters for plain text and Markdown
- SRT exports with [Speaker]: prefix, WebVTT with <v Speaker> voice tags,
  ASS with color-coded speaker styles
- Plain text groups by speaker with labels, Markdown adds timestamps
- Add export.start IPC handler and export_transcript Tauri command
- Add export dropdown menu in header (appears after transcription)
- Uses native save dialog for output file selection
- Add pysubs2 dependency
- Tests: 30 Python (6 export tests), 6 Rust, 0 Svelte errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 16:18:54 -08:00
parent 44480906a4
commit 415a648a2b
9 changed files with 557 additions and 9 deletions

View File

@@ -1,2 +1,49 @@
// Export commands — trigger caption/text export via Python sidecar
// TODO: Implement when export service is built
use serde_json::{json, Value};
use crate::sidecar::messages::IPCMessage;
use crate::sidecar::SidecarManager;
/// Export transcript to caption/text format via the Python sidecar.
#[tauri::command]
pub fn export_transcript(
segments: Value,
speakers: Value,
format: String,
output_path: String,
title: Option<String>,
) -> Result<Value, String> {
let python_path = std::env::current_dir()
.map_err(|e| e.to_string())?
.join("../python")
.canonicalize()
.map_err(|e| format!("Cannot find python directory: {e}"))?;
let python_path_str = python_path.to_string_lossy().to_string();
let manager = SidecarManager::new();
manager.start(&python_path_str)?;
let request_id = uuid::Uuid::new_v4().to_string();
let msg = IPCMessage::new(
&request_id,
"export.start",
json!({
"segments": segments,
"speakers": speakers,
"format": format,
"output_path": output_path,
"title": title.unwrap_or_default(),
}),
);
let response = manager.send_and_receive(&msg)?;
if response.msg_type == "error" {
return Err(format!(
"Export error: {}",
response.payload.get("message").and_then(|v| v.as_str()).unwrap_or("unknown")
));
}
Ok(response.payload)
}