Collapse multipart movies into single Movie entity.

This commit is contained in:
2019-11-24 11:01:46 -08:00
parent 04585e8d24
commit 8eafec7fd2
4 changed files with 311 additions and 26 deletions

View File

@@ -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);

View File

@@ -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(())
}