mirror of
https://github.com/SinTan1729/movie-rename.git
synced 2025-04-19 10:20:00 -05:00
213 lines
8 KiB
Rust
213 lines
8 KiB
Rust
use inquire::{
|
|
ui::{Color, IndexPrefix, RenderConfig, Styled},
|
|
Select,
|
|
};
|
|
use std::{collections::HashMap, fs, path::Path, process::exit};
|
|
use tmdb::{model::*, themoviedb::*};
|
|
use torrent_name_parser::Metadata;
|
|
|
|
use crate::structs::{get_long_lang, Language, MovieEntry};
|
|
|
|
// Get the version from Cargo.toml
|
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
// Function to process movie entries
|
|
pub fn process_file(
|
|
filename: &String,
|
|
tmdb: &TMDb,
|
|
pattern: &str,
|
|
dry_run: bool,
|
|
) -> (String, bool) {
|
|
// Set RenderConfig for the menu items
|
|
inquire::set_global_render_config(get_render_config());
|
|
|
|
// Get the basename
|
|
let mut file_base = String::from(filename);
|
|
let mut parent = String::from("");
|
|
match filename.rsplit_once("/") {
|
|
Some(parts) => {
|
|
parent = parts.0.to_string();
|
|
file_base = parts.1.to_string();
|
|
}
|
|
None => {}
|
|
}
|
|
println!(" Processing {}...", file_base);
|
|
|
|
// Parse the filename for metadata
|
|
let metadata = Metadata::from(file_base.as_str()).expect(" Could not parse filename!");
|
|
// Search using the TMDb API
|
|
let mut search = tmdb.search();
|
|
search.title(metadata.title());
|
|
|
|
// Check if year is present in filename
|
|
if let Some(year) = metadata.year() {
|
|
search.year(year as u64);
|
|
}
|
|
|
|
let mut results = Vec::new();
|
|
if let Ok(search_results) = search.execute() {
|
|
results = search_results.results;
|
|
} else {
|
|
eprintln!(" There was an error while searching {}!", file_base);
|
|
}
|
|
|
|
let mut movie_list: Vec<MovieEntry> = Vec::new();
|
|
// Create movie entry from the result
|
|
for result in results {
|
|
let mut movie_details = MovieEntry::from(result);
|
|
// Get director's name, if needed
|
|
if pattern.contains("{director}") {
|
|
let with_credits: Result<Movie, _> =
|
|
tmdb.fetch().id(movie_details.id).append_credits().execute();
|
|
if let Ok(movie) = with_credits {
|
|
if let Some(cre) = movie.credits {
|
|
let mut directors = cre.crew;
|
|
directors.retain(|x| x.job == "Director");
|
|
for person in directors {
|
|
movie_details.director = person.name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
movie_list.push(movie_details);
|
|
}
|
|
|
|
// If nothing is found, skip
|
|
if movie_list.len() == 0 {
|
|
eprintln!(" Could not find any entries matching {}!", file_base);
|
|
return ("".to_string(), true);
|
|
}
|
|
|
|
// Choose from the possible entries
|
|
let choice = Select::new(
|
|
format!(" Possible choices for {}:", file_base).as_str(),
|
|
movie_list,
|
|
)
|
|
.prompt()
|
|
.expect(" Invalid choice!");
|
|
|
|
let mut extension = metadata.extension().unwrap_or("").to_string();
|
|
// Handle the case for subtitle files
|
|
let mut is_subtitle = false;
|
|
if ["srt", "ssa"].contains(&extension.as_str()) {
|
|
// Try to detect if there's already language info in the filename, else ask user to choose
|
|
let filename_parts: Vec<&str> = filename.rsplit(".").collect();
|
|
if filename_parts.len() >= 3 && filename_parts[1].len() == 2 {
|
|
println!(
|
|
" Keeping language {} as detected in the subtitle file's extension...",
|
|
get_long_lang(filename_parts[1])
|
|
);
|
|
extension = format!("{}.{}", filename_parts[1], extension);
|
|
} else {
|
|
let lang_list = Language::generate_list();
|
|
let lang_choice =
|
|
Select::new(" Choose the language for the subtitle file:", lang_list)
|
|
.prompt()
|
|
.expect(" Invalid choice!");
|
|
if lang_choice.short != "none".to_string() {
|
|
extension = format!("{}.{}", lang_choice.short, extension);
|
|
}
|
|
}
|
|
is_subtitle = true;
|
|
}
|
|
|
|
// Create the new name
|
|
let new_name_base = choice.rename_format(pattern.to_string());
|
|
let mut new_name_with_ext = new_name_base.clone();
|
|
if extension != "" {
|
|
new_name_with_ext = format!("{}.{}", new_name_with_ext, extension);
|
|
}
|
|
let mut new_name = String::from(new_name_with_ext.clone());
|
|
if parent != "".to_string() {
|
|
new_name = format!("{}/{}", parent, new_name);
|
|
}
|
|
|
|
// Process the renaming
|
|
if *filename == new_name {
|
|
println!(" [file] '{}' already has correct name.", file_base);
|
|
} else {
|
|
println!(" [file] '{}' -> '{}'", file_base, new_name_with_ext);
|
|
// Only do the rename of --dry-run isn't passed
|
|
if dry_run == false {
|
|
if Path::new(new_name.as_str()).is_file() == false {
|
|
fs::rename(filename, new_name.as_str()).expect(" Unable to rename file!");
|
|
} else {
|
|
eprintln!(" Destination file already exists, skipping...");
|
|
}
|
|
}
|
|
}
|
|
(new_name_base, is_subtitle)
|
|
}
|
|
|
|
// Function to process the passed arguments
|
|
pub fn process_args(mut args: Vec<String>) -> (Vec<String>, HashMap<&'static str, bool>) {
|
|
// Remove the entry corresponding to the running process
|
|
args.remove(0);
|
|
let mut entries = Vec::new();
|
|
let mut settings = HashMap::from([("dry_run", false), ("directory", false)]);
|
|
for arg in args {
|
|
match arg.as_str() {
|
|
"--help" | "-h" => {
|
|
println!(" The expected syntax is:");
|
|
println!(
|
|
" movie-rename <filename(s)> [-n|--dry-run] [-d|--directory] [-v|--version]"
|
|
);
|
|
println!(
|
|
" There needs to be a config file named config in the $XDG_CONFIG_HOME/movie-rename/ directory."
|
|
);
|
|
println!(" It should consist of two lines. The first line should have your TMDb API key.");
|
|
println!(
|
|
" The second line should have a pattern, that will be used for the rename."
|
|
);
|
|
println!(" In the pattern, the variables need to be enclosed in {{}}, the supported variables are `title`, `year` and `director`.");
|
|
println!(
|
|
" Default pattern is `{{title}} ({{year}}) - {{director}}`. Extension is always kept."
|
|
);
|
|
println!(" Passing --directory or -d assumes that the arguments are directory names, which contain exactly one movie and optionally subtitles.");
|
|
println!(" Passing --dry-run or -n does a dry tun and only prints out the new names, without actually doing anything.");
|
|
println!(" Passing -nd or -dn does a dry run in directory mode.");
|
|
println!(" Passing --version or -v shows version and exits.");
|
|
println!(" Pass --help to get this again.");
|
|
exit(0);
|
|
}
|
|
"--version" | "-v" => {
|
|
println!("movie-rename {}", VERSION);
|
|
exit(0);
|
|
}
|
|
"--dry-run" | "-n" => {
|
|
println!("Doing a dry run...");
|
|
settings.entry("dry_run").and_modify(|x| *x = true);
|
|
}
|
|
"--directory" | "-d" => {
|
|
println!("Running in directory mode...");
|
|
settings.entry("directory").and_modify(|x| *x = true);
|
|
}
|
|
"-nd" | "-dn" => {
|
|
println!("Doing a dry run in directory mode...");
|
|
settings.entry("dry_run").and_modify(|x| *x = true);
|
|
settings.entry("directory").and_modify(|x| *x = true);
|
|
}
|
|
other => {
|
|
if other.starts_with("-") {
|
|
eprintln!("Unknown argument passed: {}", other);
|
|
exit(1);
|
|
} else {
|
|
entries.push(arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
(entries, settings)
|
|
}
|
|
|
|
// RenderConfig for the menu items
|
|
fn get_render_config() -> RenderConfig {
|
|
let mut render_config = RenderConfig::default();
|
|
render_config.option_index_prefix = IndexPrefix::Simple;
|
|
|
|
render_config.error_message = render_config
|
|
.error_message
|
|
.with_prefix(Styled::new("❌").with_fg(Color::LightRed));
|
|
|
|
render_config
|
|
}
|