use super::atom_info::{AtomIdent, AtomInfo};
use super::read::{nested_atom, skip_unneeded, AtomReader};
use crate::error::{LoftyError, Result};
use crate::macros::{decode_err, err, try_vec};
use crate::properties::FileProperties;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::time::Duration;
use byteorder::{BigEndian, ReadBytesExt};
#[allow(missing_docs)]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Mp4Codec {
#[default]
Unknown,
AAC,
ALAC,
MP3,
FLAC,
}
#[allow(missing_docs)]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[rustfmt::skip]
#[non_exhaustive]
pub enum AudioObjectType {
#[default]
NULL = 0,
AacMain = 1, AacLowComplexity = 2, AacScalableSampleRate = 3, AacLongTermPrediction = 4, SpectralBandReplication = 5, AACScalable = 6, TwinVQ = 7, CodeExcitedLinearPrediction = 8, HarmonicVectorExcitationCoding = 9, TextToSpeechtInterface = 12, MainSynthetic = 13, WavetableSynthesis = 14, GeneralMIDI = 15, AlgorithmicSynthesis = 16, ErrorResilientAacLowComplexity = 17, ErrorResilientAacLongTermPrediction = 19, ErrorResilientAacScalable = 20, ErrorResilientAacTwinVQ = 21, ErrorResilientAacBitSlicedArithmeticCoding = 22, ErrorResilientAacLowDelay = 23, ErrorResilientCodeExcitedLinearPrediction = 24, ErrorResilientHarmonicVectorExcitationCoding = 25, ErrorResilientHarmonicIndividualLinesNoise = 26, ErrorResilientParametric = 27, SinuSoidalCoding = 28, ParametricStereo = 29, MpegSurround = 30, MpegLayer1 = 32, MpegLayer2 = 33, MpegLayer3 = 34, DirectStreamTransfer = 35, AudioLosslessCoding = 36, ScalableLosslessCoding = 37, ScalableLosslessCodingNoneCore = 38, ErrorResilientAacEnhancedLowDelay = 39, SymbolicMusicRepresentationSimple = 40, SymbolicMusicRepresentationMain = 41, UnifiedSpeechAudioCoding = 42, SpatialAudioObjectCoding = 43, LowDelayMpegSurround = 44, SpatialAudioObjectCodingDialogueEnhancement = 45, AudioSync = 46, }
impl TryFrom<u8> for AudioObjectType {
type Error = LoftyError;
#[rustfmt::skip]
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
match value {
1 => Ok(Self::AacMain),
2 => Ok(Self::AacLowComplexity),
3 => Ok(Self::AacScalableSampleRate),
4 => Ok(Self::AacLongTermPrediction),
5 => Ok(Self::SpectralBandReplication),
6 => Ok(Self::AACScalable),
7 => Ok(Self::TwinVQ),
8 => Ok(Self::CodeExcitedLinearPrediction),
9 => Ok(Self::HarmonicVectorExcitationCoding),
12 => Ok(Self::TextToSpeechtInterface),
13 => Ok(Self::MainSynthetic),
14 => Ok(Self::WavetableSynthesis),
15 => Ok(Self::GeneralMIDI),
16 => Ok(Self::AlgorithmicSynthesis),
17 => Ok(Self::ErrorResilientAacLowComplexity),
19 => Ok(Self::ErrorResilientAacLongTermPrediction),
20 => Ok(Self::ErrorResilientAacScalable),
21 => Ok(Self::ErrorResilientAacTwinVQ),
22 => Ok(Self::ErrorResilientAacBitSlicedArithmeticCoding),
23 => Ok(Self::ErrorResilientAacLowDelay),
24 => Ok(Self::ErrorResilientCodeExcitedLinearPrediction),
25 => Ok(Self::ErrorResilientHarmonicVectorExcitationCoding),
26 => Ok(Self::ErrorResilientHarmonicIndividualLinesNoise),
27 => Ok(Self::ErrorResilientParametric),
28 => Ok(Self::SinuSoidalCoding),
29 => Ok(Self::ParametricStereo),
30 => Ok(Self::MpegSurround),
32 => Ok(Self::MpegLayer1),
33 => Ok(Self::MpegLayer2),
34 => Ok(Self::MpegLayer3),
35 => Ok(Self::DirectStreamTransfer),
36 => Ok(Self::AudioLosslessCoding),
37 => Ok(Self::ScalableLosslessCoding),
38 => Ok(Self::ScalableLosslessCodingNoneCore),
39 => Ok(Self::ErrorResilientAacEnhancedLowDelay),
40 => Ok(Self::SymbolicMusicRepresentationSimple),
41 => Ok(Self::SymbolicMusicRepresentationMain),
42 => Ok(Self::UnifiedSpeechAudioCoding),
43 => Ok(Self::SpatialAudioObjectCoding),
44 => Ok(Self::LowDelayMpegSurround),
45 => Ok(Self::SpatialAudioObjectCodingDialogueEnhancement),
46 => Ok(Self::AudioSync),
_ => decode_err!(@BAIL MP4, "Encountered an invalid audio object type"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct Mp4Properties {
pub(crate) codec: Mp4Codec,
pub(crate) extended_audio_object_type: Option<AudioObjectType>,
pub(crate) duration: Duration,
pub(crate) overall_bitrate: u32,
pub(crate) audio_bitrate: u32,
pub(crate) sample_rate: u32,
pub(crate) bit_depth: Option<u8>,
pub(crate) channels: u8,
}
impl From<Mp4Properties> for FileProperties {
fn from(input: Mp4Properties) -> Self {
Self {
duration: input.duration,
overall_bitrate: Some(input.overall_bitrate),
audio_bitrate: Some(input.audio_bitrate),
sample_rate: Some(input.sample_rate),
bit_depth: input.bit_depth,
channels: Some(input.channels),
}
}
}
impl Mp4Properties {
pub fn duration(&self) -> Duration {
self.duration
}
pub fn overall_bitrate(&self) -> u32 {
self.overall_bitrate
}
pub fn audio_bitrate(&self) -> u32 {
self.audio_bitrate
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn bit_depth(&self) -> Option<u8> {
self.bit_depth
}
pub fn channels(&self) -> u8 {
self.channels
}
pub fn codec(&self) -> &Mp4Codec {
&self.codec
}
pub fn audio_object_type(&self) -> Option<AudioObjectType> {
self.extended_audio_object_type
}
}
pub(super) fn read_properties<R>(
reader: &mut AtomReader<R>,
traks: &[AtomInfo],
file_length: u64,
) -> Result<Mp4Properties>
where
R: Read + Seek,
{
let mut audio_track = false;
let mut mdhd = None;
let mut minf = None;
for mdia in traks.iter() {
if audio_track {
break;
}
reader.seek(SeekFrom::Start(mdia.start + 8))?;
let mut read = 8;
while read < mdia.len {
let atom = reader.next()?;
read += atom.len;
if let AtomIdent::Fourcc(fourcc) = atom.ident {
match &fourcc {
b"mdhd" => {
skip_unneeded(reader, atom.extended, atom.len)?;
mdhd = Some(atom)
},
b"hdlr" => {
reader.seek(SeekFrom::Current(8))?;
let mut handler_type = [0; 4];
reader.read_exact(&mut handler_type)?;
if &handler_type == b"soun" {
audio_track = true
}
skip_unneeded(reader, atom.extended, atom.len - 12)?;
},
b"minf" => minf = Some(atom),
_ => {
skip_unneeded(reader, atom.extended, atom.len)?;
},
}
continue;
}
skip_unneeded(reader, atom.extended, atom.len)?;
}
}
if !audio_track {
decode_err!(@BAIL MP4, "File contains no audio tracks");
}
let mdhd = match mdhd {
Some(mdhd) => mdhd,
None => err!(BadAtom("Expected atom \"trak.mdia.mdhd\"")),
};
reader.seek(SeekFrom::Start(mdhd.start + 8))?;
let version = reader.read_u8()?;
let _flags = reader.read_uint(3)?;
let (timescale, duration) = if version == 1 {
let _creation_time = reader.read_u64()?;
let _modification_time = reader.read_u64()?;
let timescale = reader.read_u32()?;
let duration = reader.read_u64()?;
(timescale, duration)
} else {
let _creation_time = reader.read_u32()?;
let _modification_time = reader.read_u32()?;
let timescale = reader.read_u32()?;
let duration = reader.read_u32()?;
(timescale, u64::from(duration))
};
let duration = Duration::from_millis(duration * 1000 / u64::from(timescale));
let mut properties = Mp4Properties {
duration,
..Mp4Properties::default()
};
if let Some(minf) = minf {
reader.seek(SeekFrom::Start(minf.start + 8))?;
if let Some(stbl) = nested_atom(reader, minf.len, b"stbl")? {
if let Some(stsd) = nested_atom(reader, stbl.len, b"stsd")? {
let mut stsd = try_vec![0; (stsd.len - 8) as usize];
reader.read_exact(&mut stsd)?;
let mut cursor = Cursor::new(&*stsd);
let mut stsd_reader = AtomReader::new(&mut cursor)?;
stsd_reader.seek(SeekFrom::Current(8))?;
let atom = AtomInfo::read(&mut stsd_reader, stsd.len() as u64)?;
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
match fourcc {
b"mp4a" => mp4a_properties(&mut stsd_reader, &mut properties)?,
b"alac" => alac_properties(&mut stsd_reader, &mut properties)?,
b"fLaC" => flac_properties(&mut stsd_reader, &mut properties)?,
_ => {},
}
let duration_millis = properties.duration.as_millis();
if duration_millis > 0 {
let overall_bitrate = u128::from(file_length * 8) / duration_millis;
properties.overall_bitrate = overall_bitrate as u32;
if properties.audio_bitrate == 0 {
properties.audio_bitrate =
(u128::from(mdat_length(reader)? * 8) / duration_millis) as u32;
}
}
}
}
}
}
Ok(properties)
}
pub(crate) const SAMPLE_RATES: [u32; 15] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0,
];
fn mp4a_properties<R>(stsd: &mut AtomReader<R>, properties: &mut Mp4Properties) -> Result<()>
where
R: Read + Seek,
{
const ELEMENTARY_DESCRIPTOR_TAG: u8 = 0x03;
const DECODER_CONFIG_TAG: u8 = 0x04;
const DECODER_SPECIFIC_DESCRIPTOR_TAG: u8 = 0x05;
properties.codec = Mp4Codec::AAC;
stsd.seek(SeekFrom::Current(16))?;
properties.channels = stsd.read_u16()? as u8;
stsd.seek(SeekFrom::Current(4))?;
properties.sample_rate = stsd.read_u32()?;
stsd.seek(SeekFrom::Current(2))?;
if let Ok(esds) = stsd.next() {
if esds.ident == AtomIdent::Fourcc(*b"esds") && stsd.read_u32()? == 0 {
let descriptor = Descriptor::read(stsd)?;
if descriptor.tag == ELEMENTARY_DESCRIPTOR_TAG {
stsd.seek(SeekFrom::Current(3))?;
let descriptor = Descriptor::read(stsd)?;
if descriptor.tag == DECODER_CONFIG_TAG {
let codec = stsd.read_u8()?;
properties.codec = match codec {
0x40 | 0x41 | 0x66 | 0x67 | 0x68 => Mp4Codec::AAC,
0x69 | 0x6B => Mp4Codec::MP3,
_ => Mp4Codec::Unknown,
};
stsd.seek(SeekFrom::Current(8))?;
let average_bitrate = stsd.read_u32()?;
let descriptor = Descriptor::read(stsd)?;
if descriptor.tag == DECODER_SPECIFIC_DESCRIPTOR_TAG {
let byte_a = stsd.read_u8()?;
let byte_b = stsd.read_u8()?;
let mut object_type = byte_a >> 3;
let mut frequency_index = ((byte_a & 0x07) << 1) | (byte_b >> 7);
let mut channel_conf = (byte_b >> 3) & 0x0F;
let mut extended_object_type = false;
if object_type == 31 {
extended_object_type = true;
object_type = 32 + ((byte_a & 7) | (byte_b >> 5));
frequency_index = (byte_b >> 1) & 0x0F;
}
properties.extended_audio_object_type =
Some(AudioObjectType::try_from(object_type)?);
match frequency_index {
0x0F => {
let sample_rate;
let explicit_sample_rate = stsd.read_u24::<BigEndian>()?;
if extended_object_type {
sample_rate = explicit_sample_rate >> 1;
channel_conf = ((explicit_sample_rate >> 4) & 0x0F) as u8;
} else {
sample_rate = explicit_sample_rate << 1;
let byte_c = stsd.read_u8()?;
channel_conf = ((explicit_sample_rate & 0x80) as u8
| (byte_c >> 1)) & 0x0F;
}
if sample_rate > 0 {
properties.sample_rate = sample_rate;
}
},
i if i < SAMPLE_RATES.len() as u8 => {
properties.sample_rate = SAMPLE_RATES[i as usize];
if extended_object_type {
let byte_c = stsd.read_u8()?;
channel_conf = (byte_b & 1) | (byte_c & 0xE0);
} else {
channel_conf = (byte_b >> 3) & 0x0F;
}
},
_ => {},
}
if channel_conf > 0 {
properties.channels = channel_conf;
}
if object_type == 36 {
let mut ident = [0; 5];
stsd.read_exact(&mut ident)?;
if &ident == b"\0ALS\0" {
properties.sample_rate = stsd.read_u32()?;
stsd.seek(SeekFrom::Current(4))?;
properties.channels = stsd.read_u16()? as u8 + 1;
}
}
}
if average_bitrate > 0 || properties.duration.is_zero() {
properties.audio_bitrate = average_bitrate / 1000;
}
}
}
}
}
Ok(())
}
fn alac_properties<R>(stsd: &mut AtomReader<R>, properties: &mut Mp4Properties) -> Result<()>
where
R: Read + Seek,
{
if stsd.seek(SeekFrom::End(0))? != 80 {
return Ok(());
}
stsd.seek(SeekFrom::Start(44))?;
if let Ok(alac) = stsd.next() {
if alac.ident == AtomIdent::Fourcc(*b"alac") {
properties.codec = Mp4Codec::ALAC;
stsd.seek(SeekFrom::Current(9))?;
let sample_size = stsd.read_u8()?;
properties.bit_depth = Some(sample_size);
stsd.seek(SeekFrom::Current(3))?;
properties.channels = stsd.read_u8()?;
stsd.seek(SeekFrom::Current(6))?;
properties.audio_bitrate = stsd.read_u32()? / 1000;
properties.sample_rate = stsd.read_u32()?;
}
}
Ok(())
}
fn flac_properties<R>(stsd: &mut AtomReader<R>, properties: &mut Mp4Properties) -> Result<()>
where
R: Read + Seek,
{
properties.codec = Mp4Codec::FLAC;
stsd.seek(SeekFrom::Current(16))?;
properties.channels = stsd.read_u16()? as u8;
properties.bit_depth = Some(stsd.read_u16()? as u8);
stsd.seek(SeekFrom::Current(4))?;
properties.sample_rate = u32::from(stsd.read_u16()?);
let _reserved = stsd.read_u16()?;
let dfla_atom = stsd.next()?;
match dfla_atom.ident {
AtomIdent::Fourcc(ref fourcc) if fourcc == b"dfla" => {},
_ => return Ok(()),
}
stsd.seek(SeekFrom::Current(4))?;
if dfla_atom.len - 12 < 18 {
return Ok(());
}
let stream_info_block = crate::flac::block::Block::read(stsd)?;
let flac_properties =
crate::flac::properties::read_properties(&mut &stream_info_block.content[..], 0, 0)?;
properties.sample_rate = flac_properties.sample_rate;
properties.bit_depth = Some(flac_properties.bit_depth);
properties.channels = flac_properties.channels;
Ok(())
}
fn mdat_length<R>(reader: &mut AtomReader<R>) -> Result<u64>
where
R: Read + Seek,
{
reader.rewind()?;
while let Ok(atom) = reader.next() {
if atom.ident == AtomIdent::Fourcc(*b"mdat") {
return Ok(atom.len);
}
skip_unneeded(reader, atom.extended, atom.len)?;
}
decode_err!(@BAIL MP4, "Failed to find \"mdat\" atom");
}
struct Descriptor {
tag: u8,
_size: u32,
}
impl Descriptor {
fn read<R: Read>(reader: &mut R) -> Result<Descriptor> {
let tag = reader.read_u8()?;
let mut size: u32 = 0;
for _ in 0..4 {
let b = reader.read_u8()?;
size = (size << 7) | u32::from(b & 0x7F);
if b & 0x80 == 0 {
break;
}
}
Ok(Descriptor { tag, _size: size })
}
}