Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ impl Item<Audio> {
}
}

impl Item<Video> {
pub(crate) fn populate(&mut self, root: &Utf8Path) -> Result {
self.title = self.content.populate(root)?;
Ok(())
}
}

impl<T: Content> MediaItem for Item<T> {
fn info(&self, url: String) -> Info {
self
Expand Down Expand Up @@ -142,7 +149,7 @@ mod tests {
}

#[test]
fn populate() {
fn populate_audio() {
let (_tempdir, root) = tempdir();

std::fs::write(
Expand All @@ -164,4 +171,19 @@ mod tests {
item.populate(&root).unwrap();
assert_eq!(item.title, Some("bar".parse().unwrap()));
}

#[test]
fn populate_video() {
let (_tempdir, root) = tempdir();

std::fs::write(
root.join("foo.mp4"),
Mp4Builder::new().video_track(2, 1).name("bar").build(),
)
.unwrap();

let mut item = "foo.mp4".parse::<Item<Video>>().unwrap();
item.populate(&root).unwrap();
assert_eq!(item.title, Some("bar".parse().unwrap()));
}
}
2 changes: 1 addition & 1 deletion src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ impl Metadata {
}
Media::Video { items } => {
for video in items {
video.content.populate(root)?;
video.populate(root)?;
bar.inc(1);
}
}
Expand Down
34 changes: 33 additions & 1 deletion src/mp4_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub struct Mp4Builder {
duration: u32,
frame_count: u32,
matrix: [i32; 9],
name: Option<Vec<u8>>,
sample_size: u32,
sample_sizes: Vec<u32>,
sps: Vec<u8>,
Expand Down Expand Up @@ -66,7 +67,31 @@ impl Mp4Builder {
mvhd.extend_from_slice(&0x0001_0000u32.to_be_bytes());
mvhd.extend_from_slice(&[0; 76]);

let moov = [Self::atom(*b"mvhd", &mvhd), self.tracks.concat()].concat();
let udta = self
.name
.as_ref()
.map(|name| {
let mut hdlr = vec![0; 8];
hdlr.extend_from_slice(b"mdir");
hdlr.extend_from_slice(&[0; 12]);
hdlr.push(0);

let mut data = 1u32.to_be_bytes().to_vec();
data.extend_from_slice(&[0; 4]);
data.extend_from_slice(name);

let ilst = Self::atom(
*b"ilst",
&Self::atom(*b"\xa9nam", &Self::atom(*b"data", &data)),
);

let meta = [vec![0; 4], Self::atom(*b"hdlr", &hdlr), ilst].concat();

Self::atom(*b"udta", &Self::atom(*b"meta", &meta))
})
.unwrap_or_default();

let moov = [Self::atom(*b"mvhd", &mvhd), self.tracks.concat(), udta].concat();

[Self::atom(*b"ftyp", &ftyp), Self::atom(*b"moov", &moov)].concat()
}
Expand All @@ -89,12 +114,19 @@ impl Mp4Builder {
self
}

#[must_use]
pub fn name(mut self, name: impl AsRef<[u8]>) -> Self {
self.name = Some(name.as_ref().into());
self
}

pub fn new() -> Self {
Self {
avcc_profile: 0,
duration: 0,
frame_count: 0,
matrix: [0x0001_0000, 0, 0, 0, 0x0001_0000, 0, 0, 0, 0x4000_0000],
name: None,
sample_size: 1,
sample_sizes: Vec::new(),
sps: Vec::new(),
Expand Down
65 changes: 61 additions & 4 deletions src/mp4_decoder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::*;
use {
super::*,
re_mp4::{MetaBox, MetadataKey, Mp4, Mp4aBox, StsdBoxContent, TkhdBox},
};

pub(crate) struct Mp4Decoder;

Expand Down Expand Up @@ -67,8 +70,6 @@ impl Mp4Decoder {
}

fn metadata<T: Read + Seek>(reader: T, size: u64) -> Result<VideoMetadata, VideoError> {
use re_mp4::{Mp4, Mp4aBox, StsdBoxContent, TkhdBox};

fn mp4a_codec(mp4a: &Mp4aBox) -> Option<Codec> {
match mp4a
.esds
Expand Down Expand Up @@ -245,7 +246,13 @@ impl Mp4Decoder {
tracks.push(track);
}

Ok(VideoMetadata { duration, tracks })
let title = Self::title(&mp4)?;

Ok(VideoMetadata {
duration,
title,
tracks,
})
}

pub(crate) fn read(path: &Utf8Path) -> Result<VideoMetadata> {
Expand All @@ -255,6 +262,32 @@ impl Mp4Decoder {

Self::metadata(file, size).context(error::Video { path })
}

fn title(mp4: &Mp4) -> Result<Option<Text>, VideoError> {
let Some(udta) = &mp4.moov.udta else {
return Ok(None);
};

let Some(MetaBox::Mdir { ilst: Some(ilst) }) = &udta.meta else {
return Ok(None);
};

let Some(item) = ilst.items.get(&MetadataKey::Title) else {
return Ok(None);
};

let tag = "漏nam";

let title = str::from_utf8(&item.data.data).context(video_error::TagUtf8 { tag })?;

ensure!(!title.is_empty(), video_error::TagEmpty { tag });

Ok(Some(
title
.parse::<Text>()
.context(video_error::TagInvalid { tag })?,
))
}
}

#[cfg(test)]
Expand Down Expand Up @@ -322,6 +355,7 @@ mod tests {
case(Mp4Builder::new().video_track(2, 1).audio_track(0x40)).unwrap(),
VideoMetadata {
duration: 0,
title: None,
tracks: vec![
Track {
codec: Codec::H264,
Expand Down Expand Up @@ -353,6 +387,7 @@ mod tests {
case(Mp4Builder::new().video_track(2, 1)).unwrap(),
VideoMetadata {
duration: 0,
title: None,
tracks: vec![Track {
codec: Codec::H264,
info: TrackInfo::Video {
Expand Down Expand Up @@ -570,6 +605,28 @@ mod tests {
"track 1 has unsupported audio codec `unknown`",
);

assert_eq!(
case(Mp4Builder::new().video_track(2, 1).name("foo"))
.unwrap()
.title,
Some("foo".parse().unwrap()),
);

error(
Mp4Builder::new().video_track(2, 1).name(b""),
"empty `漏nam` tag",
);

error(
Mp4Builder::new().video_track(2, 1).name(b"\xff"),
"`漏nam` tag is not valid UTF-8",
);

error(
Mp4Builder::new().video_track(2, 1).name("\0"),
"invalid `漏nam` tag",
);

assert_eq!(
Mp4Decoder::metadata(io::Cursor::new(b"foo"), 3)
.unwrap_err()
Expand Down
4 changes: 2 additions & 2 deletions src/templates/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ mod tests {
],
ty: VideoType::Mp4,
},
title: None,
title: Some("bar".parse().unwrap()),
}],
}),
..default()
Expand Down Expand Up @@ -712,7 +712,7 @@ mod tests {
</dl>
<ol>
<li>
<a href=/package/{fingerprint}/item/1>foo.mp4</a>
<a href=/package/{fingerprint}/item/1>bar</a>
<time datetime=PT3M45S>3:45</time>
</li>
</ol>
Expand Down
31 changes: 30 additions & 1 deletion src/templates/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ pub(crate) struct VideoHtml {
pub(crate) video: usize,
}

impl VideoHtml {
fn video(&self) -> &Item<Video> {
let Media::Video { items } = self.metadata.media.as_ref().unwrap() else {
unreachable!();
};

&items[self.video]
}
}

impl Page for VideoHtml {
fn open_graph_image(&self) -> Option<OpenGraphImage> {
OpenGraphImage::artwork(&self.metadata, self.fingerprint)
Expand All @@ -17,7 +27,7 @@ impl Page for VideoHtml {
}

fn title(&self) -> String {
format!("video {} 路 filepack", self.video)
format!("{} 路 filepack", self.video().display_title())
}
}

Expand Down Expand Up @@ -52,4 +62,23 @@ mod tests {

assert_eq!(html.open_graph_image(), None);
}

#[test]
fn title() {
let html = VideoHtml {
fingerprint: test::FINGERPRINT.parse().unwrap(),
metadata: Metadata {
media: Some(Media::Video {
items: vec![Item {
content: "foo.mp4".parse().unwrap(),
title: Some("bar".parse().unwrap()),
}],
}),
..default()
},
video: 0,
};

assert_eq!(Page::title(&html), "bar 路 filepack");
}
}
12 changes: 8 additions & 4 deletions src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,22 @@ impl Video {
formats
}

pub(crate) fn populate(&mut self, root: &Utf8Path) -> Result {
pub(crate) fn populate(&mut self, root: &Utf8Path) -> Result<Option<Text>> {
let path = root.join(&self.path);

let VideoMetadata { duration, tracks } = match self.ty {
let VideoMetadata {
duration,
title,
tracks,
} = match self.ty {
VideoType::Mp4 => Mp4Decoder::read(&path)?,
VideoType::Webm => WebmDecoder::read(&path)?,
};

self.duration = duration;
self.tracks = tracks;

Ok(())
Ok(title)
}

pub(crate) fn resource_type(&self) -> ResourceType {
Expand Down Expand Up @@ -165,7 +169,7 @@ mod tests {

let mut video = "foo.mp4".parse::<Video>().unwrap();

video.populate(&root).map(|()| video)
video.populate(&root).map(|_| video)
}

assert_eq!(
Expand Down
12 changes: 12 additions & 0 deletions src/video_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ pub enum VideoError {
SpsInvalid,
#[snafu(display("missing SPS"))]
SpsMissing,
#[snafu(display("empty `{tag}` tag"))]
TagEmpty { tag: &'static str },
#[snafu(display("invalid `{tag}` tag"))]
TagInvalid {
source: TextError,
tag: &'static str,
},
#[snafu(display("`{tag}` tag is not valid UTF-8"))]
TagUtf8 {
source: Utf8Error,
tag: &'static str,
},
#[snafu(display("zero timescale"))]
TimescaleZero,
#[snafu(display("unsupported timestamp scale {timestamp_scale}"))]
Expand Down
1 change: 1 addition & 0 deletions src/video_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ use super::*;
#[derive(Debug, PartialEq)]
pub(crate) struct VideoMetadata {
pub(crate) duration: u64,
pub(crate) title: Option<Text>,
pub(crate) tracks: Vec<Track>,
}
13 changes: 13 additions & 0 deletions src/webm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub(crate) struct WebmBuilder {
doc_type: String,
duration: Option<f64>,
timestamp_scale: Option<u64>,
title: Option<String>,
tracks: Vec<Vec<u8>>,
}

Expand Down Expand Up @@ -41,6 +42,11 @@ impl WebmBuilder {
.duration
.map(|duration| Self::float(&[0x44, 0x89], duration))
.unwrap_or_default(),
self
.title
.as_deref()
.map(|title| Self::string(&[0x7B, 0xA9], title))
.unwrap_or_default(),
Self::string(&[0x4D, 0x80], "foo"),
Self::string(&[0x57, 0x41], "bar"),
]
Expand Down Expand Up @@ -102,6 +108,7 @@ impl WebmBuilder {
doc_type: "webm".into(),
duration: Some(0.0),
timestamp_scale: None,
title: None,
tracks: Vec::new(),
}
}
Expand All @@ -122,6 +129,12 @@ impl WebmBuilder {
self
}

#[must_use]
pub(crate) fn title(mut self, title: &str) -> Self {
self.title = Some(title.into());
self
}

#[must_use]
pub(crate) fn track(mut self, ty: u64, codec_id: &str, settings: &[u8]) -> Self {
let number = u64::try_from(self.tracks.len() + 1).unwrap();
Expand Down
Loading