one
This commit is contained in:
commit
29b12dd294
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
8
.idea/modules.xml
generated
Normal 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
11
.idea/to.iml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
54
Cargo.lock
generated
Normal 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
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "to"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
regex = "1.10"
|
157
src/main.rs
Normal file
157
src/main.rs
Normal 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"
|
||||
*/
|
Loading…
x
Reference in New Issue
Block a user