158 lines
4.8 KiB
Rust
158 lines
4.8 KiB
Rust
use std::env;
|
||
use std::path::Path;
|
||
use std::process::{Command, Stdio};
|
||
use std::io::{BufRead, BufReader, Write};
|
||
use regex::Regex;
|
||
|
||
fn main() {
|
||
let args: Vec<String> = env::args().collect();
|
||
|
||
if args.len() < 2 {
|
||
println!("使用方法: 将.ts文件拖拽到此程序上,或者通过命令行传入文件路径");
|
||
println!("例如: {} input.ts", args[0]);
|
||
std::process::exit(1);
|
||
}
|
||
|
||
let input_path = &args[1];
|
||
|
||
// 检查输入文件是否存在
|
||
if !Path::new(input_path).exists() {
|
||
eprintln!("错误: 文件 '{}' 不存在", input_path);
|
||
std::process::exit(1);
|
||
}
|
||
|
||
// 检查是否为ts文件
|
||
if !input_path.to_lowercase().ends_with(".ts") {
|
||
eprintln!("错误: 只支持.ts格式的文件");
|
||
std::process::exit(1);
|
||
}
|
||
|
||
// 生成输出文件路径(同目录同名不同后缀)
|
||
let output_path = input_path.replace(".ts", ".mp4").replace(".TS", ".mp4");
|
||
|
||
println!("输入文件: {}", input_path);
|
||
println!("输出文件: {}", output_path);
|
||
println!("开始转换...\n");
|
||
|
||
// 执行转换
|
||
match convert_ts_to_mp4(input_path, &output_path) {
|
||
Ok(_) => {
|
||
println!("\n✅ 转换完成!");
|
||
println!("输出文件: {}", output_path);
|
||
}
|
||
Err(e) => {
|
||
eprintln!("\n❌ 转换失败: {}", e);
|
||
std::process::exit(1);
|
||
}
|
||
}
|
||
|
||
// 等待用户按键后退出(防止窗口立即关闭)
|
||
println!("\n按回车键退出...");
|
||
let mut input = String::new();
|
||
std::io::stdin().read_line(&mut input).unwrap();
|
||
}
|
||
|
||
fn convert_ts_to_mp4(input_path: &str, output_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||
// 首先获取视频总时长
|
||
let duration = get_video_duration(input_path)?;
|
||
println!("视频总时长: {:.2}秒", duration);
|
||
|
||
// 启动ffmpeg进程
|
||
let mut child = Command::new("ffmpeg")
|
||
// .args(&[
|
||
// "-i", input_path,
|
||
// "-c:v", "libx264", // 视频编码器
|
||
// "-c:a", "aac", // 音频编码器
|
||
// "-preset", "medium", // 编码速度预设
|
||
// "-crf", "23", // 视频质量
|
||
// "-movflags", "+faststart", // 优化网络播放
|
||
// "-progress", "pipe:1", // 输出进度到stdout
|
||
// "-y", // 覆盖输出文件
|
||
// output_path
|
||
// ])
|
||
.args(&[
|
||
"-i", input_path,
|
||
"-codec", "copy",
|
||
"-progress", "pipe:1", // 输出进度到stdout
|
||
"-y", // 覆盖输出文件
|
||
output_path
|
||
])
|
||
.stdout(Stdio::piped())
|
||
.stderr(Stdio::piped())
|
||
.spawn()?;
|
||
|
||
// 读取进度信息
|
||
if let Some(stdout) = child.stdout.take() {
|
||
let reader = BufReader::new(stdout);
|
||
let time_regex = Regex::new(r"out_time_ms=(\d+)")?;
|
||
|
||
for line in reader.lines() {
|
||
let line = line?;
|
||
|
||
// 解析当前处理时间
|
||
if let Some(captures) = time_regex.captures(&line) {
|
||
if let Ok(time_ms) = captures[1].parse::<f64>() {
|
||
let current_seconds = time_ms / 1_000_000.0; // 转换为秒
|
||
let progress = if duration > 0.0 {
|
||
(current_seconds / duration * 100.0).min(100.0)
|
||
} else {
|
||
0.0
|
||
};
|
||
|
||
// 显示进度条
|
||
print_progress_bar(progress);
|
||
}
|
||
}
|
||
|
||
// 检查是否完成
|
||
if line.contains("progress=end") {
|
||
print_progress_bar(100.0);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 等待进程结束
|
||
let status = child.wait()?;
|
||
|
||
if !status.success() {
|
||
return Err("FFmpeg转换失败".into());
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn get_video_duration(input_path: &str) -> Result<f64, Box<dyn std::error::Error>> {
|
||
let output = Command::new("ffprobe")
|
||
.args(&[
|
||
"-v", "quiet",
|
||
"-show_entries", "format=duration",
|
||
"-of", "csv=p=0",
|
||
input_path
|
||
])
|
||
.output()?;
|
||
|
||
let duration_str = String::from_utf8(output.stdout)?;
|
||
let duration: f64 = duration_str.trim().parse().unwrap_or(0.0);
|
||
|
||
Ok(duration)
|
||
}
|
||
|
||
fn print_progress_bar(percentage: f64) {
|
||
let bar_length = 40;
|
||
let filled_length = (bar_length as f64 * percentage / 100.0) as usize;
|
||
let empty_length = bar_length - filled_length;
|
||
|
||
let filled_bar = "█".repeat(filled_length);
|
||
let empty_bar = "░".repeat(empty_length);
|
||
|
||
print!("\r进度: [{}{}] {:.1}%", filled_bar, empty_bar, percentage);
|
||
std::io::stdout().flush().unwrap();
|
||
}
|
||
|
||
// 需要在Cargo.toml中添加依赖
|
||
/*
|
||
[dependencies]
|
||
regex = "1.10"
|
||
*/
|