Files
gui-video-clipper/src-tauri/src/commands/video.rs
cottongin d85fbdac05 fix: suppress Rust dead code warnings with annotations
- ExportEvent::Error and DownloadEvent::Error reserved for future use
- clip_exporter functions scaffolded for caption burn-in pipeline
- SubtitleFormat fields required for serde deserialization

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:32:23 -04:00

194 lines
6.5 KiB
Rust

use crate::models::{CookieSource, VideoMetadata};
use crate::services::{download_manager, subtitle_downloader, video_resolver};
use serde::Serialize;
use std::io::{BufRead, BufReader};
use tauri::ipc::Channel;
#[tauri::command]
pub async fn resolve_url(
url: String,
cookie_source: CookieSource,
) -> Result<VideoMetadata, String> {
tokio::task::spawn_blocking(move || video_resolver::resolve(&url, &cookie_source))
.await
.map_err(|e| format!("Task failed: {e}"))?
}
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
pub enum DownloadEvent {
Progress { percent: f64 },
FilePath { path: String },
Finished { success: bool, path: String },
#[allow(dead_code)] // Reserved for future error-channel reporting
Error { message: String },
}
fn variant_dir(variant: &str) -> String {
std::env::temp_dir()
.join("video-clipper")
.join(variant)
.to_string_lossy()
.to_string()
}
#[tauri::command]
pub async fn start_download(
url: String,
cookie_source: CookieSource,
format_spec: String,
variant: String,
on_event: Channel<DownloadEvent>,
) -> Result<(), String> {
tokio::task::spawn_blocking(move || {
let output_dir = variant_dir(&variant);
std::fs::create_dir_all(&output_dir)
.map_err(|e| format!("Failed to create dir: {e}"))?;
eprintln!(
"[video-clipper:download:{}] starting — format='{}' dir='{}'",
variant, format_spec, output_dir
);
let (mut child, _template) =
download_manager::start_download(&url, &cookie_source, &output_dir, &format_spec)?;
let Some(stdout) = child.stdout.take() else {
let status = child
.wait()
.map_err(|e| format!("Download failed: {e}"))?;
let _ = on_event.send(DownloadEvent::Finished {
success: status.success(),
path: String::new(),
});
return Ok(());
};
let reader = BufReader::new(stdout);
let mut last_file_path: Option<String> = None;
for line in reader.lines().map_while(Result::ok) {
if let Some(pct) = download_manager::parse_progress_line(&line) {
let _ = on_event.send(DownloadEvent::Progress { percent: pct });
}
if line.contains("[download] Destination:") {
if let Some(path) = line.split(": ").nth(1) {
let trimmed = path.trim().trim_matches('"').to_string();
let _ = on_event.send(DownloadEvent::FilePath {
path: trimmed.clone(),
});
last_file_path = Some(trimmed);
}
} else if line.contains("[Merger] Merging formats into") {
let trimmed = line
.trim_start_matches("[Merger] Merging formats into ")
.trim()
.trim_matches('"')
.to_string();
if !trimmed.is_empty() {
let _ = on_event.send(DownloadEvent::FilePath {
path: trimmed.clone(),
});
last_file_path = Some(trimmed);
}
} else if line.contains("has already been downloaded") {
if let Some(rest) = line.strip_prefix("[download] ") {
if let Some(path) = rest.strip_suffix(" has already been downloaded") {
let trimmed = path.trim().to_string();
if !trimmed.is_empty() {
let _ = on_event.send(DownloadEvent::FilePath {
path: trimmed.clone(),
});
last_file_path = Some(trimmed);
}
}
}
}
}
let status = child
.wait()
.map_err(|e| format!("Download failed: {e}"))?;
let path = last_file_path.unwrap_or_default();
eprintln!(
"[video-clipper:download:{}] finished — success={}, path='{}'",
variant,
status.success(),
path
);
let _ = on_event.send(DownloadEvent::Finished {
success: status.success(),
path,
});
Ok(())
})
.await
.map_err(|e| format!("Task failed: {e}"))?
}
/// Check if a video with the given title exists in the variant's download dir.
#[tauri::command]
pub async fn check_cached_download(title: String, variant: String) -> Option<String> {
let dir = variant_dir(&variant);
let dir_path = std::path::Path::new(&dir);
if !dir_path.is_dir() {
return None;
}
let entries = std::fs::read_dir(dir_path).ok()?;
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
if stem == title {
let path_str = path.to_string_lossy().to_string();
if let Ok(meta) = std::fs::metadata(&path_str) {
if meta.len() > 0 {
eprintln!(
"[video-clipper:cache:{}] found: '{}'",
variant, path_str
);
return Some(path_str);
}
}
}
}
}
}
None
}
/// Download English VTT subtitles for a video URL.
/// Returns the path to the .vtt file, or an error string.
#[tauri::command]
pub async fn download_subtitles(
url: String,
cookie_source: CookieSource,
is_auto: bool,
) -> Result<String, String> {
tokio::task::spawn_blocking(move || {
let output_dir = std::env::temp_dir()
.join("video-clipper")
.join("subtitles")
.to_string_lossy()
.to_string();
eprintln!(
"[video-clipper:subtitles] downloading (auto={}) to '{}'",
is_auto, output_dir
);
let result =
subtitle_downloader::download_subtitles(&url, &cookie_source, &output_dir, is_auto);
match &result {
Ok(path) => eprintln!("[video-clipper:subtitles] done: '{}'", path),
Err(e) => eprintln!("[video-clipper:subtitles] FAILED: {}", e),
}
result
})
.await
.map_err(|e| format!("Task failed: {e}"))?
}