use bytes::BytesMut;
use mp4_atom::{Any, AsyncReadFrom, Atom, Esds, Ftyp, Moof, Moov, Trak};
use std::collections::HashMap;
use tokio::io::AsyncRead;
use super::{util, Error, Result};
use crate::{catalog, media};
pub struct Import<R: AsyncRead + Unpin> {
input: R,
broadcast: media::BroadcastProducer,
tracks: HashMap<u32, media::TrackProducer>,
moov: Moov,
moof: Option<Moof>,
}
impl<R: AsyncRead + Unpin> Import<R> {
pub async fn init(mut input: R, output: moq_transfork::BroadcastProducer) -> Result<Self> {
let _ = Ftyp::read_from(&mut input).await?;
let moov = Moov::read_from(&mut input).await?;
let mut broadcast = media::BroadcastProducer::new(output);
let mut tracks = HashMap::default();
for trak in &moov.trak {
let track_id = trak.tkhd.track_id;
let handler = &trak.mdia.hdlr.handler_type;
let track = match handler.as_ref() {
b"vide" => {
let track = Self::init_video(trak)?;
broadcast.create_video(track)?
}
b"soun" => {
let track = Self::init_audio(trak)?;
broadcast.create_audio(track)?
}
b"sbtl" => return Err(Error::UnsupportedTrack("subtitle")),
_ => return Err(Error::UnsupportedTrack("unknown")),
};
tracks.insert(track_id, track);
}
broadcast.publish()?;
Ok(Import {
input,
broadcast,
tracks,
moov,
moof: None,
})
}
fn init_video(trak: &Trak) -> Result<catalog::Video> {
let name = trak.tkhd.track_id.to_string();
let stsd = &trak.mdia.minf.stbl.stsd;
let timescale = trak.mdia.mdhd.timescale;
let track = if let Some(avc1) = &stsd.avc1 {
let avcc = &avc1.avcc;
let mut description = BytesMut::new();
avcc.encode_body(&mut description)?;
catalog::Video {
track: moq_transfork::Track::build(name).priority(2).into(),
resolution: catalog::Dimensions {
width: avc1.width,
height: avc1.height,
},
codec: catalog::H264 {
profile: avcc.avc_profile_indication,
constraints: avcc.profile_compatibility,
level: avcc.avc_level_indication,
}
.into(),
description: description.freeze(),
timescale,
layers: vec![],
bitrate: None,
}
} else if let Some(hev1) = &stsd.hev1 {
let _hvcc = &hev1.hvcc;
return Err(Error::UnsupportedCodec("HEVC"));
} else if let Some(vp09) = &stsd.vp09 {
let vpcc = &vp09.vpcc;
catalog::Video {
track: moq_transfork::Track::build(name).priority(2).into(),
codec: catalog::VP9 {
profile: vpcc.profile,
level: vpcc.level,
bit_depth: vpcc.bit_depth,
chroma_subsampling: vpcc.chroma_subsampling,
color_primaries: vpcc.color_primaries,
transfer_characteristics: vpcc.transfer_characteristics,
matrix_coefficients: vpcc.matrix_coefficients,
full_range: vpcc.video_full_range_flag,
}
.into(),
timescale,
description: Default::default(),
resolution: catalog::Dimensions {
width: vp09.width,
height: vp09.height,
},
layers: vec![],
bitrate: None,
}
} else {
return Err(Error::UnsupportedCodec("unknown"));
};
Ok(track)
}
fn init_audio(trak: &Trak) -> Result<catalog::Audio> {
let name = trak.tkhd.track_id.to_string();
let stsd = &trak.mdia.minf.stbl.stsd;
let timescale = trak.mdia.mdhd.timescale;
let track = if let Some(mp4a) = &stsd.mp4a {
let desc = &mp4a
.esds
.as_ref()
.ok_or(Error::MissingBox(Esds::KIND))?
.es_desc
.dec_config;
if desc.object_type_indication != 0x40 {
return Err(Error::UnsupportedCodec("MPEG2"));
}
catalog::Audio {
track: moq_transfork::Track::build(name).priority(1).into(),
codec: catalog::AAC {
profile: desc.dec_specific.profile,
}
.into(),
timescale,
sample_rate: mp4a.samplerate.integer(),
channel_count: mp4a.channelcount,
bitrate: Some(std::cmp::max(desc.avg_bitrate, desc.max_bitrate)),
}
} else {
return Err(Error::UnsupportedCodec("unknown"));
};
Ok(track)
}
pub async fn run(mut self) -> Result<()> {
while let Some(atom) = Option::<mp4_atom::Any>::read_from(&mut self.input).await? {
self.process(atom)?;
}
Ok(())
}
fn process(&mut self, atom: mp4_atom::Any) -> Result<()> {
match atom {
Any::Ftyp(_) => {
return Err(Error::DuplicateBox(Ftyp::KIND));
}
Any::Moov(_) => {
return Err(Error::DuplicateBox(Moov::KIND));
}
Any::Moof(moof) => {
let track_id = util::frame_track_id(&moof)?;
let keyframe = util::frame_is_key(&moof);
if keyframe {
let trak = self
.moov
.trak
.iter()
.find(|trak| trak.tkhd.track_id == track_id)
.ok_or(Error::UnknownTrack)?;
if trak.mdia.hdlr.handler_type == b"vide".into() {
for track in self.tracks.values_mut() {
track.keyframe();
}
}
}
if self.moof.is_some() {
return Err(Error::DuplicateBox(Moof::KIND));
}
self.moof = Some(moof);
}
Any::Mdat(mdat) => {
let moof = self.moof.take().ok_or(Error::MissingBox(Moof::KIND))?;
let track_id = util::frame_track_id(&moof)?;
let timestamp = util::frame_timestamp(&moof)?;
let track = self.tracks.get_mut(&track_id).ok_or(Error::UnknownTrack)?;
track.write(timestamp, mdat.data);
}
_ => {
}
};
Ok(())
}
pub fn catalog(&self) -> &catalog::Broadcast {
self.broadcast.catalog()
}
}