From 05e20124e05fb2897a0496270d2665085ed02394 Mon Sep 17 00:00:00 2001 From: Alf Date: Fri, 4 Sep 2020 23:09:33 -0700 Subject: [PATCH] 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, } } }