Add support for additional media types
This commit is contained in:
parent
333cd0c38b
commit
0b3c88d601
5 changed files with 124 additions and 43 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -120,6 +120,12 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.132"
|
||||
|
@ -132,6 +138,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"eyre",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"walkdir",
|
||||
]
|
||||
|
|
|
@ -10,3 +10,4 @@ eyre = "0.6"
|
|||
clap = { version = "3.2", features = ["derive"] }
|
||||
regex = "1.6"
|
||||
walkdir = "2"
|
||||
lazy_static = "1.4"
|
102
src/main.rs
102
src/main.rs
|
@ -5,6 +5,7 @@ mod utils;
|
|||
use std::fmt::{Display, Formatter};
|
||||
use eyre::Result;
|
||||
use clap::Parser;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::{Captures, Regex};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
|
@ -14,7 +15,12 @@ use utils::status_icon::*;
|
|||
|
||||
const VIDEO_EXTENSIONS: [&str; 14] = ["mkv", "mp4", "avi", "webm", "mov", "wmv", "flv", "ogg", "ogv", "yuv", "amv", "mpg", "mpeg", "m4v"];
|
||||
pub(crate) const SERIES_REGEX: &str = r"^(?P<title>.*?)(?P<titleSeparator>\s-\s?)?(?P<seasonPrefix>[Ss]|\s|\.)(?P<season>\d{1,3})(?P<episodePrefix>[Ee]|[Xx]|[Ss])(?P<episode>\d{1,3})([Ee](?P<episode2>\d{2,3}))?((?P<nameSeparator>\s-\s)?(?P<name>.+))?\.(?P<ext>...)$";
|
||||
// const MOVIE_REGEX: &str = r"^(?P<title>.+)\s(?P<year>\(\d{4}\))\s(?P<resolution>\[.+\])\.(?P<ext>...)$";
|
||||
pub(crate) const MOVIE_REGEX: &str = r"^(?P<title>.*?)\s(?P<year>\(\d{4}\))\s(?P<resolution>\[.+\])\.(?P<ext>...)$";
|
||||
|
||||
lazy_static! {
|
||||
static ref SERIES_PARSER: Regex = Regex::new(SERIES_REGEX).unwrap();
|
||||
static ref MOVIE_PARSER: Regex = Regex::new(MOVIE_REGEX).unwrap();
|
||||
}
|
||||
|
||||
struct Stats {
|
||||
success: usize,
|
||||
|
@ -43,8 +49,6 @@ struct Context {
|
|||
fn main() -> Result<()> {
|
||||
let params = Args::parse();
|
||||
|
||||
println!("{}", params.path);
|
||||
|
||||
let mut stats = Stats {
|
||||
files: 0,
|
||||
success: 0,
|
||||
|
@ -57,11 +61,9 @@ fn main() -> Result<()> {
|
|||
has_printed_folder: false,
|
||||
};
|
||||
|
||||
let regex = Regex::new(SERIES_REGEX).unwrap();
|
||||
|
||||
for entry in WalkDir::new(params.path) {
|
||||
if let Ok(file) = entry {
|
||||
check_file(file, &mut stats, &mut ctx, ®ex, params.no_emoji, params.show_success, params.episode_name);
|
||||
check_file(file, &mut stats, &mut ctx, params.no_emoji, params.show_success, params.episode_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,11 +72,27 @@ fn main() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, regex: &Regex, no_emoji: bool, show_success: bool, episode_name: bool) -> () {
|
||||
fn print_result(media_icon: MediaIcon, status_icon: StatusIcon, filename: &str, reason: Option<String>, no_emoji: bool) {
|
||||
match reason {
|
||||
None => println!(
|
||||
"{} {} {}",
|
||||
get_media_icon(media_icon, no_emoji),
|
||||
get_status_icon(status_icon, no_emoji),
|
||||
filename),
|
||||
Some(r) => println!(
|
||||
"{} {} {} --- ({})",
|
||||
get_media_icon(media_icon, no_emoji),
|
||||
get_status_icon(StatusIcon::Warning, no_emoji),
|
||||
filename,
|
||||
r)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, no_emoji: bool, show_success: bool, episode_name: bool) -> () {
|
||||
let file_type = file.file_type();
|
||||
if file_type.is_file() {
|
||||
let filename = file.file_name().to_str().unwrap();
|
||||
match lint_file_name(&file, filename, regex, episode_name) {
|
||||
match lint_file_name(&file, filename, episode_name) {
|
||||
ComplianceStatus::NotMatched => {
|
||||
stats.files += 1;
|
||||
stats.error += 1;
|
||||
|
@ -82,22 +100,26 @@ fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, regex: &Rege
|
|||
println!("\n\n=== {} {}", get_status_icon(StatusIcon::Directory, no_emoji), ctx.current_folder);
|
||||
ctx.has_printed_folder = true;
|
||||
}
|
||||
println!("{} {} --- (Failed to match)", get_status_icon(StatusIcon::Failure, no_emoji), filename);
|
||||
print_result(MediaIcon::Unknown, StatusIcon::Failure, filename, Some("Failed to match".to_string()), no_emoji);
|
||||
}
|
||||
ComplianceStatus::NonCompliant(reason) => {
|
||||
ComplianceStatus::NonCompliant(detected_file_type, reason) => {
|
||||
stats.files += 1;
|
||||
if !ctx.has_printed_folder {
|
||||
println!("\n\n=== {} {}", get_status_icon(StatusIcon::Directory, no_emoji), ctx.current_folder);
|
||||
ctx.has_printed_folder = true;
|
||||
}
|
||||
stats.warning += 1;
|
||||
println!("{} {} --- ({})", get_status_icon(StatusIcon::Warning, no_emoji), filename, reason);
|
||||
print_result(MediaIcon::from(detected_file_type), StatusIcon::Warning, filename, Some(reason.to_string()), no_emoji);
|
||||
}
|
||||
ComplianceStatus::Compliant => {
|
||||
ComplianceStatus::Compliant(detected_file_type) => {
|
||||
stats.files += 1;
|
||||
stats.success += 1;
|
||||
if show_success {
|
||||
println!("{} {}", get_status_icon(StatusIcon::Success, no_emoji), filename);
|
||||
if !ctx.has_printed_folder {
|
||||
println!("\n\n=== {} {}", get_status_icon(StatusIcon::Directory, no_emoji), ctx.current_folder);
|
||||
ctx.has_printed_folder = true;
|
||||
}
|
||||
print_result(MediaIcon::from(detected_file_type), StatusIcon::Success, filename, None, no_emoji);
|
||||
}
|
||||
}
|
||||
ComplianceStatus::Ignored => {}
|
||||
|
@ -108,21 +130,25 @@ fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, regex: &Rege
|
|||
}
|
||||
}
|
||||
|
||||
const SERIES_RULES_WITHOUT_NAME: [fn(&str, &Captures) -> Option<NonCompliantReason>; 3] = [
|
||||
DashInTitle::check,
|
||||
EpisodeMarker::check,
|
||||
HasFluff::check,
|
||||
];
|
||||
enum FileType<'lt> {
|
||||
SeriesEpisode(Captures<'lt>),
|
||||
Movie(Captures<'lt>),
|
||||
Unknown
|
||||
}
|
||||
|
||||
const SERIES_RULES_WITH_NAME: [fn(&str, &Captures) -> Option<NonCompliantReason>; 5] = [
|
||||
DashInTitle::check,
|
||||
EpisodeMarker::check,
|
||||
HasFluff::check,
|
||||
HasEpisodeName::check,
|
||||
MissingNameSeparator::check
|
||||
];
|
||||
fn parse_filename(filename: &str) -> FileType {
|
||||
if let Some(captures) = SERIES_PARSER.captures(filename) {
|
||||
return FileType::SeriesEpisode(captures)
|
||||
}
|
||||
|
||||
fn lint_file_name(file: &DirEntry, filename: &str, regex: &Regex, episode_name: bool) -> ComplianceStatus {
|
||||
if let Some(captures) = MOVIE_PARSER.captures(filename) {
|
||||
return FileType::Movie(captures)
|
||||
}
|
||||
|
||||
return FileType::Unknown
|
||||
}
|
||||
|
||||
fn lint_file_name(file: &DirEntry, filename: &str, episode_name: bool) -> ComplianceStatus {
|
||||
if let Some(ext) = file.path().extension() {
|
||||
if !VIDEO_EXTENSIONS.contains(&ext.to_str().unwrap()) {
|
||||
return ComplianceStatus::Ignored;
|
||||
|
@ -131,27 +157,25 @@ fn lint_file_name(file: &DirEntry, filename: &str, regex: &Regex, episode_name:
|
|||
return ComplianceStatus::Ignored;
|
||||
}
|
||||
|
||||
let captures = regex.captures(filename);
|
||||
|
||||
if captures.is_none() {
|
||||
return ComplianceStatus::NotMatched;
|
||||
}
|
||||
|
||||
let captures = captures.unwrap();
|
||||
|
||||
match parse_filename(filename) {
|
||||
FileType::SeriesEpisode(captures) => {
|
||||
if episode_name {
|
||||
check_rules(SERIES_RULES_WITH_NAME, filename, &captures)
|
||||
check_rules(SERIES_RULES_WITH_NAME, DetectedFileType::SeriesEpisode, filename, &captures)
|
||||
} else {
|
||||
check_rules(SERIES_RULES_WITHOUT_NAME, filename, &captures)
|
||||
check_rules(SERIES_RULES_WITHOUT_NAME, DetectedFileType::SeriesEpisode, filename, &captures)
|
||||
}
|
||||
}
|
||||
FileType::Movie(captures) => check_rules(MOVIE_RULES, DetectedFileType::Movie, filename, &captures),
|
||||
FileType::Unknown => ComplianceStatus::NotMatched
|
||||
}
|
||||
}
|
||||
|
||||
fn check_rules<const N: usize>(validation_rules: [fn(&str, &Captures) -> Option<NonCompliantReason>; N], filename: &str, captures: &Captures) -> ComplianceStatus {
|
||||
fn check_rules<const N: usize>(validation_rules: [fn(&str, &Captures) -> Option<NonCompliantReason>; N], file_type: DetectedFileType, filename: &str, captures: &Captures) -> ComplianceStatus {
|
||||
for rule in validation_rules {
|
||||
if let Some(reason) = rule(filename, &captures) {
|
||||
return ComplianceStatus::NonCompliant(reason);
|
||||
return ComplianceStatus::NonCompliant(file_type, reason);
|
||||
}
|
||||
}
|
||||
|
||||
ComplianceStatus::Compliant
|
||||
ComplianceStatus::Compliant(file_type)
|
||||
}
|
|
@ -14,6 +14,24 @@ pub use has_episode_name::HasEpisodeName;
|
|||
pub use has_dash_in_title::DashInTitle;
|
||||
pub use missing_name_separator::MissingNameSeparator;
|
||||
|
||||
pub const SERIES_RULES_WITHOUT_NAME: [fn(&str, &Captures) -> Option<NonCompliantReason>; 3] = [
|
||||
DashInTitle::check,
|
||||
EpisodeMarker::check,
|
||||
HasFluff::check,
|
||||
];
|
||||
|
||||
pub const SERIES_RULES_WITH_NAME: [fn(&str, &Captures) -> Option<NonCompliantReason>; 5] = [
|
||||
DashInTitle::check,
|
||||
EpisodeMarker::check,
|
||||
HasFluff::check,
|
||||
HasEpisodeName::check,
|
||||
MissingNameSeparator::check
|
||||
];
|
||||
|
||||
pub const MOVIE_RULES: [fn(&str, &Captures) -> Option<NonCompliantReason>; 1] = [
|
||||
HasFluff::check,
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum NonCompliantReason {
|
||||
DashInTitle,
|
||||
|
@ -62,10 +80,15 @@ impl Display for EpisodeMarkerReason {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum DetectedFileType {
|
||||
SeriesEpisode,
|
||||
Movie
|
||||
}
|
||||
|
||||
pub enum ComplianceStatus {
|
||||
NonCompliant(NonCompliantReason),
|
||||
NonCompliant(DetectedFileType, NonCompliantReason),
|
||||
NotMatched,
|
||||
Compliant,
|
||||
Compliant(DetectedFileType),
|
||||
Ignored
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use crate::DetectedFileType;
|
||||
|
||||
pub enum StatusIcon {
|
||||
Failure,
|
||||
Warning,
|
||||
|
@ -13,3 +15,27 @@ pub fn get_status_icon(icon: StatusIcon, no_emoji: bool) -> String {
|
|||
StatusIcon::Directory => { if no_emoji { "".to_string() } else { "📂".to_string() } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub enum MediaIcon {
|
||||
SeriesEpisode,
|
||||
Movie,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<DetectedFileType> for MediaIcon {
|
||||
fn from(d: DetectedFileType) -> Self {
|
||||
match d {
|
||||
DetectedFileType::SeriesEpisode => Self::SeriesEpisode,
|
||||
DetectedFileType::Movie => Self::Movie
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_media_icon(icon: MediaIcon, no_emoji: bool) -> String {
|
||||
match icon {
|
||||
MediaIcon::SeriesEpisode => { if no_emoji { "S -".to_string() } else { "🎞️".to_string() } }
|
||||
MediaIcon::Movie => { if no_emoji { "M -".to_string() } else { "🎬".to_string() } }
|
||||
MediaIcon::Unknown => { if no_emoji { "U -".to_string() } else { "❓".to_string() } }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue