2026-09-21 11:20:32 -04:00
|
|
|
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}"))?
|
|
|
|
|
}
|