mirror of
https://github.com/alfg/mp4-rust.git
synced 2025-02-22 07:56:16 +00:00
Merge pull request #28 from alfg/subtitle-track-support
Subtitle track support
This commit is contained in:
commit
d0e169b679
9 changed files with 284 additions and 20 deletions
17
README.md
17
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
|
||||
|
||||
[](https://crates.io/crates/mp4)
|
||||
[](https://crates.io/crates/mp4)
|
||||
[](https://travis-ci.org/alfg/mp4rs)
|
||||

|
||||
[](https://github.com/alfg/mp4rs/actions)
|
||||
|
||||
#### Example
|
||||
```rust
|
||||
|
@ -71,6 +74,11 @@ cargo build
|
|||
cargo run --example mp4info <movie.mp4>
|
||||
```
|
||||
|
||||
* `mp4dump`
|
||||
```
|
||||
cargo run --example mp4dump <movie.mp4>
|
||||
```
|
||||
|
||||
#### Run Tests
|
||||
```
|
||||
cargo test
|
||||
|
@ -88,9 +96,10 @@ 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
|
||||
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
|
||||
|
|
|
@ -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<String> = env::args().collect();
|
||||
|
@ -58,6 +67,7 @@ fn copy<P: AsRef<Path>>(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 {
|
||||
|
|
|
@ -48,6 +48,7 @@ fn info<P: AsRef<Path>>(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<String> {
|
|||
fn audio_info(track: &Mp4Track) -> Result<String> {
|
||||
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<String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn subtitle_info(track: &Mp4Track) -> Result<String> {
|
||||
if track.trak.mdia.minf.stbl.stsd.tx3g.is_some() {
|
||||
Ok(format!(
|
||||
"{} ({:?})",
|
||||
track.media_type()?,
|
||||
track.box_type()?,
|
||||
))
|
||||
} else {
|
||||
Err(Error::InvalidData("tx3g box not found"))
|
||||
}
|
||||
}
|
||||
|
||||
fn creation_time(creation_time: u64) -> u64 {
|
||||
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||
if creation_time >= 2082844800 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -304,13 +304,9 @@ impl<R: Read + Seek> 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<R: Read + Seek> 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<R: Read + Seek> 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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Avc1Box>,
|
||||
pub hev1: Option<Hev1Box>,
|
||||
pub mp4a: Option<Mp4aBox>,
|
||||
pub tx3g: Option<Tx3gBox>,
|
||||
}
|
||||
|
||||
impl StsdBox {
|
||||
|
@ -22,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
|
||||
}
|
||||
|
@ -50,6 +55,7 @@ impl<R: Read + Seek> 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 +71,9 @@ impl<R: Read + Seek> 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 +85,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
avc1,
|
||||
hev1,
|
||||
mp4a,
|
||||
tx3g,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -91,8 +101,12 @@ impl<W: Write> 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)
|
||||
|
|
176
src/mp4box/tx3g.rs
Normal file
176
src/mp4box/tx3g.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
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,
|
||||
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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tx3gBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Tx3gBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + 6 + 32
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Tx3gBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Tx3gBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
let display_flags = reader.read_u32::<BigEndian>()?;
|
||||
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::<BigEndian>()?,
|
||||
reader.read_i16::<BigEndian>()?,
|
||||
reader.read_i16::<BigEndian>()?,
|
||||
reader.read_i16::<BigEndian>()?,
|
||||
];
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Tx3gBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
writer.write_u32::<BigEndian>(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::<BigEndian>(self.box_record[n])?;
|
||||
}
|
||||
for n in 0..12 {
|
||||
writer.write_u8(self.style_record[n])?;
|
||||
}
|
||||
|
||||
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,
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
21
src/track.rs
21
src/track.rs
|
@ -17,6 +17,7 @@ use crate::mp4box::{
|
|||
stsc::StscEntry,
|
||||
stss::StssBox,
|
||||
stts::SttsEntry,
|
||||
tx3g::Tx3gBox,
|
||||
vmhd::VmhdBox,
|
||||
};
|
||||
use crate::*;
|
||||
|
@ -35,6 +36,7 @@ impl From<MediaConfig> 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 +74,17 @@ impl From<AacConfig> for TrackConfig {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TtxtConfig> 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,
|
||||
|
@ -98,6 +111,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 +125,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"))
|
||||
}
|
||||
|
@ -493,6 +510,10 @@ impl Mp4TrackWriter {
|
|||
let mp4a = Mp4aBox::new(aac_config);
|
||||
trak.mdia.minf.stbl.stsd.mp4a = Some(mp4a);
|
||||
}
|
||||
MediaConfig::TtxtConfig(ref _ttxt_config) => {
|
||||
let tx3g = Tx3gBox::default();
|
||||
trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g);
|
||||
}
|
||||
}
|
||||
Ok(Mp4TrackWriter {
|
||||
trak,
|
||||
|
|
16
src/types.rs
16
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<FourCC> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -481,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)]
|
||||
|
|
Loading…
Reference in a new issue