feat: add clip export with lossless/precise modes and export dialog
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1 +1,111 @@
|
||||
// Clip export commands — implemented in Task 12
|
||||
use crate::models::{ExportConfig, ExportScope};
|
||||
use crate::services::clip_exporter;
|
||||
use serde::Serialize;
|
||||
use tauri::ipc::Channel;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
|
||||
pub enum ExportEvent {
|
||||
Progress {
|
||||
current: usize,
|
||||
total: usize,
|
||||
label: String,
|
||||
},
|
||||
Finished {
|
||||
paths: Vec<String>,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn export_clips(
|
||||
config: ExportConfig,
|
||||
on_event: Channel<ExportEvent>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let ext = clip_exporter::get_extension(&config.source_file_path, &config.cut_mode);
|
||||
|
||||
match config.export_scope {
|
||||
ExportScope::Individual => {
|
||||
let total = config.clips.len();
|
||||
let mut output_paths = Vec::new();
|
||||
|
||||
for (i, clip) in config.clips.iter().enumerate() {
|
||||
let _ = on_event.send(ExportEvent::Progress {
|
||||
current: i + 1,
|
||||
total,
|
||||
label: clip.label.clone(),
|
||||
});
|
||||
|
||||
let output = clip_exporter::generate_output_path(
|
||||
&config.output_directory,
|
||||
&config.video_title,
|
||||
&clip.label,
|
||||
&ext,
|
||||
);
|
||||
|
||||
clip_exporter::export_single_clip(
|
||||
clip,
|
||||
&config.source_file_path,
|
||||
&output,
|
||||
&config.cut_mode,
|
||||
)?;
|
||||
|
||||
output_paths.push(output);
|
||||
}
|
||||
|
||||
let _ = on_event.send(ExportEvent::Finished {
|
||||
paths: output_paths.clone(),
|
||||
});
|
||||
Ok(output_paths)
|
||||
}
|
||||
ExportScope::Merged => {
|
||||
let _ = on_event.send(ExportEvent::Progress {
|
||||
current: 1,
|
||||
total: 1,
|
||||
label: "Merging clips".to_string(),
|
||||
});
|
||||
|
||||
let output = clip_exporter::generate_output_path(
|
||||
&config.output_directory,
|
||||
&config.video_title,
|
||||
"Merged",
|
||||
&ext,
|
||||
);
|
||||
|
||||
let temp_dir = std::env::temp_dir()
|
||||
.join("video-clipper-merge")
|
||||
.join(uuid::Uuid::new_v4().to_string());
|
||||
std::fs::create_dir_all(&temp_dir)
|
||||
.map_err(|e| format!("Failed to create temp dir: {e}"))?;
|
||||
|
||||
let mut sorted_clips = config.clips.clone();
|
||||
sorted_clips.sort_by(|a, b| {
|
||||
a.start_time
|
||||
.partial_cmp(&b.start_time)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
clip_exporter::export_merged(
|
||||
&sorted_clips,
|
||||
&config.source_file_path,
|
||||
&output,
|
||||
&config.cut_mode,
|
||||
&temp_dir.to_string_lossy(),
|
||||
)?;
|
||||
|
||||
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||
|
||||
let paths = vec![output];
|
||||
let _ = on_event.send(ExportEvent::Finished {
|
||||
paths: paths.clone(),
|
||||
});
|
||||
Ok(paths)
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task failed: {e}"))?
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user