This commit is contained in:
Yakumo 2025-07-24 00:35:32 +08:00
commit 29b12dd294
8 changed files with 252 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/to.iml" filepath="$PROJECT_DIR$/.idea/to.iml" />
</modules>
</component>
</project>

11
.idea/to.iml generated Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

54
Cargo.lock generated Normal file
View File

@ -0,0 +1,54 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "to"
version = "0.1.0"
dependencies = [
"regex",
]

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "to"
version = "0.1.0"
edition = "2024"
[dependencies]
regex = "1.10"

157
src/main.rs Normal file
View File

@ -0,0 +1,157 @@
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"
*/