feat: ffprobe 快速检测 H.264/yuv420p 视频,跳过不必要的预览转码
- 应用生成的成品视频已是 H.264/yuv420p,无需重复转码 - 超时后显式 kill ffprobe 子进程,避免僵尸进程 - 分辨率上限判断:4K 素材仍转码为 540p 代理保证预览流畅
This commit is contained in:
@@ -737,6 +737,56 @@ pub async fn transcode_for_preview(app: &AppHandle, input_path: &str) -> Result<
|
||||
return Err(format!("输入文件不存在: {}", input_path));
|
||||
}
|
||||
|
||||
let path_str = input.canonicalize()
|
||||
.unwrap_or(input.to_path_buf())
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
// 快速检测:如果已经是 H.264 + YUV420p,直接返回原始路径(避免应用自己生成的成品重复转码)
|
||||
let probe_args = vec![
|
||||
"-v".to_string(), "error".to_string(),
|
||||
"-select_streams".to_string(), "v:0".to_string(),
|
||||
"-show_entries".to_string(), "stream=codec_name,pix_fmt,width,height".to_string(),
|
||||
"-of".to_string(), "json".to_string(),
|
||||
path_str.clone(),
|
||||
];
|
||||
|
||||
let probe_result = app.shell().sidecar("ffprobe")
|
||||
.and_then(|s| s.args(probe_args).spawn());
|
||||
if let Ok((mut rx, child)) = probe_result {
|
||||
let mut stdout = String::new();
|
||||
let probe_future = async {
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line) => stdout.push_str(&String::from_utf8_lossy(&line)),
|
||||
CommandEvent::Terminated(status) if status.code == Some(0) => return Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok::<(), ()>(())
|
||||
};
|
||||
match tokio::time::timeout(std::time::Duration::from_secs(3), probe_future).await {
|
||||
Ok(Ok(())) => {
|
||||
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&stdout) {
|
||||
if let Some(stream) = parsed.get("streams").and_then(|s| s.as_array()).and_then(|a| a.first()) {
|
||||
let codec = stream.get("codec_name").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let pix_fmt = stream.get("pix_fmt").and_then(|v| v.as_str()).unwrap_or("");
|
||||
// 应用生成的视频通常是 1080p 及以下;若用户上传了 4K H.264,仍转码为 540p 代理以保证预览流畅
|
||||
let width = stream.get("width").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let height = stream.get("height").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
if codec == "h264" && pix_fmt == "yuv420p" && width <= 1920 && height <= 1920 {
|
||||
return Ok(path_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// 超时或异常:强制结束 ffprobe,避免僵尸进程
|
||||
let _ = child.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文件元数据用于缓存 key
|
||||
let metadata = std::fs::metadata(input_path)
|
||||
.map_err(|e| format!("无法读取文件元数据: {}", e))?;
|
||||
@@ -749,10 +799,6 @@ pub async fn transcode_for_preview(app: &AppHandle, input_path: &str) -> Result<
|
||||
let file_size = metadata.len();
|
||||
|
||||
// 计算缓存路径(基于文件路径 hash + 大小 + 修改时间)
|
||||
let path_str = input.canonicalize()
|
||||
.unwrap_or(input.to_path_buf())
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let path_hash = {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
Reference in New Issue
Block a user