mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-06-02 13:39:54 +00:00
Refactor common types
This commit is contained in:
parent
18bc289cab
commit
c83a81f174
|
@ -4,7 +4,7 @@ use std::io::prelude::*;
|
|||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Result, Mp4Track, TrackType};
|
||||
use mp4::{Mp4Track, Result, TrackType};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -42,21 +42,32 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
);
|
||||
|
||||
for track in mp4.tracks().iter() {
|
||||
let media_info = match track.track_type() {
|
||||
let media_info = match track.track_type()? {
|
||||
TrackType::Video => video_info(track),
|
||||
TrackType::Audio => audio_info(track),
|
||||
_ => String::from("error")
|
||||
};
|
||||
println!(" Track: #{}({}) {}: {} ({:?}), {}", track.track_id(), track.language(),
|
||||
track.track_type(), track.media_type(), track.box_type(), media_info);
|
||||
println!(
|
||||
" Track: #{}({}) {}: {} ({:?}), {}",
|
||||
track.track_id(),
|
||||
track.language(),
|
||||
track.track_type()?,
|
||||
track.media_type()?,
|
||||
track.box_type(),
|
||||
media_info
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn video_info(track: &Mp4Track) -> String {
|
||||
format!("{}x{}, {} kb/s, {:.2} fps", track.width(), track.height(),
|
||||
track.bitrate() / 1000, track.frame_rate())
|
||||
format!(
|
||||
"{}x{}, {} kb/s, {:.2} fps",
|
||||
track.width(),
|
||||
track.height(),
|
||||
track.bitrate() / 1000,
|
||||
track.frame_rate()
|
||||
)
|
||||
}
|
||||
|
||||
fn audio_info(track: &Mp4Track) -> String {
|
||||
|
@ -65,5 +76,10 @@ fn audio_info(track: &Mp4Track) -> String {
|
|||
2 => String::from("stereo"),
|
||||
n => format!("{}-ch", n),
|
||||
};
|
||||
format!("{} Hz, {}, {} kb/s", track.sample_rate(), ch, track.bitrate() / 1000)
|
||||
format!(
|
||||
"{} Hz, {}, {} kb/s",
|
||||
track.sample_rate(),
|
||||
ch,
|
||||
track.bitrate() / 1000
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::*;
|
||||
|
@ -99,99 +98,6 @@ boxtype! {
|
|||
EsdsBox => 0x65736473
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct FourCC {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> Self {
|
||||
let mut box_chars = Vec::new();
|
||||
for x in 0..4 {
|
||||
let c = (number >> (x * 8) & 0x0000_00FF) as u8;
|
||||
box_chars.push(c);
|
||||
}
|
||||
box_chars.reverse();
|
||||
|
||||
let box_string = match String::from_utf8(box_chars) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"), // error to retrieve fourcc
|
||||
};
|
||||
|
||||
FourCC { value: box_string }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FourCC> for u32 {
|
||||
fn from(fourcc: FourCC) -> u32 {
|
||||
(&fourcc).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FourCC> for u32 {
|
||||
fn from(fourcc: &FourCC) -> u32 {
|
||||
let mut b: [u8; 4] = Default::default();
|
||||
b.copy_from_slice(fourcc.value.as_bytes());
|
||||
u32::from_be_bytes(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for FourCC {
|
||||
fn from(fourcc: String) -> FourCC {
|
||||
let value = if fourcc.len() > 4 {
|
||||
fourcc[0..4].to_string()
|
||||
} else {
|
||||
fourcc
|
||||
};
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for FourCC {
|
||||
fn from(fourcc: &str) -> FourCC {
|
||||
let value = if fourcc.len() > 4 {
|
||||
fourcc[0..4].to_string()
|
||||
} else {
|
||||
fourcc.to_string()
|
||||
};
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for FourCC {
|
||||
fn from(t: BoxType) -> FourCC {
|
||||
let box_num: u32 = Into::into(t);
|
||||
From::from(box_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let code: u32 = self.into();
|
||||
write!(f, "{} / {:#010X}", self.value, code)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Mp4Box: Sized {
|
||||
fn box_type() -> BoxType;
|
||||
fn box_size(&self) -> u64;
|
||||
|
|
51
src/lib.rs
51
src/lib.rs
|
@ -1,58 +1,15 @@
|
|||
use std::fmt;
|
||||
|
||||
pub use bytes::Bytes;
|
||||
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
mod atoms;
|
||||
|
||||
mod reader;
|
||||
pub use reader::Mp4Reader;
|
||||
|
||||
mod track;
|
||||
pub use track::{Mp4Track, TrackType, MediaType};
|
||||
pub use track::Mp4Track;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Sample {
|
||||
pub start_time: u64,
|
||||
pub duration: u32,
|
||||
pub rendering_offset: i32,
|
||||
pub is_sync: bool,
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
|
||||
impl PartialEq for Mp4Sample {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.start_time == other.start_time
|
||||
&& self.duration == other.duration
|
||||
&& self.rendering_offset == other.rendering_offset
|
||||
&& self.is_sync == other.is_sync
|
||||
&& self.bytes.len() == other.bytes.len() // XXX for easy check
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Mp4Sample {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"start_time {}, duration {}, rendering_offset {}, is_sync {}, length {}",
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.rendering_offset,
|
||||
self.is_sync,
|
||||
self.bytes.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn creation_time(creation_time: u64) -> u64 {
|
||||
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||
if creation_time >= 2082844800 {
|
||||
creation_time - 2082844800
|
||||
} else {
|
||||
creation_time
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use crate::atoms::*;
|
||||
use crate::{Error, Mp4Sample, Mp4Track, Result};
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Reader<R> {
|
||||
|
@ -62,9 +62,8 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
let tracks = if let Some(ref moov) = moov {
|
||||
let mut tracks = Vec::with_capacity(moov.traks.len());
|
||||
for (i, trak) in moov.traks.iter().enumerate() {
|
||||
let track_id = i as u32 + 1;
|
||||
assert_eq!(track_id, trak.tkhd.track_id);
|
||||
tracks.push(Mp4Track::new(track_id, trak));
|
||||
assert_eq!(trak.tkhd.track_id, i as u32 + 1);
|
||||
tracks.push(Mp4Track::from(trak));
|
||||
}
|
||||
tracks
|
||||
} else {
|
||||
|
|
171
src/track.rs
171
src/track.rs
|
@ -1,110 +1,47 @@
|
|||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::atoms::trak::TrakBox;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{trak::TrakBox, stbl::StblBox};
|
||||
use crate::{Bytes, Error, Mp4Sample, Result};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TrackType {
|
||||
Video,
|
||||
Audio,
|
||||
Hint,
|
||||
Text,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl fmt::Display for TrackType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = match self {
|
||||
TrackType::Video => "video",
|
||||
TrackType::Audio => "audio",
|
||||
TrackType::Hint => "hint",
|
||||
TrackType::Text => "text",
|
||||
TrackType::Unknown => "unknown", // XXX
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for TrackType {
|
||||
fn from(handler: &str) -> TrackType {
|
||||
match handler {
|
||||
"vide" => TrackType::Video,
|
||||
"soun" => TrackType::Audio,
|
||||
"hint" => TrackType::Hint,
|
||||
"text" => TrackType::Text,
|
||||
_ => TrackType::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FourCC> for TrackType {
|
||||
fn from(fourcc: &FourCC) -> TrackType {
|
||||
TrackType::from(fourcc.value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum MediaType {
|
||||
H264,
|
||||
AAC,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl fmt::Display for MediaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = match self {
|
||||
MediaType::H264 => "h264",
|
||||
MediaType::AAC => "aac",
|
||||
MediaType::Unknown => "unknown", // XXX
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Track {
|
||||
track_id: u32,
|
||||
track_type: TrackType,
|
||||
media_type: MediaType,
|
||||
trak: TrakBox,
|
||||
}
|
||||
|
||||
impl Mp4Track {
|
||||
pub(crate) fn new(track_id: u32, trak: &TrakBox) -> Self {
|
||||
pub(crate) fn from(trak: &TrakBox) -> Self {
|
||||
let trak = trak.clone();
|
||||
let track_type = (&trak.mdia.hdlr.handler_type).into();
|
||||
let media_type = if trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
||||
MediaType::H264
|
||||
} else if trak.mdia.minf.stbl.stsd.mp4a.is_some() {
|
||||
MediaType::AAC
|
||||
} else {
|
||||
MediaType::Unknown
|
||||
};
|
||||
Self { track_id, track_type, media_type, trak }
|
||||
Self { trak }
|
||||
}
|
||||
|
||||
pub fn track_id(&self) -> u32 {
|
||||
self.track_id
|
||||
self.trak.tkhd.track_id
|
||||
}
|
||||
|
||||
pub fn track_type(&self) -> TrackType {
|
||||
self.track_type
|
||||
pub fn track_type(&self) -> Result<TrackType> {
|
||||
TrackType::try_from(&self.trak.mdia.hdlr.handler_type)
|
||||
}
|
||||
|
||||
pub fn media_type(&self) -> MediaType {
|
||||
self.media_type
|
||||
}
|
||||
|
||||
pub fn box_type(&self) -> FourCC {
|
||||
pub fn media_type(&self) -> Result<MediaType> {
|
||||
if self.trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
||||
FourCC::from(BoxType::Avc1Box)
|
||||
Ok(MediaType::H264)
|
||||
} else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() {
|
||||
FourCC::from(BoxType::Mp4aBox)
|
||||
Ok(MediaType::AAC)
|
||||
} else {
|
||||
FourCC::from("null") // XXX
|
||||
Err(Error::InvalidData("unsupported media type"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn box_type(&self) -> Result<FourCC> {
|
||||
if self.trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
||||
Ok(FourCC::from(BoxType::Avc1Box))
|
||||
} else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() {
|
||||
Ok(FourCC::from(BoxType::Mp4aBox))
|
||||
} else {
|
||||
Err(Error::InvalidData("unsupported sample entry box"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,8 +95,9 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn duration(&self) -> Duration {
|
||||
Duration::from_micros(self.trak.mdia.mdhd.duration * 1_000_000
|
||||
/ self.trak.mdia.mdhd.timescale as u64)
|
||||
Duration::from_micros(
|
||||
self.trak.mdia.mdhd.duration * 1_000_000 / self.trak.mdia.mdhd.timescale as u64,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bitrate(&self) -> u32 {
|
||||
|
@ -173,48 +111,39 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn sample_count(&self) -> u32 {
|
||||
let stsz = &self.stbl().stsz;
|
||||
stsz.sample_sizes.len() as u32
|
||||
}
|
||||
|
||||
fn stbl(&self) -> &StblBox {
|
||||
&self.trak.mdia.minf.stbl
|
||||
self.trak.mdia.minf.stbl.stsz.sample_sizes.len() as u32
|
||||
}
|
||||
|
||||
fn stsc_index(&self, sample_id: u32) -> usize {
|
||||
let stsc = &self.stbl().stsc;
|
||||
|
||||
for (i, entry) in stsc.entries.iter().enumerate() {
|
||||
for (i, entry) in self.trak.mdia.minf.stbl.stsc.entries.iter().enumerate() {
|
||||
if sample_id < entry.first_sample {
|
||||
assert_ne!(i, 0);
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
assert_ne!(stsc.entries.len(), 0);
|
||||
stsc.entries.len() - 1
|
||||
assert_ne!(self.trak.mdia.minf.stbl.stsc.entries.len(), 0);
|
||||
self.trak.mdia.minf.stbl.stsc.entries.len() - 1
|
||||
}
|
||||
|
||||
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
|
||||
let stbl = self.stbl();
|
||||
|
||||
if let Some(ref stco) = stbl.stco {
|
||||
if let Some(ref stco) = self.trak.mdia.minf.stbl.stco {
|
||||
if let Some(offset) = stco.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset as u64);
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id,
|
||||
self.track_id(),
|
||||
BoxType::StcoBox,
|
||||
chunk_id,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if let Some(ref co64) = stbl.co64 {
|
||||
if let Some(ref co64) = self.trak.mdia.minf.stbl.co64 {
|
||||
if let Some(offset) = co64.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset);
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id,
|
||||
self.track_id(),
|
||||
BoxType::Co64Box,
|
||||
chunk_id,
|
||||
));
|
||||
|
@ -222,18 +151,16 @@ impl Mp4Track {
|
|||
}
|
||||
}
|
||||
|
||||
assert!(stbl.stco.is_some() || stbl.co64.is_some());
|
||||
assert!(self.trak.mdia.minf.stbl.stco.is_some() || self.trak.mdia.minf.stbl.co64.is_some());
|
||||
return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box));
|
||||
}
|
||||
|
||||
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
|
||||
let stbl = self.stbl();
|
||||
|
||||
assert!(stbl.ctts.is_some());
|
||||
let ctts = if let Some(ref ctts) = stbl.ctts {
|
||||
assert!(self.trak.mdia.minf.stbl.ctts.is_some());
|
||||
let ctts = if let Some(ref ctts) = self.trak.mdia.minf.stbl.ctts {
|
||||
ctts
|
||||
} else {
|
||||
return Err(Error::BoxInStblNotFound(self.track_id, BoxType::CttsBox));
|
||||
return Err(Error::BoxInStblNotFound(self.track_id(), BoxType::CttsBox));
|
||||
};
|
||||
|
||||
let mut sample_count = 1;
|
||||
|
@ -245,14 +172,14 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id,
|
||||
self.track_id(),
|
||||
BoxType::CttsBox,
|
||||
sample_id,
|
||||
));
|
||||
}
|
||||
|
||||
fn sample_size(&self, sample_id: u32) -> Result<u32> {
|
||||
let stsz = &self.stbl().stsz;
|
||||
let stsz = &self.trak.mdia.minf.stbl.stsz;
|
||||
if stsz.sample_size > 0 {
|
||||
return Ok(stsz.sample_size);
|
||||
}
|
||||
|
@ -260,7 +187,7 @@ impl Mp4Track {
|
|||
Ok(*size)
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id,
|
||||
self.track_id(),
|
||||
BoxType::StszBox,
|
||||
sample_id,
|
||||
));
|
||||
|
@ -268,7 +195,7 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
fn total_sample_size(&self) -> u64 {
|
||||
let stsz = &self.stbl().stsz;
|
||||
let stsz = &self.trak.mdia.minf.stbl.stsz;
|
||||
if stsz.sample_size > 0 {
|
||||
stsz.sample_size as u64 * self.sample_count() as u64
|
||||
} else {
|
||||
|
@ -283,7 +210,7 @@ impl Mp4Track {
|
|||
fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
||||
let stsc_index = self.stsc_index(sample_id);
|
||||
|
||||
let stsc = &self.stbl().stsc;
|
||||
let stsc = &self.trak.mdia.minf.stbl.stsc;
|
||||
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
|
||||
|
||||
let first_chunk = stsc_entry.first_chunk;
|
||||
|
@ -305,7 +232,7 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
|
||||
let stts = &self.stbl().stts;
|
||||
let stts = &self.trak.mdia.minf.stbl.stts;
|
||||
|
||||
let mut sample_count = 1;
|
||||
let mut elapsed = 0;
|
||||
|
@ -322,16 +249,14 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id,
|
||||
self.track_id(),
|
||||
BoxType::SttsBox,
|
||||
sample_id,
|
||||
));
|
||||
}
|
||||
|
||||
fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
|
||||
let stbl = self.stbl();
|
||||
|
||||
if let Some(ref ctts) = stbl.ctts {
|
||||
if let Some(ref ctts) = self.trak.mdia.minf.stbl.ctts {
|
||||
if let Ok((ctts_index, _)) = self.ctts_index(sample_id) {
|
||||
let ctts_entry = ctts.entries.get(ctts_index).unwrap();
|
||||
return ctts_entry.sample_offset;
|
||||
|
@ -341,9 +266,7 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
fn is_sync_sample(&self, sample_id: u32) -> bool {
|
||||
let stbl = self.stbl();
|
||||
|
||||
if let Some(ref stss) = stbl.stss {
|
||||
if let Some(ref stss) = self.trak.mdia.minf.stbl.stss {
|
||||
match stss.entries.binary_search(&sample_id) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
|
|
343
src/types.rs
Normal file
343
src/types.rs
Normal file
|
@ -0,0 +1,343 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use crate::atoms::*;
|
||||
use crate::*;
|
||||
|
||||
pub use bytes::Bytes;
|
||||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct FourCC {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> Self {
|
||||
let mut box_chars = Vec::new();
|
||||
for x in 0..4 {
|
||||
let c = (number >> (x * 8) & 0x0000_00FF) as u8;
|
||||
box_chars.push(c);
|
||||
}
|
||||
box_chars.reverse();
|
||||
|
||||
let box_string = match String::from_utf8(box_chars) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"), // error to retrieve fourcc
|
||||
};
|
||||
|
||||
FourCC { value: box_string }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FourCC> for u32 {
|
||||
fn from(fourcc: FourCC) -> u32 {
|
||||
(&fourcc).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FourCC> for u32 {
|
||||
fn from(fourcc: &FourCC) -> u32 {
|
||||
let mut b: [u8; 4] = Default::default();
|
||||
b.copy_from_slice(fourcc.value.as_bytes());
|
||||
u32::from_be_bytes(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for FourCC {
|
||||
fn from(fourcc: String) -> FourCC {
|
||||
let value = if fourcc.len() > 4 {
|
||||
fourcc[0..4].to_string()
|
||||
} else {
|
||||
fourcc
|
||||
};
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for FourCC {
|
||||
fn from(fourcc: &str) -> FourCC {
|
||||
let value = if fourcc.len() > 4 {
|
||||
fourcc[0..4].to_string()
|
||||
} else {
|
||||
fourcc.to_string()
|
||||
};
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for FourCC {
|
||||
fn from(t: BoxType) -> FourCC {
|
||||
let box_num: u32 = Into::into(t);
|
||||
From::from(box_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let code: u32 = self.into();
|
||||
write!(f, "{} / {:#010X}", self.value, code)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
const HANDLER_TYPE_VIDEO: &str = "vide";
|
||||
const HANDLER_TYPE_AUDIO: &str = "soun";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TrackType {
|
||||
Video,
|
||||
Audio,
|
||||
}
|
||||
|
||||
impl fmt::Display for TrackType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s: &str = self.into();
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for TrackType {
|
||||
type Error = Error;
|
||||
fn try_from(handler: &str) -> Result<TrackType> {
|
||||
match handler {
|
||||
HANDLER_TYPE_VIDEO => Ok(TrackType::Video),
|
||||
HANDLER_TYPE_AUDIO => Ok(TrackType::Audio),
|
||||
_ => Err(Error::InvalidData("unsupported handler type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<&str> for TrackType {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
TrackType::Video => HANDLER_TYPE_VIDEO,
|
||||
TrackType::Audio => HANDLER_TYPE_AUDIO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<&str> for &TrackType {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
TrackType::Video => HANDLER_TYPE_VIDEO,
|
||||
TrackType::Audio => HANDLER_TYPE_AUDIO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&FourCC> for TrackType {
|
||||
type Error = Error;
|
||||
fn try_from(fourcc: &FourCC) -> Result<TrackType> {
|
||||
TrackType::try_from(fourcc.value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FourCC> for TrackType {
|
||||
fn into(self) -> FourCC {
|
||||
let s: &str = self.into();
|
||||
FourCC::from(s)
|
||||
}
|
||||
}
|
||||
|
||||
const MEDIA_TYPE_H264: &str = "h264";
|
||||
const MEDIA_TYPE_AAC: &str = "aac";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum MediaType {
|
||||
H264,
|
||||
AAC,
|
||||
}
|
||||
|
||||
impl fmt::Display for MediaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s: &str = self.into();
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MediaType {
|
||||
type Error = Error;
|
||||
fn try_from(media: &str) -> Result<MediaType> {
|
||||
match media {
|
||||
MEDIA_TYPE_H264 => Ok(MediaType::H264),
|
||||
MEDIA_TYPE_AAC => Ok(MediaType::AAC),
|
||||
_ => Err(Error::InvalidData("unsupported media type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<&str> for MediaType {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
MediaType::H264 => MEDIA_TYPE_H264,
|
||||
MediaType::AAC => MEDIA_TYPE_AAC,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<&str> for &MediaType {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
MediaType::H264 => MEDIA_TYPE_H264,
|
||||
MediaType::AAC => MEDIA_TYPE_AAC,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum SampleFreqIndex {
|
||||
Freq96000 = 0x0,
|
||||
Freq88200 = 0x1,
|
||||
Freq64000 = 0x2,
|
||||
Freq48000 = 0x3,
|
||||
Freq44100 = 0x4,
|
||||
Freq32000 = 0x5,
|
||||
Freq24000 = 0x6,
|
||||
Freq22050 = 0x7,
|
||||
Freq16000 = 0x8,
|
||||
Freq12000 = 0x9,
|
||||
Freq11025 = 0xa,
|
||||
Freq8000 = 0xb,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SampleFreqIndex {
|
||||
type Error = Error;
|
||||
fn try_from(value: u8) -> Result<SampleFreqIndex> {
|
||||
match value {
|
||||
0x0 => Ok(SampleFreqIndex::Freq96000),
|
||||
0x1 => Ok(SampleFreqIndex::Freq88200),
|
||||
0x2 => Ok(SampleFreqIndex::Freq64000),
|
||||
0x3 => Ok(SampleFreqIndex::Freq48000),
|
||||
0x4 => Ok(SampleFreqIndex::Freq44100),
|
||||
0x5 => Ok(SampleFreqIndex::Freq32000),
|
||||
0x6 => Ok(SampleFreqIndex::Freq24000),
|
||||
0x7 => Ok(SampleFreqIndex::Freq22050),
|
||||
0x8 => Ok(SampleFreqIndex::Freq16000),
|
||||
0x9 => Ok(SampleFreqIndex::Freq12000),
|
||||
0xa => Ok(SampleFreqIndex::Freq11025),
|
||||
0xb => Ok(SampleFreqIndex::Freq8000),
|
||||
_ => Err(Error::InvalidData("invalid sampling frequency index")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SampleFreqIndex {
|
||||
pub fn freq(&self) -> u32 {
|
||||
match self {
|
||||
&SampleFreqIndex::Freq96000 => 96000,
|
||||
&SampleFreqIndex::Freq88200 => 88200,
|
||||
&SampleFreqIndex::Freq64000 => 64000,
|
||||
&SampleFreqIndex::Freq48000 => 48000,
|
||||
&SampleFreqIndex::Freq44100 => 44100,
|
||||
&SampleFreqIndex::Freq32000 => 32000,
|
||||
&SampleFreqIndex::Freq24000 => 24000,
|
||||
&SampleFreqIndex::Freq22050 => 22050,
|
||||
&SampleFreqIndex::Freq16000 => 16000,
|
||||
&SampleFreqIndex::Freq12000 => 12000,
|
||||
&SampleFreqIndex::Freq11025 => 11025,
|
||||
&SampleFreqIndex::Freq8000 => 8000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum ChannelConfig {
|
||||
Mono = 0x1,
|
||||
Stereo = 0x2,
|
||||
Three = 0x3,
|
||||
Four = 0x4,
|
||||
Five = 0x5,
|
||||
FiveOne = 0x6,
|
||||
SevenOne = 0x7,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ChannelConfig {
|
||||
type Error = Error;
|
||||
fn try_from(value: u8) -> Result<ChannelConfig> {
|
||||
match value {
|
||||
0x1 => Ok(ChannelConfig::Mono),
|
||||
0x2 => Ok(ChannelConfig::Stereo),
|
||||
0x3 => Ok(ChannelConfig::Three),
|
||||
0x4 => Ok(ChannelConfig::Four),
|
||||
0x5 => Ok(ChannelConfig::Five),
|
||||
0x6 => Ok(ChannelConfig::FiveOne),
|
||||
0x7 => Ok(ChannelConfig::SevenOne),
|
||||
_ => Err(Error::InvalidData("invalid channel configuration")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum MediaConfig {
|
||||
AVC {
|
||||
width: u16,
|
||||
height: u16,
|
||||
// sps: Vec<u8>,
|
||||
// pps: Vec<u8>,
|
||||
},
|
||||
AAC {
|
||||
freq_index: SampleFreqIndex,
|
||||
chan_conf: ChannelConfig,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Sample {
|
||||
pub start_time: u64,
|
||||
pub duration: u32,
|
||||
pub rendering_offset: i32,
|
||||
pub is_sync: bool,
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
|
||||
impl PartialEq for Mp4Sample {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.start_time == other.start_time
|
||||
&& self.duration == other.duration
|
||||
&& self.rendering_offset == other.rendering_offset
|
||||
&& self.is_sync == other.is_sync
|
||||
&& self.bytes.len() == other.bytes.len() // XXX for easy check
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Mp4Sample {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"start_time {}, duration {}, rendering_offset {}, is_sync {}, length {}",
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.rendering_offset,
|
||||
self.is_sync,
|
||||
self.bytes.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn creation_time(creation_time: u64) -> u64 {
|
||||
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||
if creation_time >= 2082844800 {
|
||||
creation_time - 2082844800
|
||||
} else {
|
||||
creation_time
|
||||
}
|
||||
}
|
10
tests/lib.rs
10
tests/lib.rs
|
@ -1,4 +1,4 @@
|
|||
use mp4::{TrackType, MediaType};
|
||||
use mp4::{MediaType, TrackType};
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
|
@ -81,8 +81,8 @@ fn test_read_mp4() {
|
|||
// track #1
|
||||
let track1 = mp4.tracks().get(0).unwrap();
|
||||
assert_eq!(track1.track_id(), 1);
|
||||
assert_eq!(track1.track_type(), TrackType::Video);
|
||||
assert_eq!(track1.media_type(), MediaType::H264);
|
||||
assert_eq!(track1.track_type().unwrap(), TrackType::Video);
|
||||
assert_eq!(track1.media_type().unwrap(), MediaType::H264);
|
||||
assert_eq!(track1.width(), 320);
|
||||
assert_eq!(track1.height(), 240);
|
||||
assert_eq!(track1.bitrate(), 0); // XXX
|
||||
|
@ -90,8 +90,8 @@ fn test_read_mp4() {
|
|||
|
||||
// track #2
|
||||
let track2 = mp4.tracks().get(1).unwrap();
|
||||
assert_eq!(track2.track_type(), TrackType::Audio);
|
||||
assert_eq!(track2.media_type(), MediaType::AAC);
|
||||
assert_eq!(track2.track_type().unwrap(), TrackType::Audio);
|
||||
assert_eq!(track2.media_type().unwrap(), MediaType::AAC);
|
||||
assert_eq!(track2.sample_rate(), 48000);
|
||||
assert_eq!(track2.bitrate(), 0); // XXX
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue