From 05e20124e05fb2897a0496270d2665085ed02394 Mon Sep 17 00:00:00 2001 From: Alf Date: Fri, 4 Sep 2020 23:09:33 -0700 Subject: [PATCH 1/5] Add support for parsing subtitle tracks (tx3g box). --- examples/mp4info.rs | 28 +++++++++++++- src/mp4box/mod.rs | 6 ++- src/mp4box/mp4a.rs | 12 +----- src/mp4box/stsd.rs | 12 +++++- src/mp4box/tx3g.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++ src/track.rs | 4 ++ src/types.rs | 12 ++++++ 7 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 src/mp4box/tx3g.rs diff --git a/examples/mp4info.rs b/examples/mp4info.rs index 1d98c19..69bb3d6 100644 --- a/examples/mp4info.rs +++ b/examples/mp4info.rs @@ -48,6 +48,7 @@ fn info>(filename: &P) -> Result<()> { let media_info = match track.track_type()? { TrackType::Video => video_info(track)?, TrackType::Audio => audio_info(track)?, + TrackType::Subtitle => subtitle_info(track)?, }; println!( " Track: #{}({}) {}: {}", @@ -88,13 +89,24 @@ fn video_info(track: &Mp4Track) -> Result { fn audio_info(track: &Mp4Track) -> Result { if let Some(ref mp4a) = track.trak.mdia.minf.stbl.stsd.mp4a { if mp4a.esds.is_some() { + + let profile = match track.audio_profile() { + Ok(val) => val.to_string(), + _ => "-".to_string(), + }; + + let channel_config = match track.channel_config() { + Ok(val) => val.to_string(), + _ => "-".to_string(), + }; + Ok(format!( "{} ({}) ({:?}), {} Hz, {}, {} kb/s", track.media_type()?, - track.audio_profile()?, + profile, track.box_type()?, track.sample_freq_index()?.freq(), - track.channel_config()?, + channel_config, track.bitrate() / 1000 )) } else { @@ -110,6 +122,18 @@ fn audio_info(track: &Mp4Track) -> Result { } } +fn subtitle_info(track: &Mp4Track) -> Result { + if track.trak.mdia.minf.stbl.stsd.tx3g.is_some() { + Ok(format!( + "{} ({:?})", + track.media_type()?, + track.box_type()?, + )) + } else { + Ok("subtitle test".to_string()) + } +} + fn creation_time(creation_time: u64) -> u64 { // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01) if creation_time >= 2082844800 { diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index b0053cf..56fb6ab 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -5,12 +5,12 @@ use std::io::{Read, Seek, SeekFrom, Write}; use crate::*; pub(crate) mod avc1; -pub(crate) mod hev1; pub(crate) mod co64; pub(crate) mod ctts; pub(crate) mod edts; pub(crate) mod elst; pub(crate) mod ftyp; +pub(crate) mod hev1; pub(crate) mod hdlr; pub(crate) mod mdhd; pub(crate) mod mdia; @@ -32,6 +32,7 @@ pub(crate) mod tkhd; pub(crate) mod tfhd; pub(crate) mod trak; pub(crate) mod traf; +pub(crate) mod tx3g; pub(crate) mod vmhd; pub use ftyp::FtypBox; @@ -106,7 +107,8 @@ boxtype! { Hev1Box => 0x68657631, HvcCBox => 0x68766343, Mp4aBox => 0x6d703461, - EsdsBox => 0x65736473 + EsdsBox => 0x65736473, + Tx3gBox => 0x74783367 } pub trait Mp4Box: Sized { diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 8a44cff..35f219a 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -304,13 +304,9 @@ impl ReadDesc<&mut R> for ESDescriptor { current = reader.seek(SeekFrom::Current(0))?; } - if dec_config.is_none() { - return Err(Error::InvalidData("DecoderConfigDescriptor not found")); - } - Ok(ESDescriptor { es_id, - dec_config: dec_config.unwrap(), + dec_config: dec_config.unwrap_or(DecoderConfigDescriptor::default()), sl_config: sl_config.unwrap_or(SLConfigDescriptor::default()), }) } @@ -397,10 +393,6 @@ impl ReadDesc<&mut R> for DecoderConfigDescriptor { current = reader.seek(SeekFrom::Current(0))?; } - if dec_specific.is_none() { - return Err(Error::InvalidData("DecoderSpecificDescriptor not found")); - } - Ok(DecoderConfigDescriptor { object_type_indication, stream_type, @@ -408,7 +400,7 @@ impl ReadDesc<&mut R> for DecoderConfigDescriptor { buffer_size_db, max_bitrate, avg_bitrate, - dec_specific: dec_specific.unwrap(), + dec_specific: dec_specific.unwrap_or(DecoderSpecificDescriptor::default()), }) } } diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index 2886965..715b810 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -2,7 +2,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::io::{Read, Seek, Write}; use crate::mp4box::*; -use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox}; +use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox}; #[derive(Debug, Clone, PartialEq, Default)] pub struct StsdBox { @@ -11,6 +11,7 @@ pub struct StsdBox { pub avc1: Option, pub hev1: Option, pub mp4a: Option, + pub tx3g: Option, } impl StsdBox { @@ -50,6 +51,7 @@ impl ReadBox<&mut R> for StsdBox { let mut avc1 = None; let mut hev1 = None; let mut mp4a = None; + let mut tx3g = None; // Get box header. let header = BoxHeader::read(reader)?; @@ -65,6 +67,9 @@ impl ReadBox<&mut R> for StsdBox { BoxType::Mp4aBox => { mp4a = Some(Mp4aBox::read_box(reader, s)?); } + BoxType::Tx3gBox => { + tx3g = Some(Tx3gBox::read_box(reader, s)?); + } _ => {} } @@ -76,6 +81,7 @@ impl ReadBox<&mut R> for StsdBox { avc1, hev1, mp4a, + tx3g, }) } } @@ -91,8 +97,12 @@ impl WriteBox<&mut W> for StsdBox { if let Some(ref avc1) = self.avc1 { avc1.write_box(writer)?; + } else if let Some(ref hev1) = self.hev1 { + hev1.write_box(writer)?; } else if let Some(ref mp4a) = self.mp4a { mp4a.write_box(writer)?; + } else if let Some(ref tx3g) = self.tx3g { + tx3g.write_box(writer)?; } Ok(size) diff --git a/src/mp4box/tx3g.rs b/src/mp4box/tx3g.rs new file mode 100644 index 0000000..9f4bc30 --- /dev/null +++ b/src/mp4box/tx3g.rs @@ -0,0 +1,91 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct Tx3gBox { + pub data_reference_index: u16, +} + +impl Default for Tx3gBox { + fn default() -> Self { + Tx3gBox { + data_reference_index: 0, + } + } +} + +impl Tx3gBox { + pub fn get_type(&self) -> BoxType { + BoxType::Tx3gBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + 8 + } +} + +impl Mp4Box for Tx3gBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } +} + +impl ReadBox<&mut R> for Tx3gBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + reader.read_u32::()?; // reserved + reader.read_u16::()?; // reserved + let data_reference_index = reader.read_u16::()?; + + skip_bytes_to(reader, start + size)?; + + Ok(Tx3gBox { + data_reference_index, + }) + } +} + +impl WriteBox<&mut W> for Tx3gBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + writer.write_u32::(0)?; // reserved + writer.write_u16::(0)?; // reserved + writer.write_u16::(self.data_reference_index)?; + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_tx3g() { + let src_box = Tx3gBox { + data_reference_index: 1, + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::Tx3gBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Tx3gBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/track.rs b/src/track.rs index 7800c46..1151f9e 100644 --- a/src/track.rs +++ b/src/track.rs @@ -98,6 +98,8 @@ impl Mp4Track { Ok(MediaType::H265) } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { Ok(MediaType::AAC) + } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { + Ok(MediaType::TTXT) } else { Err(Error::InvalidData("unsupported media type")) } @@ -110,6 +112,8 @@ impl Mp4Track { Ok(FourCC::from(BoxType::Hev1Box)) } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { Ok(FourCC::from(BoxType::Mp4aBox)) + } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { + Ok(FourCC::from(BoxType::Tx3gBox)) } else { Err(Error::InvalidData("unsupported sample entry box")) } diff --git a/src/types.rs b/src/types.rs index 94810d0..e416257 100644 --- a/src/types.rs +++ b/src/types.rs @@ -165,14 +165,17 @@ impl fmt::Display for FourCC { const DISPLAY_TYPE_VIDEO: &str = "Video"; const DISPLAY_TYPE_AUDIO: &str = "Audio"; +const DISPLAY_TYPE_SUBTITLE: &str = "Subtitle"; const HANDLER_TYPE_VIDEO: &str = "vide"; const HANDLER_TYPE_AUDIO: &str = "soun"; +const HANDLER_TYPE_SUBTITLE: &str = "sbtl"; #[derive(Debug, Clone, Copy, PartialEq)] pub enum TrackType { Video, Audio, + Subtitle, } impl fmt::Display for TrackType { @@ -180,6 +183,7 @@ impl fmt::Display for TrackType { let s = match self { TrackType::Video => DISPLAY_TYPE_VIDEO, TrackType::Audio => DISPLAY_TYPE_AUDIO, + TrackType::Subtitle => DISPLAY_TYPE_SUBTITLE, }; write!(f, "{}", s) } @@ -191,6 +195,7 @@ impl TryFrom<&str> for TrackType { match handler { HANDLER_TYPE_VIDEO => Ok(TrackType::Video), HANDLER_TYPE_AUDIO => Ok(TrackType::Audio), + HANDLER_TYPE_SUBTITLE => Ok(TrackType::Subtitle), _ => Err(Error::InvalidData("unsupported handler type")), } } @@ -201,6 +206,7 @@ impl Into<&str> for TrackType { match self { TrackType::Video => HANDLER_TYPE_VIDEO, TrackType::Audio => HANDLER_TYPE_AUDIO, + TrackType::Subtitle => HANDLER_TYPE_SUBTITLE, } } } @@ -210,6 +216,7 @@ impl Into<&str> for &TrackType { match self { TrackType::Video => HANDLER_TYPE_VIDEO, TrackType::Audio => HANDLER_TYPE_AUDIO, + TrackType::Subtitle => HANDLER_TYPE_SUBTITLE, } } } @@ -231,12 +238,14 @@ impl Into for TrackType { const MEDIA_TYPE_H264: &str = "h264"; const MEDIA_TYPE_H265: &str = "h265"; const MEDIA_TYPE_AAC: &str = "aac"; +const MEDIA_TYPE_TTXT: &str = "ttxt"; #[derive(Debug, Clone, Copy, PartialEq)] pub enum MediaType { H264, H265, AAC, + TTXT, } impl fmt::Display for MediaType { @@ -253,6 +262,7 @@ impl TryFrom<&str> for MediaType { MEDIA_TYPE_H264 => Ok(MediaType::H264), MEDIA_TYPE_H265 => Ok(MediaType::H265), MEDIA_TYPE_AAC => Ok(MediaType::AAC), + MEDIA_TYPE_TTXT => Ok(MediaType::TTXT), _ => Err(Error::InvalidData("unsupported media type")), } } @@ -264,6 +274,7 @@ impl Into<&str> for MediaType { MediaType::H264 => MEDIA_TYPE_H264, MediaType::H265 => MEDIA_TYPE_H265, MediaType::AAC => MEDIA_TYPE_AAC, + MediaType::TTXT => MEDIA_TYPE_TTXT, } } } @@ -274,6 +285,7 @@ impl Into<&str> for &MediaType { MediaType::H264 => MEDIA_TYPE_H264, MediaType::H265 => MEDIA_TYPE_H265, MediaType::AAC => MEDIA_TYPE_AAC, + MediaType::TTXT => MEDIA_TYPE_TTXT, } } } From 421d9e76064adb3918cbf7f9371741ae56925b7c Mon Sep 17 00:00:00 2001 From: Alf Date: Sat, 5 Sep 2020 00:57:15 -0700 Subject: [PATCH 2/5] More tx3g box parsing/writing and update tests. --- examples/mp4copy.rs | 12 ++++++- src/mp4box/stsd.rs | 4 +++ src/mp4box/tx3g.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++- src/track.rs | 13 +++++++ src/types.rs | 4 +++ 5 files changed, 118 insertions(+), 2 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 9dacd8a..e90ea6a 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -4,7 +4,16 @@ use std::io::prelude::*; use std::io::{self, BufReader, BufWriter}; use std::path::Path; -use mp4::{AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig}; +use mp4::{ + AacConfig, + AvcConfig, + HevcConfig, + TtxtConfig, + MediaConfig, + MediaType, + Mp4Config, + Result, + TrackConfig}; fn main() { let args: Vec = env::args().collect(); @@ -58,6 +67,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { freq_index: track.sample_freq_index()?, chan_conf: track.channel_config()?, }), + MediaType::TTXT => MediaConfig::TtxtConfig(TtxtConfig {}), }; let track_conf = TrackConfig { diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index 715b810..bb27a3d 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -23,8 +23,12 @@ impl StsdBox { let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4; if let Some(ref avc1) = self.avc1 { size += avc1.box_size(); + } else if let Some(ref hev1) = self.hev1 { + size += hev1.box_size(); } else if let Some(ref mp4a) = self.mp4a { size += mp4a.box_size(); + } else if let Some(ref tx3g) = self.tx3g { + size += tx3g.box_size(); } size } diff --git a/src/mp4box/tx3g.rs b/src/mp4box/tx3g.rs index 9f4bc30..6bf4e5d 100644 --- a/src/mp4box/tx3g.rs +++ b/src/mp4box/tx3g.rs @@ -6,12 +6,37 @@ use crate::mp4box::*; #[derive(Debug, Clone, PartialEq)] pub struct Tx3gBox { pub data_reference_index: u16, + pub display_flags: u32, + pub horizontal_justification: i8, + pub vertical_justification: i8, + pub bg_color_rgba: RgbaColor, + pub box_record: [i16; 4], + pub style_record: [u8; 12], +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct RgbaColor { + pub red: u8, + pub green: u8, + pub blue: u8, + pub alpha: u8 } impl Default for Tx3gBox { fn default() -> Self { Tx3gBox { data_reference_index: 0, + display_flags: 0, + horizontal_justification: 1, + vertical_justification: -1, + bg_color_rgba: RgbaColor{ + red: 0, + green: 0, + blue: 0, + alpha: 255, + }, + box_record: [0, 0, 0, 0], + style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255], } } } @@ -22,7 +47,7 @@ impl Tx3gBox { } pub fn get_size(&self) -> u64 { - HEADER_SIZE + 8 + HEADER_SIZE + 6 + 32 } } @@ -44,10 +69,46 @@ impl ReadBox<&mut R> for Tx3gBox { reader.read_u16::()?; // reserved let data_reference_index = reader.read_u16::()?; + let display_flags = reader.read_u32::()?; + let horizontal_justification = reader.read_i8()?; + let vertical_justification = reader.read_i8()?; + let bg_color_rgba = RgbaColor { + red: reader.read_u8()?, + green: reader.read_u8()?, + blue: reader.read_u8()?, + alpha: reader.read_u8()?, + }; + let box_record: [i16; 4] = [ + reader.read_i16::()?, + reader.read_i16::()?, + reader.read_i16::()?, + reader.read_i16::()?, + ]; + let style_record: [u8; 12] = [ + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + reader.read_u8()?, + ]; + skip_bytes_to(reader, start + size)?; Ok(Tx3gBox { data_reference_index, + display_flags, + horizontal_justification, + vertical_justification, + bg_color_rgba, + box_record, + style_record, }) } } @@ -60,6 +121,19 @@ impl WriteBox<&mut W> for Tx3gBox { writer.write_u32::(0)?; // reserved writer.write_u16::(0)?; // reserved writer.write_u16::(self.data_reference_index)?; + writer.write_u32::(self.display_flags)?; + writer.write_i8(self.horizontal_justification)?; + writer.write_i8(self.vertical_justification)?; + writer.write_u8(self.bg_color_rgba.red)?; + writer.write_u8(self.bg_color_rgba.green)?; + writer.write_u8(self.bg_color_rgba.blue)?; + writer.write_u8(self.bg_color_rgba.alpha)?; + for n in 0..4 { + writer.write_i16::(self.box_record[n])?; + } + for n in 0..12 { + writer.write_u8(self.style_record[n])?; + } Ok(size) } @@ -75,6 +149,17 @@ mod tests { fn test_tx3g() { let src_box = Tx3gBox { data_reference_index: 1, + display_flags: 0, + horizontal_justification: 1, + vertical_justification: -1, + bg_color_rgba: RgbaColor{ + red: 0, + green: 0, + blue: 0, + alpha: 255, + }, + box_record: [0, 0, 0, 0], + style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255], }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/track.rs b/src/track.rs index 1151f9e..8e65def 100644 --- a/src/track.rs +++ b/src/track.rs @@ -35,6 +35,7 @@ impl From for TrackConfig { MediaConfig::AvcConfig(avc_conf) => Self::from(avc_conf), MediaConfig::HevcConfig(hevc_conf) => Self::from(hevc_conf), MediaConfig::AacConfig(aac_conf) => Self::from(aac_conf), + MediaConfig::TtxtConfig(ttxt_conf) => Self::from(ttxt_conf), } } } @@ -72,6 +73,17 @@ impl From for TrackConfig { } } +impl From for TrackConfig { + fn from(txtt_conf: TtxtConfig) -> Self { + Self { + track_type: TrackType::Subtitle, + timescale: 1000, // XXX + language: String::from("und"), // XXX + media_conf: MediaConfig::TtxtConfig(txtt_conf), + } + } +} + #[derive(Debug)] pub struct Mp4Track { pub trak: TrakBox, @@ -497,6 +509,7 @@ impl Mp4TrackWriter { let mp4a = Mp4aBox::new(aac_config); trak.mdia.minf.stbl.stsd.mp4a = Some(mp4a); } + MediaConfig::TtxtConfig(ref _ttxt_config) => {} } Ok(Mp4TrackWriter { trak, diff --git a/src/types.rs b/src/types.rs index e416257..1f75651 100644 --- a/src/types.rs +++ b/src/types.rs @@ -493,11 +493,15 @@ impl Default for AacConfig { } } +#[derive(Debug, PartialEq, Clone, Default)] +pub struct TtxtConfig {} + #[derive(Debug, PartialEq, Clone)] pub enum MediaConfig { AvcConfig(AvcConfig), HevcConfig(HevcConfig), AacConfig(AacConfig), + TtxtConfig(TtxtConfig), } #[derive(Debug)] From e879500c1add6d006294576fb1f7638f6c0962ee Mon Sep 17 00:00:00 2001 From: Alf Date: Sat, 5 Sep 2020 13:15:31 -0700 Subject: [PATCH 3/5] update mp4info example and track. --- examples/mp4info.rs | 2 +- src/track.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/mp4info.rs b/examples/mp4info.rs index 69bb3d6..a263777 100644 --- a/examples/mp4info.rs +++ b/examples/mp4info.rs @@ -130,7 +130,7 @@ fn subtitle_info(track: &Mp4Track) -> Result { track.box_type()?, )) } else { - Ok("subtitle test".to_string()) + Err(Error::InvalidData("tx3g box not found")) } } diff --git a/src/track.rs b/src/track.rs index 8e65def..4644533 100644 --- a/src/track.rs +++ b/src/track.rs @@ -17,6 +17,7 @@ use crate::mp4box::{ stsc::StscEntry, stss::StssBox, stts::SttsEntry, + tx3g::Tx3gBox, vmhd::VmhdBox, }; use crate::*; @@ -509,7 +510,10 @@ impl Mp4TrackWriter { let mp4a = Mp4aBox::new(aac_config); trak.mdia.minf.stbl.stsd.mp4a = Some(mp4a); } - MediaConfig::TtxtConfig(ref _ttxt_config) => {} + MediaConfig::TtxtConfig(ref _ttxt_config) => { + let tx3g = Tx3gBox::default(); + trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g); + } } Ok(Mp4TrackWriter { trak, From 6bc1c9b217aacabba635bc7494611c03963642e6 Mon Sep 17 00:00:00 2001 From: Alf Date: Sat, 5 Sep 2020 13:38:55 -0700 Subject: [PATCH 4/5] update readme. --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 075ee92..17606ac 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # mp4rs -> MP4 Reader in Rust +> MP4 Reader and Writer in Rust 🦀 -ISO/IEC 14496-12 - ISO Base Media File Format (QuickTime, MPEG-4, etc) +`mp4rs` is a Rust library to read and write ISO-MP4 files. This package contains MPEG-4 specifications defined in parts: +* [ISO/IEC 14496-12](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - ISO Base Media File Format (QuickTime, MPEG-4, etc) +* [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format +* ISO/IEC 14496-17 - Streaming text format [![Crates.io](https://img.shields.io/crates/v/mp4)](https://crates.io/crates/mp4) [![Crates.io](https://img.shields.io/crates/d/mp4)](https://crates.io/crates/mp4) [![Build Status](https://travis-ci.org/alfg/mp4rs.svg?branch=master)](https://travis-ci.org/alfg/mp4rs) -![Rust](https://github.com/alfg/mp4rs/workflows/Rust/badge.svg) +[![Rust](https://github.com/alfg/mp4rs/workflows/Rust/badge.svg)](https://github.com/alfg/mp4rs/actions) #### Example ```rust @@ -71,6 +74,11 @@ cargo build cargo run --example mp4info ``` +* `mp4dump` +``` +cargo run --example mp4dump +``` + #### Run Tests ``` cargo test @@ -89,11 +97,8 @@ cargo bench View HTML report at `target/criterion/report/index.html` -## Resources -Thanks to the following resources used when learning Rust: -* https://github.com/mozilla/mp4parse-rust -* https://github.com/pcwalton/rust-media -* https://github.com/alfg/mp4 +## Web Assembly +See the [mp4-inspector](https://github.com/alfg/mp4-inspector) project as a reference for using this library in Javascript via Web Assembly. ## License MIT From 76607394df7ff3d83d25c61e8590e7db403630bd Mon Sep 17 00:00:00 2001 From: Alf Date: Sat, 5 Sep 2020 13:43:48 -0700 Subject: [PATCH 5/5] update readme. --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 17606ac..4714330 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,13 @@ cargo bench View HTML report at `target/criterion/report/index.html` - ## Web Assembly See the [mp4-inspector](https://github.com/alfg/mp4-inspector) project as a reference for using this library in Javascript via Web Assembly. +## Resources +* https://github.com/mozilla/mp4parse-rust +* https://github.com/pcwalton/rust-media +* https://github.com/alfg/mp4 + ## License MIT