feat: register non-video files — graceful probe fallback for svg/pdf/docx/pages etc

This commit is contained in:
Accusys
2026-05-15 03:17:57 +08:00
parent 263f017972
commit fc1d7751dd
2 changed files with 78 additions and 96 deletions

View File

@@ -971,79 +971,71 @@ async fn register_single_file(
&final_name,
);
// Step 5: Probe
let probe_result = match crate::core::probe::probe_video(&canonical_path) {
Ok(r) => r,
Err(e) => {
return RegisterFileResponse {
success: false,
file_uuid,
file_name,
file_path: canonical_path,
file_type: None,
duration: 0.0,
width: 0,
height: 0,
fps: 0.0,
total_frames: 0,
registration_time: None,
already_exists: false,
message: format!("Probe failed: {}", e),
};
}
};
// Step 5: Probe (gracefully handle non-video files)
let probe_result = crate::core::probe::probe_video(&canonical_path).ok();
let file_meta = std::fs::metadata(&canonical_path).ok();
let has_video = probe_result
.streams
.iter()
.any(|s| s.codec_type.as_deref() == Some("video"));
let has_audio = probe_result
.streams
.iter()
.any(|s| s.codec_type.as_deref() == Some("audio"));
let probe_json: Option<serde_json::Value> = probe_result.as_ref().map(|r| serde_json::to_value(r)).and_then(|r| r.ok()).or_else(|| {
// Minimal probe info for non-video files
file_meta.map(|m| serde_json::json!({
"format": {
"size": m.len().to_string(),
"filename": &canonical_path,
"format_name": "unknown"
},
"streams": []
}))
});
let has_video = probe_result.as_ref().map_or(false, |r| r.streams.iter().any(|s| s.codec_type.as_deref() == Some("video")));
let has_audio = probe_result.as_ref().map_or(false, |r| r.streams.iter().any(|s| s.codec_type.as_deref() == Some("audio")));
// Determine file_type: check ffprobe result, then extension
let final_file_type = if has_video {
Some("video".to_string())
} else if has_audio {
Some("audio".to_string())
} else {
None
let ext = std::path::Path::new(&canonical_path).extension().and_then(|e| e.to_str()).map(|e| e.to_lowercase());
match ext.as_deref() {
Some("jpg" | "jpeg" | "png" | "gif" | "bmp" | "webp" | "svg") => Some("image".to_string()),
Some("pdf") => Some("document".to_string()),
Some("doc" | "docx") => Some("document".to_string()),
Some("pages") => Some("document".to_string()),
Some("xls" | "xlsx" | "numbers") => Some("spreadsheet".to_string()),
Some("ppt" | "pptx" | "key") => Some("presentation".to_string()),
_ => probe_result.as_ref().and_then(|r| {
if r.streams.is_empty() && r.format.duration.is_some() { Some("unknown".to_string()) } else { None }
}),
}
};
let duration = probe_result
.format
.duration
.as_ref()
let duration = probe_result.as_ref()
.and_then(|r| r.format.duration.as_ref())
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or(0.0);
let mut width = 0u32;
let mut height = 0u32;
let mut fps = 0.0;
let mut total_frames = 0u64;
if let Some(s) = probe_result
.streams
.iter()
.find(|s| s.codec_type.as_deref() == Some("video"))
{
width = s.width.unwrap_or(0);
height = s.height.unwrap_or(0);
if let Some(fps_str) = &s.r_frame_rate {
if let Some((num, den)) = fps_str.split_once('/') {
if let (Ok(n), Ok(d)) = (num.parse::<f64>(), den.parse::<f64>()) {
if d > 0.0 {
fps = n / d;
if let Some(ref probe) = probe_result {
if let Some(s) = probe.streams.iter().find(|s| s.codec_type.as_deref() == Some("video")) {
width = s.width.unwrap_or(0);
height = s.height.unwrap_or(0);
if let Some(fps_str) = &s.r_frame_rate {
if let Some((num, den)) = fps_str.split_once('/') {
if let (Ok(n), Ok(d)) = (num.parse::<f64>(), den.parse::<f64>()) {
if d > 0.0 {
fps = n / d;
}
}
}
}
total_frames = s.nb_frames.as_ref().and_then(|s| s.parse().ok()).unwrap_or((duration * fps) as u64);
}
total_frames = s
.nb_frames
.as_ref()
.and_then(|s| s.parse().ok())
.unwrap_or((duration * fps) as u64);
}
let videos_table = schema::table_name("videos");
let probe_json = serde_json::to_value(&probe_result).ok();
let status = "pending";
let _ = sqlx::query(&format!(
"INSERT INTO {} (file_uuid, file_path, file_name, file_type, duration, width, height, fps, probe_json, status, content_hash, registration_time) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW()) ON CONFLICT (file_uuid) DO UPDATE SET file_path = EXCLUDED.file_path, file_name = EXCLUDED.file_name, status = EXCLUDED.status, content_hash = EXCLUDED.content_hash",
@@ -1135,18 +1127,20 @@ async fn register_single_file(
}
// 更新 DB: cut_done, scene_done, audio_tracks
let audio_tracks: Vec<serde_json::Value> = probe_result.streams.iter()
.filter(|s| s.codec_type.as_deref() == Some("audio"))
.map(|s| {
serde_json::json!({
"index": s.index,
"codec": s.codec_name,
"channels": s.channels,
"sample_rate": s.sample_rate,
"language": s.tags.as_ref().and_then(|t| t.get("language")).unwrap_or(&serde_json::Value::Null),
let audio_tracks: Vec<serde_json::Value> = probe_result.as_ref().map_or(vec![], |pr| {
pr.streams.iter()
.filter(|s| s.codec_type.as_deref() == Some("audio"))
.map(|s| {
serde_json::json!({
"index": s.index,
"codec": s.codec_name,
"channels": s.channels,
"sample_rate": s.sample_rate,
"language": s.tags.as_ref().and_then(|t| t.get("language")).unwrap_or(&serde_json::Value::Null),
})
})
})
.collect();
.collect()
});
let audio_tracks_json = serde_json::to_value(&audio_tracks).ok();
// 計算 cut_count 與 cut_max_duration
let cut_path = std::path::Path::new(