use std::collections::HashMap; use std::error::Error; use std::path::Path; use std::path::PathBuf; use std::time::Duration; use human_format::Formatter; use human_format::Scales; use humantime; use log::info; use regex::Regex; use structopt::StructOpt; use superdeduper::CompactMetadata; use superdeduper::MovieLibrary; const MOVIE_DIR: &str = "/home/wathiede/Movies"; const TO_BE_REMOVED_DIR: &str = "/storage/media/to-be-deleted/"; fn clean_path_parent>(path: P) -> PathBuf { let path = path.as_ref(); let path = path.parent().unwrap(); let mut path = path.to_str().unwrap(); if path.ends_with(')') { path = &path[..path.len() - 7]; } PathBuf::from(path) } fn print_video_groups(video_groups: &HashMap>) { let mut names = video_groups.keys().collect::>(); names.sort(); let mut fmtr = Formatter::new(); fmtr.with_separator(""); fmtr.with_scales(Scales::Binary()); for name in names { let paths = &video_groups[name]; if paths.len() < 2 { continue; } let mut file: Vec<_> = video_groups[name].iter().collect(); file.sort_by(|(n1, _), (n2, _)| n1.partial_cmp(n2).unwrap()); println!("{}:", name.display()); for (p, md) in file { println!( " {:>9} {:>9} {} {}", md.largest_dimension().unwrap(), fmtr.format(md.size as f64), humantime::Duration::from(Duration::from_secs(md.duration as u64)), &p[p.rfind("/").unwrap() + 1..] ); } } } fn print_videos(videos: &HashMap, filter: Option<&Regex>) { let mut names = videos.keys().collect::>(); names.sort(); let mut fmtr = Formatter::new(); fmtr.with_separator(""); fmtr.with_scales(Scales::Binary()); for name in names { if let Some(re) = filter { if !re.is_match(name) { continue; } } let md = &videos[name]; info!( "{:>9} {:>8} {} {}", md.largest_dimension().unwrap(), fmtr.format(md.size as f64), humantime::Duration::from(Duration::from_secs(md.duration as u64)), &name[MOVIE_DIR.len() + 1..] ); println!("mv '{}' '{}'", name, TO_BE_REMOVED_DIR); } } #[derive(StructOpt)] enum Command { #[structopt(name = "samples", about = "Print video files deemed to be samples")] Samples, #[structopt(name = "groups", about = "Print videos grouped by root name")] Groups, #[structopt( name = "compact-metadata", about = "Read full metadata file and write compact file." )] CompactMetadata, #[structopt(name = "update-metadata", about = "Write full metadata files")] UpdateMetadata, #[structopt( name = "update-compact-metadata", about = "Write full metadata files and update compact file on changes" )] UpdateAndCompactMetadata, } #[derive(StructOpt)] #[structopt( name = "superdeduper", about = "Tool for pruning extra videos in collection" )] struct SuperDeduper { #[structopt( short = "v", help = "Sets the level of verbosity", parse(from_occurrences) )] verbose: usize, #[structopt(long = "module", help = "Additional log target to enable")] module: Option, #[structopt(subcommand)] // Note that we mark a field as a subcommand cmd: Command, } fn main() -> Result<(), Box> { let app = SuperDeduper::from_args(); let mut modules = vec![module_path!().to_string()]; if let Some(module) = app.module { modules.push(module); } stderrlog::new() .verbosity(app.verbose) .timestamp(stderrlog::Timestamp::Millisecond) .modules(modules) .init() .unwrap(); match app.cmd { Command::Samples => { let lib = MovieLibrary::new(MOVIE_DIR); let videos = lib.videos(false)?; let samples_re = Regex::new(r"(?i).*sample.*").unwrap(); print_videos(&videos, Some(&samples_re)); } Command::Groups => { let lib = MovieLibrary::new(MOVIE_DIR); let videos = lib.videos(false)?; let mut video_groups: HashMap> = HashMap::new(); for (name, md) in videos.into_iter() { let clean_name = clean_path_parent(&name); let paths = video_groups.entry(clean_name).or_insert(Vec::new()); paths.push((name.to_string(), md)); } print_video_groups(&video_groups); } Command::CompactMetadata => { let lib = MovieLibrary::new(MOVIE_DIR); lib.compact_metadata()?; } Command::UpdateMetadata => { let lib = MovieLibrary::new(MOVIE_DIR); lib.update_metadata()?; } Command::UpdateAndCompactMetadata => { let lib = MovieLibrary::new(MOVIE_DIR); let new_videos = lib.update_metadata()?; if !new_videos.is_empty() { info!( "{} new videos added, recompacting metadata", new_videos.len() ); lib.compact_metadata()?; } } } Ok(()) }