Compare commits

...

34 commits
2.1.2 ... main

Author SHA1 Message Date
49de0ce13c
build: Bumped version to 2.3.2 2025-04-07 21:26:27 -05:00
cd0dbcd47b
build: Updated deps to mitigate an issue in tokio 2025-04-07 21:25:19 -05:00
3757dd0a47
fix: Improved compatibility of built package 2025-03-08 16:37:23 -06:00
cf8a9ee764
build: Bumped version to 2.3.1 2025-03-08 16:06:18 -06:00
80fdc110ae
fix: Adapt to changes in deps 2025-03-08 16:05:53 -06:00
c5b1f51233
build: Updated deps 2025-03-08 15:53:54 -06:00
Sayantan Santra
3e7b8fbd5d
Merge pull request #7 from SinTan1729/dependabot/cargo/h2-0.3.26
build(deps): bump h2 from 0.3.24 to 0.3.26
2024-04-05 11:47:00 -05:00
dependabot[bot]
f979559b50
build(deps): bump h2 from 0.3.24 to 0.3.26
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 16:18:23 +00:00
443c8e6cc9
new: Print some message when flags are set 2024-03-11 18:23:16 -05:00
4f3f349292
chg: Slightly modified the manpage 2024-03-08 10:45:04 -06:00
51e0c84eb4
build: Bumped version to 2.3.0 2024-03-06 15:42:36 -06:00
83bf1f7af6
new: Autocomplete files generation 2024-03-06 15:41:45 -06:00
b4073357f7
fix: No need for the unwrap_or()s 2024-03-06 03:02:53 -06:00
67dcd9e378
new: Do argparsing using clap-rs instead of handwritten parser 2024-03-06 02:56:48 -06:00
b62bffd340
build: Bump version to 2.2.2 2024-03-04 17:09:59 -06:00
Sayantan Santra
4a69071380
Merge pull request #6 from SinTan1729/dependabot/cargo/mio-0.8.11
Bump mio from 0.8.8 to 0.8.11
2024-03-04 17:03:41 -06:00
dependabot[bot]
6027b701f0
Bump mio from 0.8.8 to 0.8.11
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.8 to 0.8.11.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/compare/v0.8.8...v0.8.11)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 21:55:54 +00:00
1b43b7a017
build: Bump version to 2.2.1 2024-01-27 16:15:58 -06:00
Sayantan Santra
bc2c9c0135
Merge pull request #5 from SinTan1729/dependabot/cargo/h2-0.3.24
Bump h2 from 0.3.19 to 0.3.24
2024-01-27 16:14:23 -06:00
dependabot[bot]
324cae4fed
Bump h2 from 0.3.19 to 0.3.24
Bumps [h2](https://github.com/hyperium/h2) from 0.3.19 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.19...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-19 16:11:49 +00:00
64d1749450
build: Bump version to 2.2.0 2023-12-04 18:27:55 -06:00
bc0ccd8107
feat: Possible to merge the short flags 2023-12-04 18:27:22 -06:00
16118a8d5a
feat: Added an option to automatically choose the first option 2023-12-04 17:47:55 -06:00
790ec225ea
build: Bump version to 2.1.6 2023-10-02 22:29:40 -05:00
Sayantan Santra
e74c35472a
Merge pull request #4 from SinTan1729/dependabot/cargo/webpki-0.22.2
Bump webpki from 0.22.1 to 0.22.2
2023-10-03 03:27:59 +00:00
dependabot[bot]
0a851a47fd
Bump webpki from 0.22.1 to 0.22.2
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.1 to 0.22.2.
- [Commits](https://github.com/briansmith/webpki/commits)

---
updated-dependencies:
- dependency-name: webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 21:53:19 +00:00
c2152264f9
fix: Version number 2023-09-13 18:24:47 -05:00
Sayantan Santra
3695c78282
Merge pull request #3 from SinTan1729/dependabot/cargo/webpki-0.22.1
Bump webpki from 0.22.0 to 0.22.1
2023-09-13 23:09:51 +00:00
dependabot[bot]
f827e351c3
Bump webpki from 0.22.0 to 0.22.1
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.0 to 0.22.1.
- [Commits](https://github.com/briansmith/webpki/commits)

---
updated-dependencies:
- dependency-name: webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-13 23:09:15 +00:00
39835837c4
chg: Updated dependencies 2023-08-25 02:27:54 -05:00
a2f0536642
build: Bumped version to 2.1.4 2023-05-29 16:39:50 -05:00
fd98c5d355
new: Support all ISO639_1 codes 2023-05-29 16:38:11 -05:00
cc59a03051
build: Bumped version to 2.1.3 2023-05-29 00:14:54 -05:00
89d15e9fb7
fix: Properly output Option enums 2023-05-29 00:14:12 -05:00
10 changed files with 1356 additions and 529 deletions

1580
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
[package]
name = "movie-rename"
version = "2.1.2"
version = "2.3.2"
build = "build.rs"
edition = "2021"
authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"]
license = "GPL-3.0"
@ -16,10 +17,15 @@ categories = ["command-line-utilities"]
[dependencies]
torrent-name-parser = "0.12.1"
tmdb-api = "0.4.0"
inquire = "0.6.2"
tmdb-api = "0.8.0"
inquire = "0.7.5"
load_file = "1.0.1"
tokio = { version = "1.28.1", 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]
strip = true

View file

@ -2,7 +2,7 @@ PREFIX := /usr/local
PKGNAME := movie-rename
build:
cargo build --release
cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.34
build-debug:
cargo build
@ -19,6 +19,6 @@ uninstall:
rm -f "$(DESTDIR)$(PREFIX)/man/man1/$(PKGNAME).1"
aur: build
tar --transform 's/.*\///g' -czf $(PKGNAME).tar.gz target/release/$(PKGNAME) $(PKGNAME).1
tar --transform 's/.*\///g' -czf $(PKGNAME).tar.gz target/x86_64-unknown-linux-gnu/release/$(PKGNAME) target/autocomplete/* $(PKGNAME).1
.PHONY: build build-debug install clean uninstall aur

View file

@ -30,7 +30,8 @@ sudo make install
- Default pattern is `{title} ({year}) - {director}`. Extension is always kept.
- Passing `--directory` or `-d` assumes that the arguments are directory names, which contain exactly one movie and optionally subtitles.
- Passing `--dry-run` or `-n` does a dry tun and only prints out the new names, without actually doing anything.
- Passing `-nd` or `-dn` does a dry run in directory mode.
- Passing `--i-feel-lucky` or `-l` automatically chooses the first option. Useful when you use the program as part of a script.
- You can join the short flags `-d`, `-n` and `-l` together (e.g. `-dn` or `-dln`).
- Passing `--help` or `-h` shows help and exits.
- Passing `--version` or `-v` shows version and exits.

21
build.rs Normal file
View file

@ -0,0 +1,21 @@
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(())
}

View file

@ -17,9 +17,6 @@ Performs a dry run, without actually renaming anything.
-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.
.TP
-dn, -nd
Performs a dry run in directory mode.
.TP
-h, --help
Print help information.
.TP

46
src/args.rs Normal file
View file

@ -0,0 +1,46 @@
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)
}

View file

@ -2,25 +2,23 @@ use inquire::{
ui::{Color, IndexPrefix, RenderConfig, Styled},
InquireError, Select,
};
use std::{collections::HashMap, fs, path::Path, process::exit};
use std::{collections::HashMap, fs, path::Path};
use tmdb_api::{
client::{reqwest::ReqwestExecutor, Client},
movie::{credits::MovieCredits, search::MovieSearch},
prelude::Command,
Client,
};
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 async fn process_file(
filename: &String,
tmdb: &Client,
tmdb: &Client<ReqwestExecutor>,
pattern: &str,
dry_run: bool,
lucky: bool,
movie_list: Option<&HashMap<String, Option<String>>>,
// The last bool tells whether the entry should be added to the movie_list or not
// The first String is filename without extension, and the second String is
@ -129,22 +127,28 @@ pub async fn process_file(
return (filename_without_ext, None, true);
}
// Choose from the possible entries
let choice = match Select::new(
format!(" Possible choices for {file_base}:").as_str(),
movie_list,
)
.prompt()
{
Ok(movie) => movie,
Err(error) => {
println!(" {error}");
let flag = matches!(
error,
InquireError::OperationCanceled | InquireError::OperationInterrupted
);
return (filename_without_ext, None, flag);
}
let choice;
if lucky {
// Take first choice if in lucky mode
choice = movie_list.into_iter().next().unwrap();
} else {
// Choose from the possible entries
choice = match Select::new(
format!(" Possible choices for {file_base}:").as_str(),
movie_list,
)
.prompt()
{
Ok(movie) => movie,
Err(error) => {
println!(" {error}");
let flag = matches!(
error,
InquireError::OperationCanceled | InquireError::OperationInterrupted
);
return (filename_without_ext, None, flag);
}
};
};
// Create the new name
@ -201,69 +205,8 @@ pub async fn process_file(
(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)]);
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 {
fn get_render_config() -> RenderConfig<'static> {
let mut render_config = RenderConfig::default();
render_config.option_index_prefix = IndexPrefix::Simple;

View file

@ -1,19 +1,31 @@
use load_file::{self, load_str};
use std::{collections::HashMap, env, fs, path::Path, process::exit};
use tmdb_api::Client;
use tmdb_api::client::{reqwest::ReqwestExecutor, Client};
// Import all the modules
mod functions;
use functions::{process_args, process_file};
use functions::process_file;
mod args;
mod structs;
#[tokio::main]
async fn main() {
// Read arguments from commandline
let args: Vec<String> = env::args().collect();
// Process the passed arguments
let (entries, settings) = process_args(args);
let (entries, settings) = args::process_args();
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.
if flag_dry_run {
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
let mut config_file = env::var("XDG_CONFIG_HOME").unwrap_or(String::from("$HOME"));
@ -38,17 +50,17 @@ async fn main() {
}
// Create TMDb object for API calls
let tmdb = Client::new(String::from(api_key));
let tmdb = Client::<ReqwestExecutor>::new(String::from(api_key));
// Iterate over entries
for entry in entries {
// Check if the file/directory exists on disk and run necessary commands
match settings["directory"] {
match flag_directory {
// Normal file
false => {
if Path::new(entry.as_str()).is_file() {
// Process the filename for movie entries
process_file(&entry, &tmdb, pattern, settings["dry_run"], None).await;
process_file(&entry, &tmdb, pattern, flag_dry_run, flag_lucky, None).await;
} else {
eprintln!("The file {entry} wasn't found on disk, skipping...");
continue;
@ -69,7 +81,8 @@ async fn main() {
&filename,
&tmdb,
pattern,
settings["dry_run"],
flag_dry_run,
flag_lucky,
Some(&movie_list),
)
.await;
@ -100,7 +113,7 @@ async fn main() {
);
} else {
println!("[directory] '{entry_clean}' -> '{name}'",);
if !settings["dry_run"] {
if !flag_dry_run {
if !Path::new(name.as_str()).is_dir() {
fs::rename(entry, name)
.expect("Unable to rename directory!");

View file

@ -64,12 +64,20 @@ impl fmt::Display for MovieEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buffer = String::new();
buffer.push_str(&format!("{} ", self.title));
buffer.push_str(&format!("({:?}), ", self.year));
if let Some(year) = &self.year {
buffer.push_str(&format!("({year}), "));
}
buffer.push_str(&format!(
"Language: {}, ",
get_long_lang(self.language.as_str())
));
buffer.push_str(&format!("Directed by: {:?}, ", self.director));
if let Some(director) = &self.director {
buffer.push_str(&format!("Directed by: {director}, "));
}
buffer.push_str(&format!("TMDB ID: {}", self.id));
// buffer.push_str(&format!("Synopsis: {}", self.overview));
write!(f, "{buffer}")
@ -104,18 +112,50 @@ impl fmt::Display for Language {
// Get long name of a language
pub fn get_long_lang(short: &str) -> String {
let long = match short {
"en" => "English",
"hi" => "Hindi",
"bn" => "Bengali",
"fr" => "French",
"ja" => "Japanese",
"de" => "German",
"sp" => "Spanish",
"none" => "None",
other => other,
};
String::from(long)
// List used from https://gist.github.com/carlopires/1262033/c52ef0f7ce4f58108619508308372edd8d0bd518#file-gistfile1-txt
#[rustfmt::skip]
static LANG_LIST: [(&str, &str); 185] = [("ab", "Abkhaz"), ("aa", "Afar"), ("af", "Afrikaans"), ("ak", "Akan"), ("sq", "Albanian"),
("am", "Amharic"), ("ar", "Arabic"), ("an", "Aragonese"), ("hy", "Armenian"), ("as", "Assamese"), ("av", "Avaric"),
("ae", "Avestan"), ("ay", "Aymara"), ("az", "Azerbaijani"), ("bm", "Bambara"), ("ba", "Bashkir"), ("eu", "Basque"),
("be", "Belarusian"), ("bn", "Bengali"), ("bh", "Bihari"), ("bi", "Bislama"), ("bs", "Bosnian"), ("br", "Breton"),
("bg", "Bulgarian"), ("my", "Burmese"), ("ca", "Catalan; Valencian"), ("ch", "Chamorro"), ("ce", "Chechen"),
("ny", "Chichewa; Chewa; Nyanja"), ("zh", "Chinese"), ("cv", "Chuvash"), ("kw", "Cornish"), ("co", "Corsican"),
("cr", "Cree"), ("hr", "Croatian"), ("cs", "Czech"), ("da", "Danish"), ("dv", "Divehi; Maldivian;"), ("nl", "Dutch"),
("dz", "Dzongkha"), ("en", "English"), ("eo", "Esperanto"), ("et", "Estonian"), ("ee", "Ewe"), ("fo", "Faroese"),
("fj", "Fijian"), ("fi", "Finnish"), ("fr", "French"), ("ff", "Fula"), ("gl", "Galician"), ("ka", "Georgian"),
("de", "German"), ("el", "Greek, Modern"), ("gn", "Guaraní"), ("gu", "Gujarati"), ("ht", "Haitian"), ("ha", "Hausa"),
("he", "Hebrew (modern)"), ("hz", "Herero"), ("hi", "Hindi"), ("ho", "Hiri Motu"), ("hu", "Hungarian"), ("ia", "Interlingua"),
("id", "Indonesian"), ("ie", "Interlingue"), ("ga", "Irish"), ("ig", "Igbo"), ("ik", "Inupiaq"), ("io", "Ido"), ("is", "Icelandic"),
("it", "Italian"), ("iu", "Inuktitut"), ("ja", "Japanese"), ("jv", "Javanese"), ("kl", "Kalaallisut"), ("kn", "Kannada"),
("kr", "Kanuri"), ("ks", "Kashmiri"), ("kk", "Kazakh"), ("km", "Khmer"), ("ki", "Kikuyu, Gikuyu"), ("rw", "Kinyarwanda"),
("ky", "Kirghiz, Kyrgyz"), ("kv", "Komi"), ("kg", "Kongo"), ("ko", "Korean"), ("ku", "Kurdish"), ("kj", "Kwanyama, Kuanyama"),
("la", "Latin"), ("lb", "Luxembourgish"), ("lg", "Luganda"), ("li", "Limburgish"), ("ln", "Lingala"), ("lo", "Lao"), ("lt", "Lithuanian"),
("lu", "Luba-Katanga"), ("lv", "Latvian"), ("gv", "Manx"), ("mk", "Macedonian"), ("mg", "Malagasy"), ("ms", "Malay"), ("ml", "Malayalam"),
("mt", "Maltese"), ("mi", "Māori"), ("mr", "Marathi (Marāṭhī)"), ("mh", "Marshallese"), ("mn", "Mongolian"), ("na", "Nauru"),
("nv", "Navajo, Navaho"), ("nb", "Norwegian Bokmål"), ("nd", "North Ndebele"), ("ne", "Nepali"), ("ng", "Ndonga"),
("nn", "Norwegian Nynorsk"), ("no", "Norwegian"), ("ii", "Nuosu"), ("nr", "South Ndebele"), ("oc", "Occitan"), ("oj", "Ojibwe, Ojibwa"),
("cu", "Old Church Slavonic"), ("om", "Oromo"), ("or", "Oriya"), ("os", "Ossetian, Ossetic"), ("pa", "Panjabi, Punjabi"), ("pi", "Pāli"),
("fa", "Persian"), ("pl", "Polish"), ("ps", "Pashto, Pushto"), ("pt", "Portuguese"), ("qu", "Quechua"), ("rm", "Romansh"), ("rn", "Kirundi"),
("ro", "Romanian, Moldavan"), ("ru", "Russian"), ("sa", "Sanskrit (Saṁskṛta)"), ("sc", "Sardinian"), ("sd", "Sindhi"), ("se", "Northern Sami"),
("sm", "Samoan"), ("sg", "Sango"), ("sr", "Serbian"), ("gd", "Scottish Gaelic"), ("sn", "Shona"), ("si", "Sinhala, Sinhalese"), ("sk", "Slovak"),
("sl", "Slovene"), ("so", "Somali"), ("st", "Southern Sotho"), ("es", "Spanish; Castilian"), ("su", "Sundanese"), ("sw", "Swahili"),
("ss", "Swati"), ("sv", "Swedish"), ("ta", "Tamil"), ("te", "Telugu"), ("tg", "Tajik"), ("th", "Thai"), ("ti", "Tigrinya"), ("bo", "Tibetan"),
("tk", "Turkmen"), ("tl", "Tagalog"), ("tn", "Tswana"), ("to", "Tonga"), ("tr", "Turkish"), ("ts", "Tsonga"), ("tt", "Tatar"), ("tw", "Twi"),
("ty", "Tahitian"), ("ug", "Uighur, Uyghur"), ("uk", "Ukrainian"), ("ur", "Urdu"), ("uz", "Uzbek"), ("ve", "Venda"), ("vi", "Vietnamese"),
("vo", "Volapük"), ("wa", "Walloon"), ("cy", "Welsh"), ("wo", "Wolof"), ("fy", "Western Frisian"), ("xh", "Xhosa"), ("yi", "Yiddish"),
("yo", "Yoruba"), ("za", "Zhuang, Chuang"), ("zu", "Zulu"), ("none", "None")];
let long = LANG_LIST
.iter()
.filter(|x| x.0 == short)
.map(|x| x.1)
.next();
if let Some(longlang) = long {
String::from(longlang)
} else {
String::from(short)
}
}
// Sanitize filename so that there are no errors while