mirror of
https://github.com/SinTan1729/movie-rename.git
synced 2024-12-27 04:38:35 -06:00
Refactor functions and structs into separate files
This commit is contained in:
parent
28e7972a15
commit
006c80a822
3 changed files with 269 additions and 217 deletions
178
src/functions.rs
Normal file
178
src/functions.rs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
use std::{collections::HashMap, fs, process::exit};
|
||||||
|
use tmdb::{model::*, themoviedb::*};
|
||||||
|
use torrent_name_parser::Metadata;
|
||||||
|
use youchoose;
|
||||||
|
|
||||||
|
use crate::structs::{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) {
|
||||||
|
// 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 => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {}!", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {}!", filename);
|
||||||
|
return ("".to_string(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose from the possible entries
|
||||||
|
let mut menu = youchoose::Menu::new(movie_list.iter())
|
||||||
|
.preview(display)
|
||||||
|
.preview_label(file_base.to_string());
|
||||||
|
let choice = menu.show()[0];
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
let languages = Language::generate_list();
|
||||||
|
let mut lang_menu = youchoose::Menu::new(languages.iter());
|
||||||
|
let lang_choice = lang_menu.show()[0];
|
||||||
|
if languages[lang_choice].short != "none".to_string() {
|
||||||
|
extension = format!("{}.{}", languages[lang_choice].short, extension);
|
||||||
|
}
|
||||||
|
is_subtitle = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new name
|
||||||
|
let mut new_name_base = movie_list[choice].rename_format(pattern.to_string());
|
||||||
|
if extension != "" {
|
||||||
|
new_name_base = format!("{}.{}", new_name_base, extension);
|
||||||
|
}
|
||||||
|
let mut new_name = String::from(new_name_base.clone());
|
||||||
|
if parent != "".to_string() {
|
||||||
|
new_name = format!("{}/{}", parent, new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the renaming
|
||||||
|
if *filename == new_name {
|
||||||
|
println!("[file] {} already has correct name.", filename);
|
||||||
|
} else {
|
||||||
|
println!("[file] {} -> {}", file_base, new_name_base);
|
||||||
|
// Only do the rename of --dry-run isn't passed
|
||||||
|
if dry_run == false {
|
||||||
|
fs::rename(filename, new_name.as_str()).expect("Unable to rename file!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(new_name_base, is_subtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display function for preview in menu
|
||||||
|
fn display(movie: &MovieEntry) -> String {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
buffer.push_str(&format!("Title: {}\n", movie.title));
|
||||||
|
buffer.push_str(&format!("Release year: {}\n", movie.year));
|
||||||
|
buffer.push_str(&format!("Language: {}\n", movie.language));
|
||||||
|
buffer.push_str(&format!("Director: {}\n", movie.director));
|
||||||
|
buffer.push_str(&format!("TMDb ID: {}\n", movie.id));
|
||||||
|
buffer.push_str(&format!("Overview: {}\n", movie.overview));
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 names movie_rename.conf in your $XDG_CONFIG_HOME."
|
||||||
|
);
|
||||||
|
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 assumes that the arguments are directory names, which contain exactly one movie and optionally subtitles.");
|
||||||
|
println!(" Pass --help to get this again.");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
"--version" | "-v" => {
|
||||||
|
println!("movie_rename {}", VERSION);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
if other.starts_with("-") {
|
||||||
|
eprintln!("Unknown argument passed: {}", other);
|
||||||
|
} else {
|
||||||
|
entries.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(entries, settings)
|
||||||
|
}
|
223
src/main.rs
223
src/main.rs
|
@ -1,58 +1,11 @@
|
||||||
use load_file::{self, load_str};
|
use load_file::{self, load_str};
|
||||||
use std::{collections::HashMap, env, fmt, fs, path::Path, process::exit};
|
use std::{env, fs, path::Path, process::exit};
|
||||||
use tmdb::{model::*, themoviedb::*};
|
use tmdb::themoviedb::*;
|
||||||
use torrent_name_parser::Metadata;
|
|
||||||
use youchoose;
|
|
||||||
|
|
||||||
// Get the version from Cargo.toml
|
// Import all the modules
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
mod functions;
|
||||||
|
use functions::{process_args, process_file};
|
||||||
// Struct for movie entries
|
mod structs;
|
||||||
struct MovieEntry {
|
|
||||||
title: String,
|
|
||||||
id: u64,
|
|
||||||
director: String,
|
|
||||||
year: String,
|
|
||||||
language: String,
|
|
||||||
overview: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MovieEntry {
|
|
||||||
// Create movie entry from results
|
|
||||||
fn from(movie: SearchMovie) -> MovieEntry {
|
|
||||||
MovieEntry {
|
|
||||||
title: movie.title,
|
|
||||||
id: movie.id,
|
|
||||||
director: String::from("N/A"),
|
|
||||||
year: String::from(movie.release_date.split('-').next().unwrap_or("N/A")),
|
|
||||||
language: movie.original_language,
|
|
||||||
overview: movie.overview.unwrap_or(String::from("N/A")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate desired filename from movie entry
|
|
||||||
fn rename_format(&self, mut format: String) -> String {
|
|
||||||
format = format.replace("{title}", self.title.as_str());
|
|
||||||
if self.year.as_str() != "N/A" {
|
|
||||||
format = format.replace("{year}", self.year.as_str());
|
|
||||||
} else {
|
|
||||||
format = format.replace("{year}", "");
|
|
||||||
}
|
|
||||||
if self.director.as_str() != "N/A" {
|
|
||||||
format = format.replace("{director}", self.director.as_str());
|
|
||||||
} else {
|
|
||||||
format = format.replace("{director}", "");
|
|
||||||
}
|
|
||||||
format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement display trait for movie entries
|
|
||||||
impl fmt::Display for MovieEntry {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{} ({})", self.title, self.year)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Read arguments from commandline
|
// Read arguments from commandline
|
||||||
|
@ -142,167 +95,3 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to process movie entries
|
|
||||||
fn process_file(filename: &String, tmdb: &TMDb, pattern: &str, dry_run: bool) -> (String, bool) {
|
|
||||||
// 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 => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {}!", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {}!", filename);
|
|
||||||
return ("".to_string(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose from the possible entries
|
|
||||||
let mut menu = youchoose::Menu::new(movie_list.iter())
|
|
||||||
.preview(display)
|
|
||||||
.preview_label(file_base.to_string());
|
|
||||||
let choice = menu.show()[0];
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
let languages = Vec::from(["en", "hi", "bn", "de", "fr", "sp", "ja", "n/a"]);
|
|
||||||
let mut lang_menu = youchoose::Menu::new(languages.iter());
|
|
||||||
let lang_choice = lang_menu.show()[0];
|
|
||||||
if languages[lang_choice] != "none" {
|
|
||||||
extension = format!("{}.{}", languages[lang_choice], extension);
|
|
||||||
}
|
|
||||||
is_subtitle = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the new name
|
|
||||||
let new_name_base = movie_list[choice].rename_format(pattern.to_string());
|
|
||||||
let mut new_name = String::from(new_name_base.clone());
|
|
||||||
if extension != "" {
|
|
||||||
new_name = format!("{}.{}", new_name, extension);
|
|
||||||
}
|
|
||||||
if parent != "".to_string() {
|
|
||||||
new_name = format!("{}/{}", parent, new_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the renaming
|
|
||||||
if *filename == new_name {
|
|
||||||
println!("[file] {} already has correct name.", filename);
|
|
||||||
} else {
|
|
||||||
println!("[file] {} -> {}", file_base, new_name);
|
|
||||||
// Only do the rename of --dry-run isn't passed
|
|
||||||
if dry_run == false {
|
|
||||||
fs::rename(filename, new_name.as_str()).expect("Unable to rename file!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(new_name_base, is_subtitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display function for preview in menu
|
|
||||||
fn display(movie: &MovieEntry) -> String {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
buffer.push_str(&format!("Title: {}\n", movie.title));
|
|
||||||
buffer.push_str(&format!("Release year: {}\n", movie.year));
|
|
||||||
buffer.push_str(&format!("Language: {}\n", movie.language));
|
|
||||||
buffer.push_str(&format!("Director: {}\n", movie.director));
|
|
||||||
buffer.push_str(&format!("TMDb ID: {}\n", movie.id));
|
|
||||||
buffer.push_str(&format!("Overview: {}\n", movie.overview));
|
|
||||||
buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to process the passed arguments
|
|
||||||
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 names movie_rename.conf in your $XDG_CONFIG_HOME."
|
|
||||||
);
|
|
||||||
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 assumes that the arguments are directory names, which contain exactly one movie and optionally subtitles.");
|
|
||||||
println!(" Pass --help to get this again.");
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
"--version" | "-v" => {
|
|
||||||
println!("movie_rename {}", VERSION);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
if other.starts_with("-") {
|
|
||||||
eprintln!("Unknown argument passed: {}", other);
|
|
||||||
} else {
|
|
||||||
entries.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(entries, settings)
|
|
||||||
}
|
|
||||||
|
|
85
src/structs.rs
Normal file
85
src/structs.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use std::fmt;
|
||||||
|
use tmdb::model::*;
|
||||||
|
|
||||||
|
// Struct for movie entries
|
||||||
|
pub struct MovieEntry {
|
||||||
|
pub title: String,
|
||||||
|
pub id: u64,
|
||||||
|
pub director: String,
|
||||||
|
pub year: String,
|
||||||
|
pub language: String,
|
||||||
|
pub overview: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MovieEntry {
|
||||||
|
// Create movie entry from results
|
||||||
|
pub fn from(movie: SearchMovie) -> MovieEntry {
|
||||||
|
MovieEntry {
|
||||||
|
title: movie.title,
|
||||||
|
id: movie.id,
|
||||||
|
director: String::from("N/A"),
|
||||||
|
year: String::from(movie.release_date.split('-').next().unwrap_or("N/A")),
|
||||||
|
language: movie.original_language,
|
||||||
|
overview: movie.overview.unwrap_or(String::from("N/A")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate desired filename from movie entry
|
||||||
|
pub fn rename_format(&self, mut format: String) -> String {
|
||||||
|
format = format.replace("{title}", self.title.as_str());
|
||||||
|
if self.year.as_str() != "N/A" {
|
||||||
|
format = format.replace("{year}", self.year.as_str());
|
||||||
|
} else {
|
||||||
|
format = format.replace("{year}", "");
|
||||||
|
}
|
||||||
|
if self.director.as_str() != "N/A" {
|
||||||
|
format = format.replace("{director}", self.director.as_str());
|
||||||
|
} else {
|
||||||
|
format = format.replace("{director}", "");
|
||||||
|
}
|
||||||
|
format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement display trait for movie entries
|
||||||
|
impl fmt::Display for MovieEntry {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} ({})", self.title, self.year)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Language {
|
||||||
|
pub short: String,
|
||||||
|
pub long: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Language {
|
||||||
|
// Create Language entries from &str pairs
|
||||||
|
fn from(short: &str, long: &str) -> Language {
|
||||||
|
Language {
|
||||||
|
short: short.to_string(),
|
||||||
|
long: long.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a vector of Language entries of all supported languages
|
||||||
|
pub fn generate_list() -> Vec<Language> {
|
||||||
|
let mut list = Vec::new();
|
||||||
|
list.push(Language::from("en", "English"));
|
||||||
|
list.push(Language::from("hi", "Hindi"));
|
||||||
|
list.push(Language::from("bn", "Bengali"));
|
||||||
|
list.push(Language::from("fr", "French"));
|
||||||
|
list.push(Language::from("ja", "Japanese"));
|
||||||
|
list.push(Language::from("de", "German"));
|
||||||
|
list.push(Language::from("sp", "Spanish"));
|
||||||
|
list.push(Language::from("none", "None"));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement display trait for Language
|
||||||
|
impl fmt::Display for Language {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.long)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue