From ae0ba21b115bdb4e5318402ede46567aa8c11c50 Mon Sep 17 00:00:00 2001 From: licsber Date: Sun, 4 Jan 2026 03:12:57 +0800 Subject: [PATCH] feat: origin filename support. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/dry_run.rs | 5 ++++ src/file_finder.rs | 68 ++++++++++++++++++++++++++++++++++++++------- src/merge.rs | 33 +++++++++++++++++++--- src/video_merger.rs | 6 ++-- 6 files changed, 97 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b853035..2f59217 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,7 +54,7 @@ dependencies = [ [[package]] name = "bilibili-merge" -version = "0.2.0" +version = "0.3.0" dependencies = [ "clap", ] diff --git a/Cargo.toml b/Cargo.toml index 0636158..084f2a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bilibili-merge" -version = "0.2.0" +version = "0.3.0" authors = ["licsber "] edition = "2021" diff --git a/src/dry_run.rs b/src/dry_run.rs index ec6a160..198bb11 100644 --- a/src/dry_run.rs +++ b/src/dry_run.rs @@ -20,6 +20,11 @@ impl ShellScript { self.lines.push(format!("{}=\"{}\"", name, escaped)); } + pub fn add_variable_str(&mut self, name: &str, value: &str) { + let escaped = value.replace('"', r#"\""#).replace('$', r#"\$"#); + self.lines.push(format!("{}=\"{}\"", name, escaped)); + } + pub fn add_command(&mut self, command: &str) { self.lines.push(command.to_string()); } diff --git a/src/file_finder.rs b/src/file_finder.rs index 4d4f1fa..98007b8 100644 --- a/src/file_finder.rs +++ b/src/file_finder.rs @@ -21,17 +21,65 @@ pub fn find_largest_video_file(path: &Path) -> Result { } pub fn find_audio_file(video_path: &Path) -> Result { - let video_name = video_path.file_name().unwrap().to_str().unwrap(); - let video_extension = video_path.extension().unwrap(); - let audio_name = format!("{}.m4a", &video_name[..video_name.len() - video_extension.len() - 1]); - let audio_path = video_path.parent().unwrap().join(audio_name); + let parent = video_path.parent().unwrap(); + let video_extension = video_path.extension().and_then(|e| e.to_str()); - if !audio_path.exists() { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("Audio file does not exist: {}", audio_path.display()) - )); + // Try to find .m4a file first (old format) + let video_name = video_path.file_stem().and_then(|n| n.to_str()).unwrap_or(""); + let audio_m4a_path = parent.join(format!("{}.m4a", video_name)); + if audio_m4a_path.exists() { + return Ok(audio_m4a_path); } - Ok(audio_path) + // Try to find another .m4s file (new format) + if video_extension == Some("m4s") { + let mut audio_candidates = Vec::new(); + for entry in std::fs::read_dir(parent)? { + let entry_path = entry?.path(); + if entry_path.is_file() { + if let Some(ext) = entry_path.extension().and_then(|e| e.to_str()) { + if ext == "m4s" && entry_path != video_path { + let size = entry_path.metadata()?.len(); + audio_candidates.push((entry_path, size)); + } + } + } + } + + if !audio_candidates.is_empty() { + // Return the largest .m4s file that's not the video file + audio_candidates.sort_by(|a, b| b.1.cmp(&a.1)); + return Ok(audio_candidates[0].0.clone()); + } + } + + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Audio file does not exist. Looked for: {}", audio_m4a_path.display()) + )) +} + +pub fn find_ass_file(path: &Path) -> Result { + for entry in std::fs::read_dir(path)? { + let entry_path = entry?.path(); + if entry_path.is_file() { + if let Some(ext) = entry_path.extension().and_then(|e| e.to_str()) { + if ext == "ass" { + return Ok(entry_path); + } + } + } + } + + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "ASS subtitle file not found in directory" + )) +} + +pub fn get_output_name_from_ass(ass_path: &Path) -> String { + ass_path.file_stem() + .and_then(|n| n.to_str()) + .unwrap_or("output") + .to_string() } diff --git a/src/merge.rs b/src/merge.rs index 808bde5..80883f4 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -1,6 +1,6 @@ use crate::audio_converter::convert_audio_to_aac; use crate::dry_run::{print_shell_script_header, ShellScript}; -use crate::file_finder::{find_audio_file, find_largest_video_file}; +use crate::file_finder::{find_audio_file, find_ass_file, find_largest_video_file, get_output_name_from_ass}; use crate::video_merger::merge_video_and_audio; use std::io::Result; use std::path::Path; @@ -34,13 +34,38 @@ pub fn merge_video_from_path(path: &Path, dry_run: bool, overwrite: bool) -> Res crate::dry_run::print_shell_script(&script); } + println!("Step 3: Looking for ASS subtitle file to determine output name..."); + let output_name = match find_ass_file(path) { + Ok(ass_path) => { + println!("Found ASS file: {}", ass_path.display()); + let name = get_output_name_from_ass(&ass_path); + println!("Output name will be: {}", name); + if dry_run { + let mut script = ShellScript::new(); + script.add_variable("ASS_FILE", &ass_path); + script.add_variable_str("OUTPUT_NAME", &name); + crate::dry_run::print_shell_script(&script); + } + name + } + Err(_) => { + println!("No ASS file found, using video file name as output name"); + let name = video_path.file_stem() + .and_then(|n| n.to_str()) + .unwrap_or("output") + .to_string(); + println!("Output name will be: {}", name); + name + } + }; + println!(); - println!("Step 3: Converting audio to AAC format..."); + println!("Step 4: Converting audio to AAC format..."); let aac_path = convert_audio_to_aac(&audio_path, dry_run, overwrite)?; println!(); - println!("Step 4: Merging video and audio..."); - let output_path = merge_video_and_audio(&video_path, &aac_path, dry_run, overwrite)?; + println!("Step 5: Merging video and audio..."); + let output_path = merge_video_and_audio(&video_path, &aac_path, &output_name, path, dry_run, overwrite)?; if dry_run { println!(); diff --git a/src/video_merger.rs b/src/video_merger.rs index ccb0687..7e06ee5 100644 --- a/src/video_merger.rs +++ b/src/video_merger.rs @@ -4,11 +4,11 @@ use std::io::Result; use std::path::{Path, PathBuf}; use std::fs; -pub fn merge_video_and_audio(video_path: &Path, audio_path: &Path, dry_run: bool, overwrite: bool) -> Result { +pub fn merge_video_and_audio(video_path: &Path, audio_path: &Path, output_name: &str, output_dir: &Path, dry_run: bool, overwrite: bool) -> Result { let origin_video_extension = video_path.extension().unwrap().to_str().unwrap(); let origin_filename = format!("original.{}", &origin_video_extension); let origin_path = video_path.parent().unwrap().join(origin_filename); - let output_path = video_path.with_extension("mp4"); + let output_path = output_dir.join(format!("{}.mp4", output_name)); if dry_run { let mut script = ShellScript::new(); @@ -24,7 +24,7 @@ pub fn merge_video_and_audio(video_path: &Path, audio_path: &Path, dry_run: bool .input_var("VIDEO_BACKUP") .input_var("AUDIO_AAC") .codec("copy") - .output_var("VIDEO_OUTPUT") + .output(&output_path) .execute(true)?; return Ok(output_path); }