Collapse multipart movies into single Movie entity.
This commit is contained in:
63
src/lib.rs
63
src/lib.rs
@@ -1,5 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
@@ -34,6 +33,12 @@ const COMPACT_METADATA_FILENAME: &str = "metadata.compact.json";
|
||||
|
||||
#[derive(Clone, Deserialize, Debug, PartialEq, Serialize)]
|
||||
pub struct Resolution(usize, usize);
|
||||
impl From<(usize, usize)> for Resolution {
|
||||
fn from(res: (usize, usize)) -> Self {
|
||||
Resolution(res.0, res.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Resolution {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let v = format!("{}x{}", self.0, self.1);
|
||||
@@ -61,19 +66,22 @@ where
|
||||
T::from_str(&s).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
pub fn is_multidisc(names: &Vec<String>) -> bool {
|
||||
// TODO(wathiede): smarter version that helps with:
|
||||
// The Hudsucker Proxy:
|
||||
// 1920x1080 4.78Gi 1h 50m 45s 54151c3b9a2a4773958f848efecefc3b.mkv
|
||||
// 720x416 736.51Mi 50m 40s The Hudsucker Proxy CD1.avi
|
||||
// 720x416 736.49Mi 1h 3s The Hudsucker Proxy CD2.avi
|
||||
fn collapse_multidisc(names: &Vec<String>) -> HashMap<String, Vec<String>> {
|
||||
lazy_static! {
|
||||
static ref DIGIT: Regex = Regex::new("[0-9]").unwrap();
|
||||
}
|
||||
let mut set = HashSet::new();
|
||||
let mut set = HashMap::new();
|
||||
for name in names {
|
||||
set.insert(DIGIT.replace_all(&name, "#").to_string());
|
||||
let clean = DIGIT.replace_all(&name, "#").to_string();
|
||||
set.entry(clean)
|
||||
.or_insert(Vec::new())
|
||||
.push(name.to_string());
|
||||
}
|
||||
set
|
||||
}
|
||||
|
||||
pub fn is_multidisc(names: &Vec<String>) -> bool {
|
||||
let set = collapse_multidisc(names);
|
||||
set.len() == 1
|
||||
}
|
||||
|
||||
@@ -282,7 +290,7 @@ lazy_static! {
|
||||
static ref MOVIE_EXTS: Vec<&'static str> = vec!["avi", "m4v", "mkv", "mov", "mp4"];
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
struct Movie {
|
||||
files: Vec<(String, CompactMetadata)>,
|
||||
}
|
||||
@@ -292,22 +300,29 @@ pub struct Movies {
|
||||
movies: Vec<Movie>,
|
||||
}
|
||||
|
||||
fn movies_from_paths_compact_metadata(p_cmd: HashMap<String, CompactMetadata>) -> Movies {
|
||||
// file path
|
||||
let files_to_movies: HashMap<String, Vec<(String, CompactMetadata)>> = HashMap::new();
|
||||
// TODO(wathiede):
|
||||
// - walk over every item, use something based on is_multidisc to pack multifile movies
|
||||
// together.
|
||||
// - then walk over `files` and create a Movie for each
|
||||
// - then store those Movie structs in Movies
|
||||
|
||||
let movies = p_cmd
|
||||
fn movies_from_paths_compact_metadata(mut p_cmd: HashMap<String, CompactMetadata>) -> Movies {
|
||||
let multidisc = collapse_multidisc(&p_cmd.keys().map(|s| s.to_string()).collect());
|
||||
let movies = multidisc
|
||||
.into_iter()
|
||||
.map(|(p, cmd)| Movie {
|
||||
files: vec![(p, cmd)],
|
||||
.map(|(_hash, names)| {
|
||||
let mut files: Vec<(String, CompactMetadata)> = names
|
||||
.iter()
|
||||
.map(|name| (name.to_string(), p_cmd.remove(name).unwrap()))
|
||||
.collect();
|
||||
files.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||
Movie { files }
|
||||
})
|
||||
.collect();
|
||||
Movies { movies }
|
||||
let mut m = Movies { movies };
|
||||
m.movies.sort_by(|a, b| {
|
||||
a.files
|
||||
.first()
|
||||
.unwrap()
|
||||
.0
|
||||
.partial_cmp(&b.files.first().unwrap().0)
|
||||
.unwrap()
|
||||
});
|
||||
m
|
||||
}
|
||||
|
||||
impl MovieLibrary {
|
||||
@@ -504,7 +519,6 @@ impl MovieLibrary {
|
||||
|
||||
pub fn movies(&self) -> Result<Movies, Error> {
|
||||
let path = Path::new(&self.root).join(COMPACT_METADATA_FILENAME);
|
||||
// Open the file in read-only mode with buffer.
|
||||
let f = File::open(&path).context(format!("open {}", path.display()))?;
|
||||
let r = BufReader::new(f);
|
||||
|
||||
@@ -515,7 +529,6 @@ impl MovieLibrary {
|
||||
|
||||
pub fn videos(&self) -> Result<(HashMap<String, CompactMetadata>), Error> {
|
||||
let path = Path::new(&self.root).join(COMPACT_METADATA_FILENAME);
|
||||
// Open the file in read-only mode with buffer.
|
||||
let f = File::open(&path).context(format!("open {}", path.display()))?;
|
||||
let r = BufReader::new(f);
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
use std::error::Error;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -6,7 +11,7 @@ fn testdata_dir() -> PathBuf {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library() {
|
||||
fn test_simple_library() {
|
||||
let ml = MovieLibrary::new(testdata_dir().join("simple").to_str().unwrap());
|
||||
assert_eq!(
|
||||
ml.movies().expect("failed to build movies"),
|
||||
@@ -43,3 +48,131 @@ fn test_library() {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn build_tuple<R>(path: &str, res: R) -> (String, CompactMetadata)
|
||||
where
|
||||
R: Into<Resolution>,
|
||||
{
|
||||
let res = res.into();
|
||||
(
|
||||
path.to_string(),
|
||||
CompactMetadata {
|
||||
filename: format!("./{}", path),
|
||||
bit_rate: 1,
|
||||
duration: 1.0,
|
||||
format_name: "test_format".to_string(),
|
||||
size: 1,
|
||||
video: vec![VideoFormat {
|
||||
width: res.0,
|
||||
height: res.1,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn build_movie<R>(paths: Vec<(&str, R)>) -> Movie
|
||||
where
|
||||
R: Into<Resolution>,
|
||||
{
|
||||
Movie {
|
||||
files: paths
|
||||
.into_iter()
|
||||
.map(|(path, res)| build_tuple(path, res))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_complex_metadata() -> HashMap<String, CompactMetadata> {
|
||||
vec![
|
||||
build_tuple(
|
||||
"One Movie With Year (2019)/abcdef123456789.mkv",
|
||||
(1920, 1080),
|
||||
),
|
||||
build_tuple(
|
||||
"One Movie With Two Parts (2019)/abcdef123456789 part 1.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
build_tuple(
|
||||
"One Movie With Two Parts (2019)/abcdef123456789 part 2.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
build_tuple(
|
||||
"Two Movies With Multi Parts (2019)/abcdef123456789 part 1.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
build_tuple(
|
||||
"Two Movies With Multi Parts (2019)/abcdef123456789 part 2.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
build_tuple(
|
||||
"Two Movies With Multi Parts (2019)/somethingelse.mkv",
|
||||
(1920, 1080),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_complex_movies() -> Movies {
|
||||
let mut m = Movies {
|
||||
movies: vec![
|
||||
build_movie(vec![(
|
||||
"One Movie With Year (2019)/abcdef123456789.mkv",
|
||||
(1920, 1080),
|
||||
)]),
|
||||
build_movie(vec![
|
||||
(
|
||||
"One Movie With Two Parts (2019)/abcdef123456789 part 1.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
(
|
||||
"One Movie With Two Parts (2019)/abcdef123456789 part 2.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
]),
|
||||
build_movie(vec![
|
||||
(
|
||||
"Two Movies With Multi Parts (2019)/abcdef123456789 part 1.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
(
|
||||
"Two Movies With Multi Parts (2019)/abcdef123456789 part 2.mkv",
|
||||
(1280, 720),
|
||||
),
|
||||
]),
|
||||
build_movie(vec![(
|
||||
"Two Movies With Multi Parts (2019)/somethingelse.mkv",
|
||||
(1920, 1080),
|
||||
)]),
|
||||
],
|
||||
};
|
||||
m.movies.sort_by(|a, b| {
|
||||
a.files
|
||||
.first()
|
||||
.unwrap()
|
||||
.0
|
||||
.partial_cmp(&b.files.first().unwrap().0)
|
||||
.unwrap()
|
||||
});
|
||||
m
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_library() -> Result<(), Box<dyn Error>> {
|
||||
let metadata = build_complex_metadata();
|
||||
let want = build_complex_movies();
|
||||
let dir = tempdir()?;
|
||||
let path = dir.path().join(COMPACT_METADATA_FILENAME);
|
||||
let f = File::create(path.to_str().unwrap().to_string()).expect("failed to save");
|
||||
let f = BufWriter::new(f);
|
||||
serde_json::ser::to_writer_pretty(f, &metadata)?;
|
||||
|
||||
let ml = MovieLibrary::new(dir.path().to_str().unwrap().to_string());
|
||||
let got = ml.movies().expect("failed to build movies");
|
||||
assert_eq!(got.movies.len(), want.movies.len());
|
||||
assert_eq!(got, want);
|
||||
//assert_eq!(got, want, "Got {:#?}\nWant {:#?}", got, want);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user