Skip to content

Commit

Permalink
Support MP4 inscriptions (ordinals#1419)
Browse files Browse the repository at this point in the history
  • Loading branch information
cryptoquick committed Feb 3, 2023
1 parent ca37cbc commit 0ded5d8
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 14 deletions.
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ log = "0.4.14"
mime = "0.3.16"
mime_guess = "2.0.4"
miniscript = "9.0.0"
mp4 = "0.12.0"
ord-bitcoincore-rpc = "0.16.5"
redb = "0.12.1"
regex = "1.6.0"
Expand Down
Binary file added examples/h264.mp4
Binary file not shown.
8 changes: 1 addition & 7 deletions src/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,7 @@ impl Inscription {
}
}

let content_type = Media::content_type_for_extension(
path
.extension()
.ok_or_else(|| anyhow!("file must have extension"))?
.to_str()
.ok_or_else(|| anyhow!("unrecognized extension"))?,
)?;
let content_type = Media::content_type_for_path(path)?;

Ok(Self {
body: Some(body),
Expand Down
60 changes: 53 additions & 7 deletions src/media.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use super::*;
use {
super::*,
mp4::{MediaType, Mp4Reader, TrackType},
std::{fs::File, io::BufReader},
};

#[derive(Debug, PartialEq, Copy, Clone)]
pub(crate) enum Media {
Expand All @@ -12,9 +16,19 @@ pub(crate) enum Media {
}

impl Media {
pub(crate) fn content_type_for_extension(extension: &str) -> Result<&'static str, Error> {
pub(crate) fn content_type_for_path(path: &Path) -> Result<&'static str, Error> {
let extension = path
.extension()
.ok_or_else(|| anyhow!("file must have extension"))?
.to_str()
.ok_or_else(|| anyhow!("unrecognized extension"))?;

let extension = extension.to_lowercase();

if extension == "mp4" {
Media::check_mp4_codec(path)?;
}

for (content_type, _, extensions) in TABLE {
if extensions.contains(&extension.as_str()) {
return Ok(content_type);
Expand All @@ -33,6 +47,27 @@ impl Media {
extensions.join(" "),
))
}

pub(crate) fn check_mp4_codec(path: &Path) -> Result<(), Error> {
let f = File::open(path)?;
let size = f.metadata()?.len();
let reader = BufReader::new(f);

let mp4 = Mp4Reader::read_header(reader, size)?;

for track in mp4.tracks().values() {
if let TrackType::Video = track.track_type()? {
let media_type = track.media_type()?;
if media_type != MediaType::H264 {
return Err(anyhow!(
"Unsupported video codec, only H.264 is supported in MP4: {media_type}"
));
}
}
}

Ok(())
}
}

impl FromStr for Media {
Expand All @@ -51,9 +86,9 @@ impl FromStr for Media {

const TABLE: &[(&str, Media, &[&str])] = &[
("application/json", Media::Text, &["json"]),
("application/yaml", Media::Text, &["yaml", "yml"]),
("application/pdf", Media::Pdf, &["pdf"]),
("application/pgp-signature", Media::Text, &["asc"]),
("application/yaml", Media::Text, &["yaml", "yml"]),
("audio/flac", Media::Audio, &["flac"]),
("audio/mpeg", Media::Audio, &["mp3"]),
("audio/wav", Media::Audio, &["wav"]),
Expand All @@ -65,6 +100,7 @@ const TABLE: &[(&str, Media, &[&str])] = &[
("image/webp", Media::Image, &["webp"]),
("text/html;charset=utf-8", Media::Iframe, &["html"]),
("text/plain;charset=utf-8", Media::Text, &["txt"]),
("video/mp4", Media::Video, &["mp4"]),
("video/webm", Media::Video, &["webm"]),
];

Expand All @@ -75,21 +111,31 @@ mod tests {
#[test]
fn for_extension() {
assert_eq!(
Media::content_type_for_extension("jpg").unwrap(),
Media::content_type_for_path(Path::new("pepe.jpg")).unwrap(),
"image/jpeg"
);
assert_eq!(
Media::content_type_for_extension("jpeg").unwrap(),
Media::content_type_for_path(Path::new("pepe.jpeg")).unwrap(),
"image/jpeg"
);
assert_eq!(
Media::content_type_for_extension("JPG").unwrap(),
Media::content_type_for_path(Path::new("pepe.JPG")).unwrap(),
"image/jpeg"
);

assert_regex_match!(
Media::content_type_for_extension("foo").unwrap_err(),
Media::content_type_for_path(Path::new("pepe.foo")).unwrap_err(),
r"unsupported file extension `\.foo`, supported extensions: apng .*"
);
}

#[test]
fn h264_in_mp4_is_allowed() {
assert!(Media::check_mp4_codec(Path::new("examples/h264.mp4")).is_ok(),);
}

#[test]
fn av1_in_mp4_is_rejected() {
assert!(Media::check_mp4_codec(Path::new("examples/av1.mp4")).is_err(),);
}
}
1 change: 1 addition & 0 deletions tests/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn preview() {
.unwrap()
.into()
})
.filter(|example| example != "examples/av1.mp4")
.collect::<Vec<String>>();

let mut args = vec![
Expand Down

0 comments on commit 0ded5d8

Please sign in to comment.