Restructed compact format. Properly include video, audio and subtitles.

This commit is contained in:
Bill Thiede 2019-11-03 19:39:26 -08:00
parent 9cc6a46cf5
commit 1c7ee3cfcc

View File

@ -68,6 +68,18 @@ pub struct Format {
size: usize,
}
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct Tags(HashMap<String, String>);
impl Tags {
fn title(&self) -> Option<String> {
self.0.get("title").map(|s| s.to_string())
}
fn language(&self) -> Option<String> {
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<Tags>,
},
#[serde(rename = "audio")]
Audio {},
Audio {
codec_name: String,
codec_long_name: String,
channels: usize,
channel_layout: String,
tags: Option<Tags>,
},
#[serde(rename = "subtitle")]
Subtitle {},
Subtitle {
codec_name: String,
codec_long_name: String,
tags: Option<Tags>,
},
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
language: Option<String>,
}
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
language: Option<String>,
}
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct SubtitleFormat {
short_name: String,
long_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
language: Option<String>,
}
#[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<VideoFormat>,
audio: Vec<AudioFormat>,
subtitle: Vec<SubtitleFormat>,
}
#[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<String, CompactMetadata> = mdf
.metadata
.into_iter()
.map(|(path, Metadata { format, streams })| {
(
path,
Metadata {
format,
streams: streams
.into_iter()
.filter(|s| {
if let Stream::Video { .. } = s {
true
.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 {
false
None
}
})
.collect(),
.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,
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> {