Restructed compact format. Properly include video, audio and subtitles.
This commit is contained in:
parent
9cc6a46cf5
commit
1c7ee3cfcc
180
src/lib.rs
180
src/lib.rs
@ -68,6 +68,18 @@ pub struct Format {
|
|||||||
size: usize,
|
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?
|
// TODO(wathiede): make strem an enum with the tag type stored in codec_type?
|
||||||
#[derive(Clone, Deserialize, Debug, Serialize)]
|
#[derive(Clone, Deserialize, Debug, Serialize)]
|
||||||
#[serde(tag = "codec_type")]
|
#[serde(tag = "codec_type")]
|
||||||
@ -86,11 +98,22 @@ pub enum Stream {
|
|||||||
duration: f32,
|
duration: f32,
|
||||||
height: usize,
|
height: usize,
|
||||||
width: usize,
|
width: usize,
|
||||||
|
tags: Option<Tags>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "audio")]
|
#[serde(rename = "audio")]
|
||||||
Audio {},
|
Audio {
|
||||||
|
codec_name: String,
|
||||||
|
codec_long_name: String,
|
||||||
|
channels: usize,
|
||||||
|
channel_layout: String,
|
||||||
|
tags: Option<Tags>,
|
||||||
|
},
|
||||||
#[serde(rename = "subtitle")]
|
#[serde(rename = "subtitle")]
|
||||||
Subtitle {},
|
Subtitle {
|
||||||
|
codec_name: String,
|
||||||
|
codec_long_name: String,
|
||||||
|
tags: Option<Tags>,
|
||||||
|
},
|
||||||
#[serde(rename = "attachment")]
|
#[serde(rename = "attachment")]
|
||||||
Attachment {},
|
Attachment {},
|
||||||
#[serde(rename = "data")]
|
#[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)]
|
#[derive(Deserialize, Debug, Serialize)]
|
||||||
pub struct MetadataFile {
|
pub struct MetadataFile {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@ -168,28 +240,100 @@ impl MovieLibrary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn compact_metadata(&self) -> Result<(), Error> {
|
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());
|
info!("Read metadata, {} videos found", mdf.metadata.len());
|
||||||
|
|
||||||
// Remove non-video streams from metadata.
|
let metadata: HashMap<String, CompactMetadata> = mdf
|
||||||
mdf.metadata = mdf
|
|
||||||
.metadata
|
.metadata
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(path, Metadata { format, streams })| {
|
.map(|(path, Metadata { format, streams })| (path, Metadata { format, streams }))
|
||||||
(
|
.map(|(path, md)| {
|
||||||
path,
|
let video = md
|
||||||
Metadata {
|
.streams
|
||||||
format,
|
.iter()
|
||||||
streams: streams
|
.filter_map(|s| {
|
||||||
.into_iter()
|
if let Stream::Video {
|
||||||
.filter(|s| {
|
codec_name,
|
||||||
if let Stream::Video { .. } = s {
|
codec_long_name,
|
||||||
true
|
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 {
|
} 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 = File::create(Path::new(&self.root).join("metadata.compact.json"))?;
|
||||||
let f = BufWriter::new(f);
|
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> {
|
pub fn update_metadata(&self) -> Result<(), Error> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user