From 1c7ee3cfcc6139bac43950a7bc2d8f94b2c16167 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 3 Nov 2019 19:39:26 -0800 Subject: [PATCH] Restructed compact format. Properly include video, audio and subtitles. --- src/lib.rs | 182 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 361710d..28ea101 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,18 @@ pub struct Format { size: usize, } +#[derive(Clone, Deserialize, Debug, Serialize)] +pub struct Tags(HashMap); + +impl Tags { + fn title(&self) -> Option { + self.0.get("title").map(|s| s.to_string()) + } + fn language(&self) -> Option { + self.0.get("language").map(|s| s.to_string()) + } +} + // TODO(wathiede): make strem an enum with the tag type stored in codec_type? #[derive(Clone, Deserialize, Debug, Serialize)] #[serde(tag = "codec_type")] @@ -86,11 +98,22 @@ pub enum Stream { duration: f32, height: usize, width: usize, + tags: Option, }, #[serde(rename = "audio")] - Audio {}, + Audio { + codec_name: String, + codec_long_name: String, + channels: usize, + channel_layout: String, + tags: Option, + }, #[serde(rename = "subtitle")] - Subtitle {}, + Subtitle { + codec_name: String, + codec_long_name: String, + tags: Option, + }, #[serde(rename = "attachment")] Attachment {}, #[serde(rename = "data")] @@ -121,6 +144,55 @@ impl Metadata { } } +#[derive(Clone, Deserialize, Debug, Serialize)] +pub struct VideoFormat { + short_name: String, + long_name: String, + height: usize, + width: usize, + #[serde(skip_serializing_if = "Option::is_none")] + title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + language: Option, +} + +#[derive(Clone, Deserialize, Debug, Serialize)] +pub struct AudioFormat { + short_name: String, + long_name: String, + channels: usize, + channel_layout: String, + #[serde(skip_serializing_if = "Option::is_none")] + title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + language: Option, +} + +#[derive(Clone, Deserialize, Debug, Serialize)] +pub struct SubtitleFormat { + short_name: String, + long_name: String, + #[serde(skip_serializing_if = "Option::is_none")] + title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + language: Option, +} + +#[derive(Clone, Deserialize, Debug, Serialize)] +pub struct CompactMetadata { + #[serde(deserialize_with = "from_str")] + bit_rate: usize, + #[serde(deserialize_with = "from_str")] + duration: f32, + filename: String, + format_name: String, + #[serde(deserialize_with = "from_str")] + size: usize, + video: Vec, + audio: Vec, + subtitle: Vec, +} + #[derive(Deserialize, Debug, Serialize)] pub struct MetadataFile { #[serde(flatten)] @@ -168,28 +240,100 @@ impl MovieLibrary { } pub fn compact_metadata(&self) -> Result<(), Error> { - let mut mdf = read_metadata_from_file(Path::new(&self.root).join("metadata.json"))?; + let mdf = read_metadata_from_file(Path::new(&self.root).join("metadata.json"))?; info!("Read metadata, {} videos found", mdf.metadata.len()); - // Remove non-video streams from metadata. - mdf.metadata = mdf + let metadata: HashMap = mdf .metadata .into_iter() - .map(|(path, Metadata { format, streams })| { + .map(|(path, Metadata { format, streams })| (path, Metadata { format, streams })) + .map(|(path, md)| { + let video = md + .streams + .iter() + .filter_map(|s| { + if let Stream::Video { + codec_name, + codec_long_name, + height, + width, + tags, + .. + } = s + { + Some(VideoFormat { + short_name: codec_name.to_string(), + long_name: codec_long_name.to_string(), + height: *height, + width: *width, + title: tags.as_ref().and_then(|t| t.title()), + language: tags.as_ref().and_then(|t| t.language()), + }) + } else { + None + } + }) + .collect(); + + let audio = md + .streams + .iter() + .filter_map(|s| { + if let Stream::Audio { + codec_name, + codec_long_name, + channels, + channel_layout, + tags, + .. + } = s + { + Some(AudioFormat { + short_name: codec_name.to_string(), + long_name: codec_long_name.to_string(), + channels: *channels, + channel_layout: channel_layout.to_string(), + title: tags.as_ref().and_then(|t| t.title()), + language: tags.as_ref().and_then(|t| t.language()), + }) + } else { + None + } + }) + .collect(); + let subtitle = md + .streams + .iter() + .filter_map(|s| { + if let Stream::Subtitle { + codec_name, + codec_long_name, + tags, + .. + } = s + { + Some(SubtitleFormat { + short_name: codec_name.to_string(), + long_name: codec_long_name.to_string(), + title: tags.as_ref().and_then(|t| t.title()), + language: tags.as_ref().and_then(|t| t.language()), + }) + } else { + None + } + }) + .collect(); ( path, - Metadata { - format, - streams: streams - .into_iter() - .filter(|s| { - if let Stream::Video { .. } = s { - true - } else { - false - } - }) - .collect(), + CompactMetadata { + bit_rate: md.format.bit_rate, + duration: md.format.duration, + filename: md.format.filename, + format_name: md.format.format_name, + size: md.format.size, + video, + audio, + subtitle, }, ) }) @@ -197,7 +341,7 @@ impl MovieLibrary { let f = File::create(Path::new(&self.root).join("metadata.compact.json"))?; let f = BufWriter::new(f); - Ok(serde_json::ser::to_writer_pretty(f, &mdf)?) + Ok(serde_json::ser::to_writer_pretty(f, &metadata)?) } pub fn update_metadata(&self) -> Result<(), Error> {