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