mirror of
https://github.com/SinTan1729/movie-rename.git
synced 2025-04-20 02:40:00 -05:00
Compare commits
No commits in common. "main" and "2.2.2" have entirely different histories.
8 changed files with 492 additions and 1158 deletions
1431
Cargo.lock
generated
1431
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
@ -1,7 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "movie-rename"
|
name = "movie-rename"
|
||||||
version = "2.3.2"
|
version = "2.2.2"
|
||||||
build = "build.rs"
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"]
|
authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"]
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
|
@ -17,15 +16,10 @@ categories = ["command-line-utilities"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
torrent-name-parser = "0.12.1"
|
torrent-name-parser = "0.12.1"
|
||||||
tmdb-api = "0.8.0"
|
tmdb-api = "0.5.0"
|
||||||
inquire = "0.7.5"
|
inquire = "0.6.2"
|
||||||
load_file = "1.0.1"
|
load_file = "1.0.1"
|
||||||
tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] }
|
||||||
clap = { version = "4.5.1", features = ["cargo"] }
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
clap = { version = "4.5.1", features = ["cargo"] }
|
|
||||||
clap_complete = "4.5.1"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -2,7 +2,7 @@ PREFIX := /usr/local
|
||||||
PKGNAME := movie-rename
|
PKGNAME := movie-rename
|
||||||
|
|
||||||
build:
|
build:
|
||||||
cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.34
|
cargo build --release
|
||||||
|
|
||||||
build-debug:
|
build-debug:
|
||||||
cargo build
|
cargo build
|
||||||
|
@ -19,6 +19,6 @@ uninstall:
|
||||||
rm -f "$(DESTDIR)$(PREFIX)/man/man1/$(PKGNAME).1"
|
rm -f "$(DESTDIR)$(PREFIX)/man/man1/$(PKGNAME).1"
|
||||||
|
|
||||||
aur: build
|
aur: build
|
||||||
tar --transform 's/.*\///g' -czf $(PKGNAME).tar.gz target/x86_64-unknown-linux-gnu/release/$(PKGNAME) target/autocomplete/* $(PKGNAME).1
|
tar --transform 's/.*\///g' -czf $(PKGNAME).tar.gz target/release/$(PKGNAME) $(PKGNAME).1
|
||||||
|
|
||||||
.PHONY: build build-debug install clean uninstall aur
|
.PHONY: build build-debug install clean uninstall aur
|
||||||
|
|
21
build.rs
21
build.rs
|
@ -1,21 +0,0 @@
|
||||||
use clap_complete::generate_to;
|
|
||||||
use clap_complete::shells::{Bash, Fish, Zsh};
|
|
||||||
use std::env;
|
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::fs::{create_dir, remove_dir_all};
|
|
||||||
use std::io::Error;
|
|
||||||
|
|
||||||
include!("src/args.rs");
|
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
|
||||||
let target = "./target/autocomplete";
|
|
||||||
remove_dir_all(target).ok();
|
|
||||||
create_dir(target)?;
|
|
||||||
let outdir = OsString::from(target);
|
|
||||||
|
|
||||||
let mut cmd = get_command();
|
|
||||||
generate_to(Bash, &mut cmd, "movie-rename", &outdir)?;
|
|
||||||
generate_to(Fish, &mut cmd, "movie-rename", &outdir)?;
|
|
||||||
generate_to(Zsh, &mut cmd, "movie-rename", &outdir)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -17,6 +17,9 @@ Performs a dry run, without actually renaming anything.
|
||||||
-d, --directory
|
-d, --directory
|
||||||
Runs in directory mode. In this mode, it is assumed that the arguments are directory names, which contain exactly one movie and optionally subtitles.
|
Runs in directory mode. In this mode, it is assumed that the arguments are directory names, which contain exactly one movie and optionally subtitles.
|
||||||
.TP
|
.TP
|
||||||
|
-dn, -nd
|
||||||
|
Performs a dry run in directory mode.
|
||||||
|
.TP
|
||||||
-h, --help
|
-h, --help
|
||||||
Print help information.
|
Print help information.
|
||||||
.TP
|
.TP
|
||||||
|
|
46
src/args.rs
46
src/args.rs
|
@ -1,46 +0,0 @@
|
||||||
use clap::{arg, command, ArgAction, Command, ValueHint};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
// Bare command generation function to help with autocompletion
|
|
||||||
pub fn get_command() -> Command {
|
|
||||||
command!()
|
|
||||||
.name("movie-rename")
|
|
||||||
.author("Sayantan Santra <sayantan.santra@gmail.com>")
|
|
||||||
.about("A simple tool to rename movies, written in Rust.")
|
|
||||||
.arg(arg!(-d --directory "Run in directory mode").action(ArgAction::SetTrue))
|
|
||||||
.arg(arg!(-n --"dry-run" "Do a dry run").action(ArgAction::SetTrue))
|
|
||||||
.arg(arg!(-l --"i-feel-lucky" "Always choose the first option").action(ArgAction::SetTrue))
|
|
||||||
.arg(
|
|
||||||
arg!([entries] "The files/directories to be processed")
|
|
||||||
.trailing_var_arg(true)
|
|
||||||
.num_args(1..)
|
|
||||||
.value_hint(ValueHint::AnyPath)
|
|
||||||
.required(true),
|
|
||||||
)
|
|
||||||
// Use -v instead of -V for version
|
|
||||||
.disable_version_flag(true)
|
|
||||||
.arg(arg!(-v --version "Print version").action(ArgAction::Version))
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to process the passed arguments
|
|
||||||
pub fn process_args() -> (Vec<String>, HashMap<String, bool>) {
|
|
||||||
let matches = get_command().get_matches();
|
|
||||||
|
|
||||||
// Generate the settings HashMap from read flags
|
|
||||||
let mut settings = HashMap::new();
|
|
||||||
for id in matches.ids().map(|x| x.as_str()) {
|
|
||||||
if id != "entries" {
|
|
||||||
settings.insert(id.to_string(), matches.get_flag(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every unmatched argument should be treated as a file entry
|
|
||||||
let entries: Vec<String> = matches
|
|
||||||
.get_many::<String>("entries")
|
|
||||||
.expect("No entries provided!")
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
(entries, settings)
|
|
||||||
}
|
|
|
@ -2,20 +2,23 @@ use inquire::{
|
||||||
ui::{Color, IndexPrefix, RenderConfig, Styled},
|
ui::{Color, IndexPrefix, RenderConfig, Styled},
|
||||||
InquireError, Select,
|
InquireError, Select,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, fs, path::Path};
|
use std::{collections::HashMap, fs, path::Path, process::exit};
|
||||||
use tmdb_api::{
|
use tmdb_api::{
|
||||||
client::{reqwest::ReqwestExecutor, Client},
|
|
||||||
movie::{credits::MovieCredits, search::MovieSearch},
|
movie::{credits::MovieCredits, search::MovieSearch},
|
||||||
prelude::Command,
|
prelude::Command,
|
||||||
|
Client,
|
||||||
};
|
};
|
||||||
use torrent_name_parser::Metadata;
|
use torrent_name_parser::Metadata;
|
||||||
|
|
||||||
use crate::structs::{get_long_lang, Language, MovieEntry};
|
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
|
// Function to process movie entries
|
||||||
pub async fn process_file(
|
pub async fn process_file(
|
||||||
filename: &String,
|
filename: &String,
|
||||||
tmdb: &Client<ReqwestExecutor>,
|
tmdb: &Client,
|
||||||
pattern: &str,
|
pattern: &str,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
lucky: bool,
|
lucky: bool,
|
||||||
|
@ -205,8 +208,86 @@ pub async fn process_file(
|
||||||
(filename_without_ext, Some(new_name_base), true)
|
(filename_without_ext, Some(new_name_base), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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), ("lucky", 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] [-l|--i-feel-lucky] [-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!(
|
||||||
|
" You can join the short flags -d, -n and -l together (e.g. -dn or -dln)."
|
||||||
|
);
|
||||||
|
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" => {
|
||||||
|
println!("Doing a dry run...");
|
||||||
|
settings.entry("dry_run").and_modify(|x| *x = true);
|
||||||
|
}
|
||||||
|
"--i-feel-lucky" => {
|
||||||
|
println!("Choosing the first option automatically...");
|
||||||
|
settings.entry("lucky").and_modify(|x| *x = true);
|
||||||
|
}
|
||||||
|
"--directory" => {
|
||||||
|
println!("Running in directory mode...");
|
||||||
|
settings.entry("directory").and_modify(|x| *x = true);
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
if other.starts_with('-') {
|
||||||
|
if !other.starts_with("--") && other.len() < 5 {
|
||||||
|
// Process short args
|
||||||
|
if other.contains('l') {
|
||||||
|
println!("Choosing the first option automatically...");
|
||||||
|
settings.entry("lucky").and_modify(|x| *x = true);
|
||||||
|
}
|
||||||
|
if other.contains('d') {
|
||||||
|
println!("Running in directory mode...");
|
||||||
|
settings.entry("directory").and_modify(|x| *x = true);
|
||||||
|
}
|
||||||
|
if other.contains('n') {
|
||||||
|
println!("Doing a dry run...");
|
||||||
|
settings.entry("dry_run").and_modify(|x| *x = true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("Unknown argument passed: {other}");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entries.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(entries, settings)
|
||||||
|
}
|
||||||
|
|
||||||
// RenderConfig for the menu items
|
// RenderConfig for the menu items
|
||||||
fn get_render_config() -> RenderConfig<'static> {
|
fn get_render_config() -> RenderConfig {
|
||||||
let mut render_config = RenderConfig::default();
|
let mut render_config = RenderConfig::default();
|
||||||
render_config.option_index_prefix = IndexPrefix::Simple;
|
render_config.option_index_prefix = IndexPrefix::Simple;
|
||||||
|
|
||||||
|
|
44
src/main.rs
44
src/main.rs
|
@ -1,31 +1,19 @@
|
||||||
use load_file::{self, load_str};
|
use load_file::{self, load_str};
|
||||||
use std::{collections::HashMap, env, fs, path::Path, process::exit};
|
use std::{collections::HashMap, env, fs, path::Path, process::exit};
|
||||||
use tmdb_api::client::{reqwest::ReqwestExecutor, Client};
|
use tmdb_api::Client;
|
||||||
|
|
||||||
// Import all the modules
|
// Import all the modules
|
||||||
mod functions;
|
mod functions;
|
||||||
use functions::process_file;
|
use functions::{process_args, process_file};
|
||||||
mod args;
|
|
||||||
mod structs;
|
mod structs;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Process the passed arguments
|
// Read arguments from commandline
|
||||||
let (entries, settings) = args::process_args();
|
let args: Vec<String> = env::args().collect();
|
||||||
let flag_dry_run = settings["dry-run"];
|
|
||||||
let flag_directory = settings["directory"];
|
|
||||||
let flag_lucky = settings["i-feel-lucky"];
|
|
||||||
|
|
||||||
// Print some message when flags are set.
|
// Process the passed arguments
|
||||||
if flag_dry_run {
|
let (entries, settings) = process_args(args);
|
||||||
println!("Doing a dry run. No files will be modified.")
|
|
||||||
}
|
|
||||||
if flag_directory {
|
|
||||||
println!("Running in directory mode...")
|
|
||||||
}
|
|
||||||
if flag_lucky {
|
|
||||||
println!("Automatically selecting the first entry...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to read config file, or display error
|
// Try to read config file, or display error
|
||||||
let mut config_file = env::var("XDG_CONFIG_HOME").unwrap_or(String::from("$HOME"));
|
let mut config_file = env::var("XDG_CONFIG_HOME").unwrap_or(String::from("$HOME"));
|
||||||
|
@ -50,17 +38,25 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create TMDb object for API calls
|
// Create TMDb object for API calls
|
||||||
let tmdb = Client::<ReqwestExecutor>::new(String::from(api_key));
|
let tmdb = Client::new(String::from(api_key));
|
||||||
|
|
||||||
// Iterate over entries
|
// Iterate over entries
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
// Check if the file/directory exists on disk and run necessary commands
|
// Check if the file/directory exists on disk and run necessary commands
|
||||||
match flag_directory {
|
match settings["directory"] {
|
||||||
// Normal file
|
// Normal file
|
||||||
false => {
|
false => {
|
||||||
if Path::new(entry.as_str()).is_file() {
|
if Path::new(entry.as_str()).is_file() {
|
||||||
// Process the filename for movie entries
|
// Process the filename for movie entries
|
||||||
process_file(&entry, &tmdb, pattern, flag_dry_run, flag_lucky, None).await;
|
process_file(
|
||||||
|
&entry,
|
||||||
|
&tmdb,
|
||||||
|
pattern,
|
||||||
|
settings["dry_run"],
|
||||||
|
settings["lucky"],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("The file {entry} wasn't found on disk, skipping...");
|
eprintln!("The file {entry} wasn't found on disk, skipping...");
|
||||||
continue;
|
continue;
|
||||||
|
@ -81,8 +77,8 @@ async fn main() {
|
||||||
&filename,
|
&filename,
|
||||||
&tmdb,
|
&tmdb,
|
||||||
pattern,
|
pattern,
|
||||||
flag_dry_run,
|
settings["dry_run"],
|
||||||
flag_lucky,
|
settings["lucky"],
|
||||||
Some(&movie_list),
|
Some(&movie_list),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -113,7 +109,7 @@ async fn main() {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("[directory] '{entry_clean}' -> '{name}'",);
|
println!("[directory] '{entry_clean}' -> '{name}'",);
|
||||||
if !flag_dry_run {
|
if !settings["dry_run"] {
|
||||||
if !Path::new(name.as_str()).is_dir() {
|
if !Path::new(name.as_str()).is_dir() {
|
||||||
fs::rename(entry, name)
|
fs::rename(entry, name)
|
||||||
.expect("Unable to rename directory!");
|
.expect("Unable to rename directory!");
|
||||||
|
|
Loading…
Reference in a new issue