symphonia-format-isomp4-0.5.4/.cargo_vcs_info.json0000644000000001650000000000100155520ustar { "git": { "sha1": "d3b7742fa73674b70d9ab80cc5f8384cc653df3a" }, "path_in_vcs": "symphonia-format-isomp4" }symphonia-format-isomp4-0.5.4/Cargo.toml0000644000000023430000000000100135500ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.53" name = "symphonia-format-isomp4" version = "0.5.4" authors = ["Philip Deljanov "] description = "Pure Rust ISO/MP4 demuxer (a part of project Symphonia)." homepage = "https://github.com/pdeljanov/Symphonia" readme = "README.md" keywords = [ "audio", "media", "demuxer", "mp4", "iso", ] categories = [ "multimedia", "multimedia::audio", "multimedia::encoding", ] license = "MPL-2.0" repository = "https://github.com/pdeljanov/Symphonia" [dependencies.encoding_rs] version = "0.8.17" [dependencies.log] version = "0.4" [dependencies.symphonia-core] version = "0.5.4" [dependencies.symphonia-metadata] version = "0.5.4" [dependencies.symphonia-utils-xiph] version = "0.5.4" symphonia-format-isomp4-0.5.4/Cargo.toml.orig000064400000000000000000000014111046102023000172240ustar 00000000000000[package] name = "symphonia-format-isomp4" version = "0.5.4" description = "Pure Rust ISO/MP4 demuxer (a part of project Symphonia)." homepage = "https://github.com/pdeljanov/Symphonia" repository = "https://github.com/pdeljanov/Symphonia" authors = ["Philip Deljanov "] license = "MPL-2.0" readme = "README.md" categories = ["multimedia", "multimedia::audio", "multimedia::encoding"] keywords = ["audio", "media", "demuxer", "mp4", "iso"] edition = "2018" rust-version = "1.53" [dependencies] encoding_rs = "0.8.17" log = "0.4" symphonia-core = { version = "0.5.4", path = "../symphonia-core" } symphonia-metadata = { version = "0.5.4", path = "../symphonia-metadata" } symphonia-utils-xiph = { version = "0.5.4", path = "../symphonia-utils-xiph" }symphonia-format-isomp4-0.5.4/README.md000064400000000000000000000012201046102023000156120ustar 00000000000000# Symphonia ISO/MP4 Demuxer [![Docs](https://docs.rs/symphonia-format-isomp4/badge.svg)](https://docs.rs/symphonia-format-isomp4) ISO/MP4 demuxer for Project Symphonia. **Note:** This crate is part of Project Symphonia. Please use the [`symphonia`](https://crates.io/crates/symphonia) crate instead of this one directly. ## License Symphonia is provided under the MPL v2.0 license. Please refer to the LICENSE file for more details. ## Contributing Symphonia is a free and open-source project that welcomes contributions! To get started, please read our [Contribution Guidelines](https://github.com/pdeljanov/Symphonia/tree/master/CONTRIBUTING.md). symphonia-format-isomp4-0.5.4/src/atoms/alac.rs000064400000000000000000000035451046102023000175270ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::codecs::{CodecParameters, CODEC_TYPE_ALAC}; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub struct AlacAtom { /// Atom header. header: AtomHeader, /// ALAC extra data (magic cookie). extra_data: Box<[u8]>, } impl Atom for AlacAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, flags) = AtomHeader::read_extra(reader)?; if version != 0 { return unsupported_error("isomp4 (alac): unsupported alac version"); } if flags != 0 { return decode_error("isomp4 (alac): flags not zero"); } if header.data_len <= AtomHeader::EXTRA_DATA_SIZE { return decode_error("isomp4 (alac): invalid alac atom length"); } // The ALAC magic cookie (aka extra data) is either 24 or 48 bytes long. let magic_len = match header.data_len - AtomHeader::EXTRA_DATA_SIZE { len @ 24 | len @ 48 => len as usize, _ => return decode_error("isomp4 (alac): invalid magic cookie length"), }; // Read the magic cookie. let extra_data = reader.read_boxed_slice_exact(magic_len)?; Ok(AlacAtom { header, extra_data }) } } impl AlacAtom { pub fn fill_codec_params(&self, codec_params: &mut CodecParameters) { codec_params.for_codec(CODEC_TYPE_ALAC).with_extra_data(self.extra_data.clone()); } } symphonia-format-isomp4-0.5.4/src/atoms/co64.rs000064400000000000000000000021001046102023000173640ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; /// Chunk offset atom (64-bit version). #[derive(Debug)] pub struct Co64Atom { /// Atom header. header: AtomHeader, pub chunk_offsets: Vec, } impl Atom for Co64Atom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let entry_count = reader.read_be_u32()?; // TODO: Apply a limit. let mut chunk_offsets = Vec::with_capacity(entry_count as usize); for _ in 0..entry_count { chunk_offsets.push(reader.read_be_u64()?); } Ok(Co64Atom { header, chunk_offsets }) } } symphonia-format-isomp4-0.5.4/src/atoms/ctts.rs000064400000000000000000000012751046102023000176020ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; /// Composition time atom. #[derive(Debug)] pub struct CttsAtom { /// Atom header. header: AtomHeader, } impl Atom for CttsAtom { fn header(&self) -> AtomHeader { self.header } fn read(_reader: &mut B, _header: AtomHeader) -> Result { todo!() } } symphonia-format-isomp4-0.5.4/src/atoms/edts.rs000064400000000000000000000021541046102023000175610ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, ElstAtom}; /// Edits atom. #[derive(Debug)] pub struct EdtsAtom { header: AtomHeader, pub elst: Option, } impl Atom for EdtsAtom { fn header(&self) -> AtomHeader { self.header } #[allow(clippy::single_match)] fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut elst = None; while let Some(header) = iter.next()? { match header.atype { AtomType::EditList => { elst = Some(iter.read_atom::()?); } _ => (), } } Ok(EdtsAtom { header, elst }) } } symphonia-format-isomp4-0.5.4/src/atoms/elst.rs000064400000000000000000000040321046102023000175660ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use symphonia_core::util::bits; use crate::atoms::{Atom, AtomHeader}; /// Edit list entry. #[derive(Debug)] #[allow(dead_code)] pub struct ElstEntry { segment_duration: u64, media_time: i64, media_rate_int: i16, media_rate_frac: i16, } /// Edit list atom. #[derive(Debug)] #[allow(dead_code)] pub struct ElstAtom { header: AtomHeader, entries: Vec, } impl Atom for ElstAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, _) = AtomHeader::read_extra(reader)?; // TODO: Apply a limit. let entry_count = reader.read_be_u32()?; let mut entries = Vec::new(); for _ in 0..entry_count { let (segment_duration, media_time) = match version { 0 => ( u64::from(reader.read_be_u32()?), i64::from(bits::sign_extend_leq32_to_i32(reader.read_be_u32()?, 32)), ), 1 => ( reader.read_be_u64()?, bits::sign_extend_leq64_to_i64(reader.read_be_u64()?, 64), ), _ => return decode_error("isomp4: invalid tkhd version"), }; let media_rate_int = bits::sign_extend_leq16_to_i16(reader.read_be_u16()?, 16); let media_rate_frac = bits::sign_extend_leq16_to_i16(reader.read_be_u16()?, 16); entries.push(ElstEntry { segment_duration, media_time, media_rate_int, media_rate_frac, }); } Ok(ElstAtom { header, entries }) } } symphonia-format-isomp4-0.5.4/src/atoms/esds.rs000064400000000000000000000247571046102023000175750ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::codecs::{ CodecParameters, CodecType, CODEC_TYPE_AAC, CODEC_TYPE_MP3, CODEC_TYPE_NULL, }; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::{FiniteStream, ReadBytes, ScopedStream}; use crate::atoms::{Atom, AtomHeader}; use log::{debug, warn}; const ES_DESCRIPTOR: u8 = 0x03; const DECODER_CONFIG_DESCRIPTOR: u8 = 0x04; const DECODER_SPECIFIC_DESCRIPTOR: u8 = 0x05; const SL_CONFIG_DESCRIPTOR: u8 = 0x06; const MIN_DESCRIPTOR_SIZE: u64 = 2; fn read_descriptor_header(reader: &mut B) -> Result<(u8, u32)> { let tag = reader.read_u8()?; let mut size = 0; for _ in 0..4 { let val = reader.read_u8()?; size = (size << 7) | u32::from(val & 0x7f); if val & 0x80 == 0 { break; } } Ok((tag, size)) } #[derive(Debug)] pub struct EsdsAtom { /// Atom header. header: AtomHeader, /// Elementary stream descriptor. descriptor: ESDescriptor, } impl Atom for EsdsAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let mut descriptor = None; let mut scoped = ScopedStream::new(reader, header.data_len - AtomHeader::EXTRA_DATA_SIZE); while scoped.bytes_available() > MIN_DESCRIPTOR_SIZE { let (desc, desc_len) = read_descriptor_header(&mut scoped)?; match desc { ES_DESCRIPTOR => { descriptor = Some(ESDescriptor::read(&mut scoped, desc_len)?); } _ => { warn!("unknown descriptor in esds atom, desc={}", desc); scoped.ignore_bytes(desc_len as u64)?; } } } // Ignore remainder of the atom. scoped.ignore()?; Ok(EsdsAtom { header, descriptor: descriptor.unwrap() }) } } impl EsdsAtom { pub fn fill_codec_params(&self, codec_params: &mut CodecParameters) { codec_params.for_codec(self.descriptor.dec_config.codec_type); if let Some(ds_config) = &self.descriptor.dec_config.dec_specific_info { codec_params.with_extra_data(ds_config.extra_data.clone()); } } } pub trait ObjectDescriptor: Sized { fn read(reader: &mut B, len: u32) -> Result; } /* class ES_Descriptor extends BaseDescriptor : bit(8) tag=ES_DescrTag { bit(16) ES_ID; bit(1) streamDependenceFlag; bit(1) URL_Flag; bit(1) OCRstreamFlag; bit(5) streamPriority; if (streamDependenceFlag) bit(16) dependsOn_ES_ID; if (URL_Flag) { bit(8) URLlength; bit(8) URLstring[URLlength]; } if (OCRstreamFlag) bit(16) OCR_ES_Id; DecoderConfigDescriptor decConfigDescr; SLConfigDescriptor slConfigDescr; IPI_DescrPointer ipiPtr[0 .. 1]; IP_IdentificationDataSet ipIDS[0 .. 255]; IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; LanguageDescriptor langDescr[0 .. 255]; QoS_Descriptor qosDescr[0 .. 1]; RegistrationDescriptor regDescr[0 .. 1]; ExtensionDescriptor extDescr[0 .. 255]; } */ #[derive(Debug)] pub struct ESDescriptor { pub es_id: u16, pub dec_config: DecoderConfigDescriptor, pub sl_config: SLDescriptor, } impl ObjectDescriptor for ESDescriptor { fn read(reader: &mut B, len: u32) -> Result { let es_id = reader.read_be_u16()?; let es_flags = reader.read_u8()?; // Stream dependence flag. if es_flags & 0x80 != 0 { let _depends_on_es_id = reader.read_u16()?; } // URL flag. if es_flags & 0x40 != 0 { let url_len = reader.read_u8()?; reader.ignore_bytes(u64::from(url_len))?; } // OCR stream flag. if es_flags & 0x20 != 0 { let _ocr_es_id = reader.read_u16()?; } let mut dec_config = None; let mut sl_config = None; let mut scoped = ScopedStream::new(reader, u64::from(len) - 3); // Multiple descriptors follow, but only the decoder configuration descriptor is useful. while scoped.bytes_available() > MIN_DESCRIPTOR_SIZE { let (desc, desc_len) = read_descriptor_header(&mut scoped)?; match desc { DECODER_CONFIG_DESCRIPTOR => { dec_config = Some(DecoderConfigDescriptor::read(&mut scoped, desc_len)?); } SL_CONFIG_DESCRIPTOR => { sl_config = Some(SLDescriptor::read(&mut scoped, desc_len)?); } _ => { debug!("skipping {} object in es descriptor", desc); scoped.ignore_bytes(u64::from(desc_len))?; } } } // Consume remaining bytes. scoped.ignore()?; // Decoder configuration descriptor is mandatory. if dec_config.is_none() { return decode_error("isomp4: missing decoder config descriptor"); } // SL descriptor is mandatory. if sl_config.is_none() { return decode_error("isomp4: missing sl config descriptor"); } Ok(ESDescriptor { es_id, dec_config: dec_config.unwrap(), sl_config: sl_config.unwrap() }) } } /* class DecoderConfigDescriptor extends BaseDescriptor : bit(8) tag=DecoderConfigDescrTag { bit(8) objectTypeIndication; bit(6) streamType; bit(1) upStream; const bit(1) reserved=1; bit(24) bufferSizeDB; bit(32) maxBitrate; bit(32) avgBitrate; DecoderSpecificInfo decSpecificInfo[0 .. 1]; profileLevelIndicationIndexDescriptor profileLevelIndicationIndexDescr [0..255]; } */ #[derive(Debug)] pub struct DecoderConfigDescriptor { pub codec_type: CodecType, pub object_type_indication: u8, pub dec_specific_info: Option, } impl ObjectDescriptor for DecoderConfigDescriptor { fn read(reader: &mut B, len: u32) -> Result { // AAC const OBJECT_TYPE_ISO14496_3: u8 = 0x40; const OBJECT_TYPE_ISO13818_7_MAIN: u8 = 0x66; const OBJECT_TYPE_ISO13818_7_LC: u8 = 0x67; // MP3 const OBJECT_TYPE_ISO13818_3: u8 = 0x69; const OBJECT_TYPE_ISO11172_3: u8 = 0x6b; let object_type_indication = reader.read_u8()?; let (_stream_type, _upstream) = { let val = reader.read_u8()?; if val & 0x1 != 1 { debug!("decoder config descriptor reserved bit is not 1"); } ((val & 0xfc) >> 2, (val & 0x2) >> 1) }; let _buffer_size = reader.read_be_u24()?; let _max_bitrate = reader.read_be_u32()?; let _avg_bitrate = reader.read_be_u32()?; let mut dec_specific_config = None; let mut scoped = ScopedStream::new(reader, u64::from(len) - 13); // Multiple descriptors follow, but only the decoder specific info descriptor is useful. while scoped.bytes_available() > MIN_DESCRIPTOR_SIZE { let (desc, desc_len) = read_descriptor_header(&mut scoped)?; match desc { DECODER_SPECIFIC_DESCRIPTOR => { dec_specific_config = Some(DecoderSpecificInfo::read(&mut scoped, desc_len)?); } _ => { debug!("skipping {} object in decoder config descriptor", desc); scoped.ignore_bytes(u64::from(desc_len))?; } } } let codec_type = match object_type_indication { OBJECT_TYPE_ISO14496_3 | OBJECT_TYPE_ISO13818_7_LC | OBJECT_TYPE_ISO13818_7_MAIN => { CODEC_TYPE_AAC } OBJECT_TYPE_ISO13818_3 | OBJECT_TYPE_ISO11172_3 => CODEC_TYPE_MP3, _ => { debug!( "unknown object type indication {:#x} for decoder config descriptor", object_type_indication ); CODEC_TYPE_NULL } }; // Consume remaining bytes. scoped.ignore()?; Ok(DecoderConfigDescriptor { codec_type, object_type_indication, dec_specific_info: dec_specific_config, }) } } #[derive(Debug)] pub struct DecoderSpecificInfo { pub extra_data: Box<[u8]>, } impl ObjectDescriptor for DecoderSpecificInfo { fn read(reader: &mut B, len: u32) -> Result { Ok(DecoderSpecificInfo { extra_data: reader.read_boxed_slice_exact(len as usize)? }) } } /* class SLConfigDescriptor extends BaseDescriptor : bit(8) tag=SLConfigDescrTag { bit(8) predefined; if (predefined==0) { bit(1) useAccessUnitStartFlag; bit(1) useAccessUnitEndFlag; bit(1) useRandomAccessPointFlag; bit(1) hasRandomAccessUnitsOnlyFlag; bit(1) usePaddingFlag; bit(1) useTimeStampsFlag; bit(1) useIdleFlag; bit(1) durationFlag; bit(32) timeStampResolution; bit(32) OCRResolution; bit(8) timeStampLength; // must be 64 bit(8) OCRLength; // must be 64 bit(8) AU_Length; // must be 32 bit(8) instantBitrateLength; bit(4) degradationPriorityLength; bit(5) AU_seqNumLength; // must be 16 bit(5) packetSeqNumLength; // must be 16 bit(2) reserved=0b11; } if (durationFlag) { bit(32) timeScale; bit(16) accessUnitDuration; bit(16) compositionUnitDuration; } if (!useTimeStampsFlag) { bit(timeStampLength) startDecodingTimeStamp; bit(timeStampLength) startCompositionTimeStamp; } } timeStampLength == 32, for predefined == 0x1 timeStampLength == 0, for predefined == 0x2 */ #[derive(Debug)] pub struct SLDescriptor; impl ObjectDescriptor for SLDescriptor { fn read(reader: &mut B, _len: u32) -> Result { // const SLCONFIG_PREDEFINED_CUSTOM: u8 = 0x0; // const SLCONFIG_PREDEFINED_NULL: u8 = 0x1; const SLCONFIG_PREDEFINED_MP4: u8 = 0x2; let predefined = reader.read_u8()?; if predefined != SLCONFIG_PREDEFINED_MP4 { return unsupported_error("isomp4: sl descriptor predefined not mp4"); } Ok(SLDescriptor {}) } } symphonia-format-isomp4-0.5.4/src/atoms/flac.rs000064400000000000000000000051631046102023000175320ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::codecs::{CodecParameters, VerificationCheck, CODEC_TYPE_FLAC}; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::{BufReader, ReadBytes}; use symphonia_utils_xiph::flac::metadata::{MetadataBlockHeader, MetadataBlockType, StreamInfo}; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub struct FlacAtom { /// Atom header. header: AtomHeader, /// FLAC stream info block. stream_info: StreamInfo, /// FLAC extra data. extra_data: Box<[u8]>, } impl Atom for FlacAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, flags) = AtomHeader::read_extra(reader)?; if version != 0 { return unsupported_error("isomp4 (flac): unsupported flac version"); } if flags != 0 { return decode_error("isomp4 (flac): flags not zero"); } // The first block must be the stream information block. let block_header = MetadataBlockHeader::read(reader)?; if block_header.block_type != MetadataBlockType::StreamInfo { return decode_error("isomp4 (flac): first block is not stream info"); } // Ensure the block length is correct for a stream information block before allocating a // buffer for it. if !StreamInfo::is_valid_size(u64::from(block_header.block_len)) { return decode_error("isomp4 (flac): invalid stream info block length"); } let extra_data = reader.read_boxed_slice_exact(block_header.block_len as usize)?; let stream_info = StreamInfo::read(&mut BufReader::new(&extra_data))?; Ok(FlacAtom { header, stream_info, extra_data }) } } impl FlacAtom { pub fn fill_codec_params(&self, codec_params: &mut CodecParameters) { codec_params .for_codec(CODEC_TYPE_FLAC) .with_sample_rate(self.stream_info.sample_rate) .with_bits_per_sample(self.stream_info.bits_per_sample) .with_channels(self.stream_info.channels) .with_packet_data_integrity(true) .with_extra_data(self.extra_data.clone()); if let Some(md5) = self.stream_info.md5 { codec_params.with_verification_code(VerificationCheck::Md5(md5)); } } } symphonia-format-isomp4-0.5.4/src/atoms/ftyp.rs000064400000000000000000000030551046102023000176050ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; use crate::fourcc::FourCc; /// File type atom. #[derive(Debug)] pub struct FtypAtom { header: AtomHeader, pub major: FourCc, pub minor: [u8; 4], pub compatible: Vec, } impl Atom for FtypAtom { fn read(reader: &mut B, header: AtomHeader) -> Result { // The Ftyp atom must be have a data length that is known, and it must be a multiple of 4 // since it only stores FourCCs. if header.data_len < 8 || header.data_len & 0x3 != 0 { return decode_error("isomp4: invalid ftyp data length"); } // Major let major = FourCc::new(reader.read_quad_bytes()?); // Minor let minor = reader.read_quad_bytes()?; // The remainder of the Ftyp atom contains the FourCCs of compatible brands. let n_brands = (header.data_len - 8) / 4; let mut compatible = Vec::new(); for _ in 0..n_brands { let brand = reader.read_quad_bytes()?; compatible.push(FourCc::new(brand)); } Ok(FtypAtom { header, major, minor, compatible }) } fn header(&self) -> AtomHeader { self.header } } symphonia-format-isomp4-0.5.4/src/atoms/hdlr.rs000064400000000000000000000042241046102023000175530ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::{ atoms::{Atom, AtomHeader}, fourcc::FourCc, }; use log::warn; /// Handler type. #[derive(Debug, PartialEq, Eq)] pub enum HandlerType { /// Video handler. Video, /// Audio handler. Sound, /// Subtitle handler. Subtitle, /// Metadata handler. Metadata, /// Text handler. Text, /// Unknown handler type. Other([u8; 4]), } /// Handler atom. #[derive(Debug)] pub struct HdlrAtom { /// Atom header. header: AtomHeader, /// Handler type. pub handler_type: HandlerType, /// Human-readable handler name. pub name: String, } impl Atom for HdlrAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; // Always 0 for MP4, but for Quicktime this contains the component type. let _ = reader.read_quad_bytes()?; let handler_type = match &reader.read_quad_bytes()? { b"vide" => HandlerType::Video, b"soun" => HandlerType::Sound, b"meta" => HandlerType::Metadata, b"subt" => HandlerType::Subtitle, b"text" => HandlerType::Text, &hdlr => { warn!("unknown handler type {:?}", FourCc::new(hdlr)); HandlerType::Other(hdlr) } }; // These bytes are reserved for MP4, but for QuickTime they contain the component // manufacturer, flags, and flags mask. reader.ignore_bytes(4 * 3)?; // Human readable UTF-8 string of the track type. let buf = reader.read_boxed_slice_exact((header.data_len - 24) as usize)?; let name = String::from_utf8_lossy(&buf).to_string(); Ok(HdlrAtom { header, handler_type, name }) } } symphonia-format-isomp4-0.5.4/src/atoms/ilst.rs000064400000000000000000000604671046102023000176100ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::{BufReader, ReadBytes}; use symphonia_core::meta::{ MetadataBuilder, MetadataRevision, StandardTagKey, StandardVisualKey, Tag, }; use symphonia_core::meta::{Value, Visual}; use symphonia_core::util::bits; use symphonia_metadata::{id3v1, itunes}; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType}; use encoding_rs::{SHIFT_JIS, UTF_16BE}; use log::warn; /// Data type enumeration for metadata value atoms as defined in the QuickTime File Format standard. #[derive(Debug, Copy, Clone)] pub enum DataType { AffineTransformF64, Bmp, DimensionsF32, Float32, Float64, Jpeg, /// The data type is implicit to the atom. NoType, Png, PointF32, QuickTimeMetadata, RectF32, ShiftJis, SignedInt16, SignedInt32, SignedInt64, SignedInt8, SignedIntVariable, UnsignedInt16, UnsignedInt32, UnsignedInt64, UnsignedInt8, UnsignedIntVariable, Utf16, Utf16Sort, Utf8, Utf8Sort, Unknown(u32), } impl From for DataType { fn from(value: u32) -> Self { match value { 0 => DataType::NoType, 1 => DataType::Utf8, 2 => DataType::Utf16, 3 => DataType::ShiftJis, 4 => DataType::Utf8Sort, 5 => DataType::Utf16Sort, 13 => DataType::Jpeg, 14 => DataType::Png, 21 => DataType::SignedIntVariable, 22 => DataType::UnsignedIntVariable, 23 => DataType::Float32, 24 => DataType::Float64, 27 => DataType::Bmp, 28 => DataType::QuickTimeMetadata, 65 => DataType::SignedInt8, 66 => DataType::SignedInt16, 67 => DataType::SignedInt32, 70 => DataType::PointF32, 71 => DataType::DimensionsF32, 72 => DataType::RectF32, 74 => DataType::SignedInt64, 75 => DataType::UnsignedInt8, 76 => DataType::UnsignedInt16, 77 => DataType::UnsignedInt32, 78 => DataType::UnsignedInt64, 79 => DataType::AffineTransformF64, _ => DataType::Unknown(value), } } } fn parse_no_type(data: &[u8]) -> Option { // Latin1, potentially null-terminated. let end = data.iter().position(|&c| c == b'\0').unwrap_or(data.len()); let text = String::from_utf8_lossy(&data[..end]); Some(Value::from(text)) } fn parse_utf8(data: &[u8]) -> Option { // UTF8, no null-terminator or count. let text = String::from_utf8_lossy(data); Some(Value::from(text)) } fn parse_utf16(data: &[u8]) -> Option { // UTF16 BE let text = UTF_16BE.decode(data).0; Some(Value::from(text)) } fn parse_shift_jis(data: &[u8]) -> Option { // Shift-JIS let text = SHIFT_JIS.decode(data).0; Some(Value::from(text)) } fn parse_signed_int8(data: &[u8]) -> Option { match data.len() { 1 => { let s = bits::sign_extend_leq8_to_i8(data[0], 8); Some(Value::from(s)) } _ => None, } } fn parse_signed_int16(data: &[u8]) -> Option { match data.len() { 2 => { let u = BufReader::new(data).read_be_u16().ok()?; let s = bits::sign_extend_leq16_to_i16(u, 16); Some(Value::from(s)) } _ => None, } } fn parse_signed_int32(data: &[u8]) -> Option { match data.len() { 4 => { let u = BufReader::new(data).read_be_u32().ok()?; let s = bits::sign_extend_leq32_to_i32(u, 32); Some(Value::from(s)) } _ => None, } } fn parse_signed_int64(data: &[u8]) -> Option { match data.len() { 8 => { let u = BufReader::new(data).read_be_u64().ok()?; let s = bits::sign_extend_leq64_to_i64(u, 64); Some(Value::from(s)) } _ => None, } } fn parse_var_signed_int(data: &[u8]) -> Option { match data.len() { 1 => parse_signed_int8(data), 2 => parse_signed_int16(data), 4 => parse_signed_int32(data), _ => None, } } fn parse_unsigned_int8(data: &[u8]) -> Option { match data.len() { 1 => Some(Value::from(data[0])), _ => None, } } fn parse_unsigned_int16(data: &[u8]) -> Option { match data.len() { 2 => { let u = BufReader::new(data).read_be_u16().ok()?; Some(Value::from(u)) } _ => None, } } fn parse_unsigned_int32(data: &[u8]) -> Option { match data.len() { 4 => { let u = BufReader::new(data).read_be_u32().ok()?; Some(Value::from(u)) } _ => None, } } fn parse_unsigned_int64(data: &[u8]) -> Option { match data.len() { 8 => { let u = BufReader::new(data).read_be_u64().ok()?; Some(Value::from(u)) } _ => None, } } fn parse_var_unsigned_int(data: &[u8]) -> Option { match data.len() { 1 => parse_unsigned_int8(data), 2 => parse_unsigned_int16(data), 4 => parse_unsigned_int32(data), _ => None, } } fn parse_float32(data: &[u8]) -> Option { match data.len() { 4 => { let f = BufReader::new(data).read_be_f32().ok()?; Some(Value::Float(f64::from(f))) } _ => None, } } fn parse_float64(data: &[u8]) -> Option { match data.len() { 8 => { let f = BufReader::new(data).read_be_f64().ok()?; Some(Value::Float(f)) } _ => None, } } fn parse_tag_value(data_type: DataType, data: &[u8]) -> Option { match data_type { DataType::NoType => parse_no_type(data), DataType::Utf8 | DataType::Utf8Sort => parse_utf8(data), DataType::Utf16 | DataType::Utf16Sort => parse_utf16(data), DataType::ShiftJis => parse_shift_jis(data), DataType::UnsignedInt8 => parse_unsigned_int8(data), DataType::UnsignedInt16 => parse_unsigned_int16(data), DataType::UnsignedInt32 => parse_unsigned_int32(data), DataType::UnsignedInt64 => parse_unsigned_int64(data), DataType::UnsignedIntVariable => parse_var_unsigned_int(data), DataType::SignedInt8 => parse_signed_int8(data), DataType::SignedInt16 => parse_signed_int16(data), DataType::SignedInt32 => parse_signed_int32(data), DataType::SignedInt64 => parse_signed_int64(data), DataType::SignedIntVariable => parse_var_signed_int(data), DataType::Float32 => parse_float32(data), DataType::Float64 => parse_float64(data), _ => None, } } /// Reads and parses a `MetaTagAtom` from the provided iterator and adds it to the `MetadataBuilder` /// if there are no errors. fn add_generic_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, std_key: Option, ) -> Result<()> { let tag = iter.read_atom::()?; for value_atom in tag.values.iter() { // Parse the value atom data into a string, if possible. if let Some(value) = parse_tag_value(value_atom.data_type, &value_atom.data) { builder.add_tag(Tag::new(std_key, "", value)); } else { warn!("unsupported data type {:?} for {:?} tag", value_atom.data_type, std_key); } } Ok(()) } fn add_var_unsigned_int_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, std_key: StandardTagKey, ) -> Result<()> { let tag = iter.read_atom::()?; if let Some(value_atom) = tag.values.first() { if let Some(value) = parse_var_unsigned_int(&value_atom.data) { builder.add_tag(Tag::new(Some(std_key), "", value)); } else { warn!("got unexpected data for {:?} tag", std_key); } } Ok(()) } fn add_var_signed_int_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, std_key: StandardTagKey, ) -> Result<()> { let tag = iter.read_atom::()?; if let Some(value_atom) = tag.values.first() { if let Some(value) = parse_var_signed_int(&value_atom.data) { builder.add_tag(Tag::new(Some(std_key), "", value)); } else { warn!("got unexpected data for {:?} tag", std_key); } } Ok(()) } fn add_boolean_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, std_key: StandardTagKey, ) -> Result<()> { let tag = iter.read_atom::()?; // There should only be 1 value. if let Some(value) = tag.values.first() { // Boolean tags are just "flags", only add a tag if the boolean is true (1). if let Some(bool_value) = value.data.first() { if *bool_value == 1 { builder.add_tag(Tag::new(Some(std_key), "", Value::Flag)); } } } Ok(()) } fn add_m_of_n_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, m_key: StandardTagKey, n_key: StandardTagKey, ) -> Result<()> { let tag = iter.read_atom::()?; // There should only be 1 value. if let Some(value) = tag.values.first() { // The trkn and disk atoms contains an 8 byte value buffer, where the 4th and 6th bytes // indicate the track/disk number and total number of tracks/disks, respectively. Odd. if value.data.len() == 8 { let m = value.data[3]; let n = value.data[5]; builder.add_tag(Tag::new(Some(m_key), "", Value::from(m))); builder.add_tag(Tag::new(Some(n_key), "", Value::from(n))); } } Ok(()) } fn add_visual_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, ) -> Result<()> { let tag = iter.read_atom::()?; // There could be more than one attached image. for value in tag.values { let media_type = match value.data_type { DataType::Bmp => "image/bmp", DataType::Jpeg => "image/jpeg", DataType::Png => "image/png", _ => "", }; builder.add_visual(Visual { media_type: media_type.into(), dimensions: None, bits_per_pixel: None, color_mode: None, usage: Some(StandardVisualKey::FrontCover), tags: Default::default(), data: value.data, }); } Ok(()) } fn add_advisory_tag( _iter: &mut AtomIterator, _builder: &mut MetadataBuilder, ) -> Result<()> { Ok(()) } fn add_media_type_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, ) -> Result<()> { let tag = iter.read_atom::()?; // There should only be 1 value. if let Some(value) = tag.values.first() { if let Some(media_type_value) = value.data.first() { let media_type = match media_type_value { 0 => "Movie", 1 => "Normal", 2 => "Audio Book", 5 => "Whacked Bookmark", 6 => "Music Video", 9 => "Short Film", 10 => "TV Show", 11 => "Booklet", _ => "Unknown", }; builder.add_tag(Tag::new( Some(StandardTagKey::MediaFormat), "", Value::from(media_type), )); } } Ok(()) } fn add_id3v1_genre_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, ) -> Result<()> { let tag = iter.read_atom::()?; // There should only be 1 value. if let Some(value) = tag.values.first() { // The ID3v1 genre is stored as a unsigned 16-bit big-endian integer. let index = BufReader::new(&value.data).read_be_u16()?; // The stored index uses 1-based indexing, but the ID3v1 genre list is 0-based. if index > 0 { if let Some(genre) = id3v1::util::genre_name((index - 1) as u8) { builder.add_tag(Tag::new(Some(StandardTagKey::Genre), "", Value::from(*genre))); } } } Ok(()) } fn add_freeform_tag( iter: &mut AtomIterator, builder: &mut MetadataBuilder, ) -> Result<()> { let tag = iter.read_atom::()?; // A user-defined tag should only have 1 value. for value_atom in tag.values.iter() { // Parse the value atom data into a string, if possible. if let Some(value) = parse_tag_value(value_atom.data_type, &value_atom.data) { // Gets the fully qualified tag name. let full_name = tag.full_name(); // Try to map iTunes freeform tags to standard tag keys. let std_key = itunes::std_key_from_tag(&full_name); builder.add_tag(Tag::new(std_key, &full_name, value)); } else { warn!("unsupported data type {:?} for free-form tag", value_atom.data_type); } } Ok(()) } /// Metadata tag data atom. pub struct MetaTagDataAtom { /// Atom header. header: AtomHeader, /// Tag data. pub data: Box<[u8]>, /// The data type contained in buf. pub data_type: DataType, } impl Atom for MetaTagDataAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, flags) = AtomHeader::read_extra(reader)?; // For the mov brand, this a data type indicator and must always be 0 (well-known type). It // specifies the table in which the next 24-bit integer specifying the actual data type // indexes. For iso/mp4, this is a version, and there is only one version, 0. Therefore, // flags are interpreted as the actual data type index. if version != 0 { return decode_error("isomp4: invalid data atom version"); } let data_type = DataType::from(flags); // For the mov brand, the next four bytes are country and languages code. However, for // iso/mp4 these codes should be ignored. let _country = reader.read_be_u16()?; let _language = reader.read_be_u16()?; // The data payload is the remainder of the atom. // TODO: Apply a limit. let data = reader .read_boxed_slice_exact((header.data_len - AtomHeader::EXTRA_DATA_SIZE - 4) as usize)?; Ok(MetaTagDataAtom { header, data, data_type }) } } /// Metadata tag name and mean atom. pub struct MetaTagNamespaceAtom { /// Atom header. header: AtomHeader, /// For 'mean' atoms, this is the key namespace. For 'name' atom, this is the key name. pub value: String, } impl Atom for MetaTagNamespaceAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let buf = reader .read_boxed_slice_exact((header.data_len - AtomHeader::EXTRA_DATA_SIZE) as usize)?; // Do a lossy conversion because metadata should not prevent the demuxer from working. let value = String::from_utf8_lossy(&buf).to_string(); Ok(MetaTagNamespaceAtom { header, value }) } } /// A generic metadata tag atom. pub struct MetaTagAtom { /// Atom header. header: AtomHeader, /// Tag value(s). pub values: Vec, /// Optional, tag key namespace. pub mean: Option, /// Optional, tag key name. pub name: Option, } impl MetaTagAtom { pub fn full_name(&self) -> String { let mut full_name = String::new(); if self.mean.is_some() || self.name.is_some() { // full_name.push_str("----:"); if let Some(mean) = &self.mean { full_name.push_str(&mean.value); } full_name.push(':'); if let Some(name) = &self.name { full_name.push_str(&name.value); } } full_name } } impl Atom for MetaTagAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut mean = None; let mut name = None; let mut values = Vec::new(); while let Some(header) = iter.next()? { match header.atype { AtomType::MetaTagData => { values.push(iter.read_atom::()?); } AtomType::MetaTagName => { name = Some(iter.read_atom::()?); } AtomType::MetaTagMeaning => { mean = Some(iter.read_atom::()?); } _ => (), } } Ok(MetaTagAtom { header, values, mean, name }) } } /// User data atom. pub struct IlstAtom { /// Atom header. header: AtomHeader, /// Metadata revision. pub metadata: MetadataRevision, } impl Atom for IlstAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut mb = MetadataBuilder::new(); while let Some(header) = iter.next()? { // Ignore standard atoms, check if other is a metadata atom. match &header.atype { AtomType::AdvisoryTag => add_advisory_tag(&mut iter, &mut mb)?, AtomType::AlbumArtistTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::AlbumArtist))? } AtomType::AlbumTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Album))? } AtomType::ArtistLowerTag => (), AtomType::ArtistTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Artist))? } AtomType::CategoryTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::PodcastCategory))? } AtomType::CommentTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Comment))? } AtomType::CompilationTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Compilation))? } AtomType::ComposerTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Composer))? } AtomType::CopyrightTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Copyright))? } AtomType::CoverTag => add_visual_tag(&mut iter, &mut mb)?, AtomType::CustomGenreTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Genre))? } AtomType::DateTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Date))? } AtomType::DescriptionTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Description))? } AtomType::DiskNumberTag => add_m_of_n_tag( &mut iter, &mut mb, StandardTagKey::DiscNumber, StandardTagKey::DiscTotal, )?, AtomType::EncodedByTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::EncodedBy))? } AtomType::EncoderTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Encoder))? } AtomType::GaplessPlaybackTag => { // TODO: Need standard tag key for gapless playback. // add_boolean_tag(&mut iter, &mut mb, )? } AtomType::GenreTag => add_id3v1_genre_tag(&mut iter, &mut mb)?, AtomType::GroupingTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::ContentGroup))? } AtomType::HdVideoTag => (), AtomType::IdentPodcastTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::IdentPodcast))? } AtomType::KeywordTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::PodcastKeywords))? } AtomType::LongDescriptionTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Description))? } AtomType::LyricsTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Lyrics))? } AtomType::MediaTypeTag => add_media_type_tag(&mut iter, &mut mb)?, AtomType::OwnerTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Owner))? } AtomType::PodcastTag => { add_boolean_tag(&mut iter, &mut mb, StandardTagKey::Podcast)? } AtomType::PurchaseDateTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::PurchaseDate))? } AtomType::RatingTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::Rating))? } AtomType::SortAlbumArtistTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::SortAlbumArtist))? } AtomType::SortAlbumTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::SortAlbum))? } AtomType::SortArtistTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::SortArtist))? } AtomType::SortComposerTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::SortComposer))? } AtomType::SortNameTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::SortTrackTitle))? } AtomType::TempoTag => { add_var_signed_int_tag(&mut iter, &mut mb, StandardTagKey::Bpm)? } AtomType::TrackNumberTag => add_m_of_n_tag( &mut iter, &mut mb, StandardTagKey::TrackNumber, StandardTagKey::TrackTotal, )?, AtomType::TrackTitleTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::TrackTitle))? } AtomType::TvEpisodeNameTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::TvEpisodeTitle))? } AtomType::TvEpisodeNumberTag => { add_var_unsigned_int_tag(&mut iter, &mut mb, StandardTagKey::TvEpisode)? } AtomType::TvNetworkNameTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::TvNetwork))? } AtomType::TvSeasonNumberTag => { add_var_unsigned_int_tag(&mut iter, &mut mb, StandardTagKey::TvSeason)? } AtomType::TvShowNameTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::TvShowTitle))? } AtomType::UrlPodcastTag => { add_generic_tag(&mut iter, &mut mb, Some(StandardTagKey::UrlPodcast))? } AtomType::FreeFormTag => add_freeform_tag(&mut iter, &mut mb)?, _ => (), } } Ok(IlstAtom { header, metadata: mb.metadata() }) } } symphonia-format-isomp4-0.5.4/src/atoms/mdhd.rs000064400000000000000000000050521046102023000175360ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; fn parse_language(code: u16) -> String { // An ISO language code outside of these bounds is not valid. if code < 0x400 || code > 0x7fff { String::new() } else { let chars = [ ((code >> 10) & 0x1f) as u8 + 0x60, ((code >> 5) & 0x1f) as u8 + 0x60, ((code >> 0) & 0x1f) as u8 + 0x60, ]; String::from_utf8_lossy(&chars).to_string() } } /// Media header atom. #[derive(Debug)] pub struct MdhdAtom { /// Atom header. header: AtomHeader, /// Creation time. pub ctime: u64, /// Modification time. pub mtime: u64, /// Timescale. pub timescale: u32, /// Duration of the media in timescale units. pub duration: u64, /// Language. pub language: String, } impl Atom for MdhdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, _) = AtomHeader::read_extra(reader)?; let mut mdhd = MdhdAtom { header, ctime: 0, mtime: 0, timescale: 0, duration: 0, language: String::new(), }; match version { 0 => { mdhd.ctime = u64::from(reader.read_be_u32()?); mdhd.mtime = u64::from(reader.read_be_u32()?); mdhd.timescale = reader.read_be_u32()?; // 0xffff_ffff is a special case. mdhd.duration = match reader.read_be_u32()? { std::u32::MAX => std::u64::MAX, duration => u64::from(duration), }; } 1 => { mdhd.ctime = reader.read_be_u64()?; mdhd.mtime = reader.read_be_u64()?; mdhd.timescale = reader.read_be_u32()?; mdhd.duration = reader.read_be_u64()?; } _ => { return decode_error("isomp4: invalid mdhd version"); } } mdhd.language = parse_language(reader.read_be_u16()?); // Quality let _ = reader.read_be_u16()?; Ok(mdhd) } } symphonia-format-isomp4-0.5.4/src/atoms/mdia.rs000064400000000000000000000034361046102023000175400ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, HdlrAtom, MdhdAtom, MinfAtom}; #[derive(Debug)] pub struct MdiaAtom { header: AtomHeader, pub mdhd: MdhdAtom, pub hdlr: HdlrAtom, pub minf: MinfAtom, } impl Atom for MdiaAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut mdhd = None; let mut hdlr = None; let mut minf = None; while let Some(header) = iter.next()? { match header.atype { AtomType::MediaHeader => { mdhd = Some(iter.read_atom::()?); } AtomType::Handler => { hdlr = Some(iter.read_atom::()?); } AtomType::MediaInfo => { minf = Some(iter.read_atom::()?); } _ => (), } } if mdhd.is_none() { return decode_error("isomp4: missing mdhd atom"); } if hdlr.is_none() { return decode_error("isomp4: missing hdlr atom"); } if minf.is_none() { return decode_error("isomp4: missing minf atom"); } Ok(MdiaAtom { header, mdhd: mdhd.unwrap(), hdlr: hdlr.unwrap(), minf: minf.unwrap() }) } } symphonia-format-isomp4-0.5.4/src/atoms/mehd.rs000064400000000000000000000021471046102023000175410ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; /// Movie extends header atom. #[derive(Debug)] pub struct MehdAtom { /// Atom header. header: AtomHeader, /// Fragment duration. pub fragment_duration: u64, } impl Atom for MehdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, _) = AtomHeader::read_extra(reader)?; let fragment_duration = match version { 0 => u64::from(reader.read_be_u32()?), 1 => reader.read_be_u64()?, _ => { return decode_error("isomp4: invalid mehd version"); } }; Ok(MehdAtom { header, fragment_duration }) } } symphonia-format-isomp4-0.5.4/src/atoms/meta.rs000064400000000000000000000035051046102023000175510ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::fmt::Debug; use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use symphonia_core::meta::MetadataRevision; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, IlstAtom}; /// User data atom. pub struct MetaAtom { /// Atom header. header: AtomHeader, /// Metadata revision. pub metadata: Option, } impl Debug for MetaAtom { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(redacted)") } } impl MetaAtom { /// If metadata was read, consumes the metadata and returns it. pub fn take_metadata(&mut self) -> Option { self.metadata.take() } } impl Atom for MetaAtom { fn header(&self) -> AtomHeader { self.header } #[allow(clippy::single_match)] fn read(reader: &mut B, mut header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; // AtomIterator doesn't know the extra data was read already, so the extra data size must be // subtrated from the atom's data length. header.data_len -= AtomHeader::EXTRA_DATA_SIZE; let mut iter = AtomIterator::new(reader, header); let mut metadata = None; while let Some(header) = iter.next()? { match header.atype { AtomType::MetaList => { metadata = Some(iter.read_atom::()?.metadata); } _ => (), } } Ok(MetaAtom { header, metadata }) } } symphonia-format-isomp4-0.5.4/src/atoms/mfhd.rs000064400000000000000000000016361046102023000175440ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; /// Movie fragment header atom. #[derive(Debug)] pub struct MfhdAtom { /// Atom header. header: AtomHeader, /// Sequence number associated with fragment. pub sequence_number: u32, } impl Atom for MfhdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let sequence_number = reader.read_be_u32()?; Ok(MfhdAtom { header, sequence_number }) } } symphonia-format-isomp4-0.5.4/src/atoms/minf.rs000064400000000000000000000027641046102023000175620ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, SmhdAtom, StblAtom}; /// Media information atom. #[derive(Debug)] pub struct MinfAtom { /// Atom header. header: AtomHeader, /// Sound media header atom. pub smhd: Option, /// Sample table atom. pub stbl: StblAtom, } impl Atom for MinfAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut smhd = None; let mut stbl = None; while let Some(header) = iter.next()? { match header.atype { AtomType::SoundMediaHeader => { smhd = Some(iter.read_atom::()?); } AtomType::SampleTable => { stbl = Some(iter.read_atom::()?); } _ => (), } } if stbl.is_none() { return decode_error("isomp4: missing stbl atom"); } Ok(MinfAtom { header, smhd, stbl: stbl.unwrap() }) } } symphonia-format-isomp4-0.5.4/src/atoms/mod.rs000064400000000000000000000335721046102023000174110ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; pub(crate) mod alac; pub(crate) mod co64; pub(crate) mod ctts; pub(crate) mod edts; pub(crate) mod elst; pub(crate) mod esds; pub(crate) mod flac; pub(crate) mod ftyp; pub(crate) mod hdlr; pub(crate) mod ilst; pub(crate) mod mdhd; pub(crate) mod mdia; pub(crate) mod mehd; pub(crate) mod meta; pub(crate) mod mfhd; pub(crate) mod minf; pub(crate) mod moof; pub(crate) mod moov; pub(crate) mod mvex; pub(crate) mod mvhd; pub(crate) mod opus; pub(crate) mod sidx; pub(crate) mod smhd; pub(crate) mod stbl; pub(crate) mod stco; pub(crate) mod stsc; pub(crate) mod stsd; pub(crate) mod stss; pub(crate) mod stsz; pub(crate) mod stts; pub(crate) mod tfhd; pub(crate) mod tkhd; pub(crate) mod traf; pub(crate) mod trak; pub(crate) mod trex; pub(crate) mod trun; pub(crate) mod udta; pub(crate) mod wave; pub use self::meta::MetaAtom; pub use alac::AlacAtom; pub use co64::Co64Atom; #[allow(unused_imports)] pub use ctts::CttsAtom; pub use edts::EdtsAtom; pub use elst::ElstAtom; pub use esds::EsdsAtom; pub use flac::FlacAtom; pub use ftyp::FtypAtom; pub use hdlr::HdlrAtom; pub use ilst::IlstAtom; pub use mdhd::MdhdAtom; pub use mdia::MdiaAtom; pub use mehd::MehdAtom; pub use mfhd::MfhdAtom; pub use minf::MinfAtom; pub use moof::MoofAtom; pub use moov::MoovAtom; pub use mvex::MvexAtom; pub use mvhd::MvhdAtom; pub use opus::OpusAtom; pub use sidx::SidxAtom; pub use smhd::SmhdAtom; pub use stbl::StblAtom; pub use stco::StcoAtom; pub use stsc::StscAtom; pub use stsd::StsdAtom; #[allow(unused_imports)] pub use stss::StssAtom; pub use stsz::StszAtom; pub use stts::SttsAtom; pub use tfhd::TfhdAtom; pub use tkhd::TkhdAtom; pub use traf::TrafAtom; pub use trak::TrakAtom; pub use trex::TrexAtom; pub use trun::TrunAtom; pub use udta::UdtaAtom; pub use wave::WaveAtom; /// Atom types. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum AtomType { Ac3, AdvisoryTag, Alac, ALaw, AlbumArtistTag, AlbumTag, ArtistLowerTag, ArtistTag, CategoryTag, ChunkOffset, ChunkOffset64, CommentTag, CompilationTag, ComposerTag, CompositionTimeToSample, CopyrightTag, CoverTag, CustomGenreTag, DateTag, DescriptionTag, DiskNumberTag, Edit, EditList, EncodedByTag, EncoderTag, Esds, F32SampleEntry, F64SampleEntry, FileType, Flac, FlacDsConfig, Free, FreeFormTag, GaplessPlaybackTag, GenreTag, GroupingTag, Handler, HdVideoTag, IdentPodcastTag, KeywordTag, LongDescriptionTag, Lpcm, LyricsTag, Media, MediaData, MediaHeader, MediaInfo, MediaTypeTag, Meta, MetaList, MetaTagData, MetaTagMeaning, MetaTagName, Movie, MovieExtends, MovieExtendsHeader, MovieFragment, MovieFragmentHeader, MovieHeader, Mp3, Mp4a, MuLaw, Opus, OpusDsConfig, OwnerTag, PodcastTag, PurchaseDateTag, QtWave, RatingTag, S16BeSampleEntry, S16LeSampleEntry, S24SampleEntry, S32SampleEntry, SampleDescription, SampleSize, SampleTable, SampleToChunk, SegmentIndex, Skip, SortAlbumArtistTag, SortAlbumTag, SortArtistTag, SortComposerTag, SortNameTag, SoundMediaHeader, SyncSample, TempoTag, TimeToSample, Track, TrackExtends, TrackFragment, TrackFragmentHeader, TrackFragmentRun, TrackHeader, TrackNumberTag, TrackTitleTag, TvEpisodeNameTag, TvEpisodeNumberTag, TvNetworkNameTag, TvSeasonNumberTag, TvShowNameTag, U8SampleEntry, UrlPodcastTag, UserData, Other([u8; 4]), } impl From<[u8; 4]> for AtomType { fn from(val: [u8; 4]) -> Self { match &val { b".mp3" => AtomType::Mp3, b"ac-3" => AtomType::Ac3, b"alac" => AtomType::Alac, b"alaw" => AtomType::ALaw, b"co64" => AtomType::ChunkOffset64, b"ctts" => AtomType::CompositionTimeToSample, b"data" => AtomType::MetaTagData, b"dfLa" => AtomType::FlacDsConfig, b"dOps" => AtomType::OpusDsConfig, b"edts" => AtomType::Edit, b"elst" => AtomType::EditList, b"esds" => AtomType::Esds, b"fl32" => AtomType::F32SampleEntry, b"fl64" => AtomType::F64SampleEntry, b"fLaC" => AtomType::Flac, b"free" => AtomType::Free, b"ftyp" => AtomType::FileType, b"hdlr" => AtomType::Handler, b"ilst" => AtomType::MetaList, b"in24" => AtomType::S24SampleEntry, b"in32" => AtomType::S32SampleEntry, b"lpcm" => AtomType::Lpcm, b"mdat" => AtomType::MediaData, b"mdhd" => AtomType::MediaHeader, b"mdia" => AtomType::Media, b"mean" => AtomType::MetaTagMeaning, b"mehd" => AtomType::MovieExtendsHeader, b"meta" => AtomType::Meta, b"mfhd" => AtomType::MovieFragmentHeader, b"minf" => AtomType::MediaInfo, b"moof" => AtomType::MovieFragment, b"moov" => AtomType::Movie, b"mp4a" => AtomType::Mp4a, b"mvex" => AtomType::MovieExtends, b"mvhd" => AtomType::MovieHeader, b"name" => AtomType::MetaTagName, b"Opus" => AtomType::Opus, b"raw " => AtomType::U8SampleEntry, b"sidx" => AtomType::SegmentIndex, b"skip" => AtomType::Skip, b"smhd" => AtomType::SoundMediaHeader, b"sowt" => AtomType::S16LeSampleEntry, b"stbl" => AtomType::SampleTable, b"stco" => AtomType::ChunkOffset, b"stsc" => AtomType::SampleToChunk, b"stsd" => AtomType::SampleDescription, b"stss" => AtomType::SyncSample, b"stsz" => AtomType::SampleSize, b"stts" => AtomType::TimeToSample, b"tfhd" => AtomType::TrackFragmentHeader, b"tkhd" => AtomType::TrackHeader, b"traf" => AtomType::TrackFragment, b"trak" => AtomType::Track, b"trex" => AtomType::TrackExtends, b"trun" => AtomType::TrackFragmentRun, b"twos" => AtomType::S16BeSampleEntry, b"udta" => AtomType::UserData, b"ulaw" => AtomType::MuLaw, b"wave" => AtomType::QtWave, // Metadata Boxes b"----" => AtomType::FreeFormTag, b"aART" => AtomType::AlbumArtistTag, b"catg" => AtomType::CategoryTag, b"covr" => AtomType::CoverTag, b"cpil" => AtomType::CompilationTag, b"cprt" => AtomType::CopyrightTag, b"desc" => AtomType::DescriptionTag, b"disk" => AtomType::DiskNumberTag, b"egid" => AtomType::IdentPodcastTag, b"gnre" => AtomType::GenreTag, b"hdvd" => AtomType::HdVideoTag, b"keyw" => AtomType::KeywordTag, b"ldes" => AtomType::LongDescriptionTag, b"ownr" => AtomType::OwnerTag, b"pcst" => AtomType::PodcastTag, b"pgap" => AtomType::GaplessPlaybackTag, b"purd" => AtomType::PurchaseDateTag, b"purl" => AtomType::UrlPodcastTag, b"rate" => AtomType::RatingTag, b"rtng" => AtomType::AdvisoryTag, b"soaa" => AtomType::SortAlbumArtistTag, b"soal" => AtomType::SortAlbumTag, b"soar" => AtomType::SortArtistTag, b"soco" => AtomType::SortComposerTag, b"sonm" => AtomType::SortNameTag, b"stik" => AtomType::MediaTypeTag, b"tmpo" => AtomType::TempoTag, b"trkn" => AtomType::TrackNumberTag, b"tven" => AtomType::TvEpisodeNameTag, b"tves" => AtomType::TvEpisodeNumberTag, b"tvnn" => AtomType::TvNetworkNameTag, b"tvsh" => AtomType::TvShowNameTag, b"tvsn" => AtomType::TvSeasonNumberTag, b"\xa9alb" => AtomType::AlbumTag, b"\xa9art" => AtomType::ArtistLowerTag, b"\xa9ART" => AtomType::ArtistTag, b"\xa9cmt" => AtomType::CommentTag, b"\xa9day" => AtomType::DateTag, b"\xa9enc" => AtomType::EncodedByTag, b"\xa9gen" => AtomType::CustomGenreTag, b"\xa9grp" => AtomType::GroupingTag, b"\xa9lyr" => AtomType::LyricsTag, b"\xa9nam" => AtomType::TrackTitleTag, b"\xa9too" => AtomType::EncoderTag, b"\xa9wrt" => AtomType::ComposerTag, _ => AtomType::Other(val), } } } /// Common atom header. #[derive(Copy, Clone, Debug)] pub struct AtomHeader { /// The atom type. pub atype: AtomType, /// The total size of the atom including the header. pub atom_len: u64, /// The size of the payload data. pub data_len: u64, } impl AtomHeader { const HEADER_SIZE: u64 = 8; const EXTENDED_HEADER_SIZE: u64 = AtomHeader::HEADER_SIZE + 8; const EXTRA_DATA_SIZE: u64 = 4; /// Reads an atom header from the provided `ByteStream`. pub fn read(reader: &mut B) -> Result { let mut atom_len = u64::from(reader.read_be_u32()?); let atype = AtomType::from(reader.read_quad_bytes()?); let data_len = match atom_len { 0 => 0, 1 => { atom_len = reader.read_be_u64()?; // The atom size should be atleast the length of the header. if atom_len < AtomHeader::EXTENDED_HEADER_SIZE { return decode_error("isomp4: atom size is invalid"); } atom_len - AtomHeader::EXTENDED_HEADER_SIZE } _ => { // The atom size should be atleast the length of the header. if atom_len < AtomHeader::HEADER_SIZE { return decode_error("isomp4: atom size is invalid"); } atom_len - AtomHeader::HEADER_SIZE } }; Ok(AtomHeader { atype, atom_len, data_len }) } #[allow(dead_code)] pub fn base_header_len(&self) -> u64 { match self.atom_len { 0 => AtomHeader::HEADER_SIZE, _ => self.atom_len - self.data_len, } } /// For applicable atoms, reads the atom header extra data: a tuple composed of a u8 version /// number, and a u24 bitset of flags. pub fn read_extra(reader: &mut B) -> Result<(u8, u32)> { Ok((reader.read_u8()?, reader.read_be_u24()?)) } } pub trait Atom: Sized { fn header(&self) -> AtomHeader; fn read(reader: &mut B, header: AtomHeader) -> Result; } pub struct AtomIterator { reader: B, len: Option, cur_atom: Option, base_pos: u64, next_atom_pos: u64, } impl AtomIterator { pub fn new_root(reader: B, len: Option) -> Self { let base_pos = reader.pos(); AtomIterator { reader, len, cur_atom: None, base_pos, next_atom_pos: base_pos } } pub fn new(reader: B, container: AtomHeader) -> Self { let base_pos = reader.pos(); AtomIterator { reader, len: Some(container.data_len), cur_atom: None, base_pos, next_atom_pos: base_pos, } } pub fn into_inner(self) -> B { self.reader } pub fn inner_mut(&mut self) -> &mut B { &mut self.reader } pub fn next(&mut self) -> Result> { // Ignore any remaining data in the current atom that was not read. let cur_pos = self.reader.pos(); if cur_pos < self.next_atom_pos { self.reader.ignore_bytes(self.next_atom_pos - cur_pos)?; } else if cur_pos > self.next_atom_pos { // This is very bad, either the atom's length was incorrect or the demuxer erroroneously // overread an atom. return decode_error("isomp4: overread atom"); } // If len is specified, then do not read more than len bytes. if let Some(len) = self.len { if self.next_atom_pos - self.base_pos >= len { return Ok(None); } } // Read the next atom header. let atom = AtomHeader::read(&mut self.reader)?; // Calculate the start position for the next atom (the exclusive end of the current atom). self.next_atom_pos = match atom.atom_len { 0 => { // An atom with a length of zero is defined to span to the end of the stream. If // len is available, use it for the next atom start position, otherwise, use u64 max // which will trip an end of stream error on the next iteration. self.len.map(|l| self.base_pos + l).unwrap_or(std::u64::MAX) } len => self.next_atom_pos + len, }; self.cur_atom = Some(atom); Ok(self.cur_atom) } pub fn next_no_consume(&mut self) -> Result> { if self.cur_atom.is_some() { Ok(self.cur_atom) } else { self.next() } } pub fn read_atom(&mut self) -> Result { // It is not possible to read the current atom more than once because ByteStream is not // seekable. Therefore, raise an assert if read_atom is called more than once between calls // to next, or after next returns None. assert!(self.cur_atom.is_some()); A::read(&mut self.reader, self.cur_atom.take().unwrap()) } pub fn consume_atom(&mut self) { assert!(self.cur_atom.take().is_some()); } } symphonia-format-isomp4-0.5.4/src/atoms/moof.rs000064400000000000000000000034171046102023000175650ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, MfhdAtom, TrafAtom}; /// Movie fragment atom. #[derive(Debug)] pub struct MoofAtom { /// Atom header. header: AtomHeader, /// The position of the first byte of this moof atom. This is used as the anchor point for the /// subsequent track atoms. pub moof_base_pos: u64, /// Movie fragment header. pub mfhd: MfhdAtom, /// Track fragments. pub trafs: Vec, } impl Atom for MoofAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let moof_base_pos = reader.pos() - AtomHeader::HEADER_SIZE; let mut mfhd = None; let mut trafs = Vec::new(); let mut iter = AtomIterator::new(reader, header); while let Some(header) = iter.next()? { match header.atype { AtomType::MovieFragmentHeader => { mfhd = Some(iter.read_atom::()?); } AtomType::TrackFragment => { let traf = iter.read_atom::()?; trafs.push(traf); } _ => (), } } if mfhd.is_none() { return decode_error("isomp4: missing mfhd atom"); } Ok(MoofAtom { header, moof_base_pos, mfhd: mfhd.unwrap(), trafs }) } } symphonia-format-isomp4-0.5.4/src/atoms/moov.rs000064400000000000000000000056101046102023000176020ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use symphonia_core::meta::MetadataRevision; use crate::atoms::{ Atom, AtomHeader, AtomIterator, AtomType, MvexAtom, MvhdAtom, TrakAtom, UdtaAtom, }; use log::warn; /// Movie atom. #[derive(Debug)] pub struct MoovAtom { /// Atom header. header: AtomHeader, /// Movie header atom. pub mvhd: MvhdAtom, /// Trak atoms. pub traks: Vec, /// Movie extends atom. The presence of this atom indicates a fragmented stream. pub mvex: Option, /// User data (usually metadata). pub udta: Option, } impl MoovAtom { /// If metadata was read, consumes the metadata and returns it. pub fn take_metadata(&mut self) -> Option { self.udta.as_mut().and_then(|udta| udta.take_metadata()) } /// Is the movie segmented. pub fn is_fragmented(&self) -> bool { self.mvex.is_some() } } impl Atom for MoovAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut mvhd = None; let mut traks = Vec::new(); let mut mvex = None; let mut udta = None; while let Some(header) = iter.next()? { match header.atype { AtomType::MovieHeader => { mvhd = Some(iter.read_atom::()?); } AtomType::Track => { let trak = iter.read_atom::()?; traks.push(trak); } AtomType::MovieExtends => { mvex = Some(iter.read_atom::()?); } AtomType::UserData => { udta = Some(iter.read_atom::()?); } _ => (), } } if mvhd.is_none() { return decode_error("isomp4: missing mvhd atom"); } // If fragmented, the mvex atom should contain a trex atom for each trak atom in moov. if let Some(mvex) = mvex.as_ref() { // For each trak, find a matching trex atom using the track id. for trak in traks.iter() { let found = mvex.trexs.iter().any(|trex| trex.track_id == trak.tkhd.id); if !found { warn!("missing trex atom for trak with id={}", trak.tkhd.id); } } } Ok(MoovAtom { header, mvhd: mvhd.unwrap(), traks, mvex, udta }) } } symphonia-format-isomp4-0.5.4/src/atoms/mvex.rs000064400000000000000000000026731046102023000176070ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, MehdAtom, TrexAtom}; /// Movie extends atom. #[derive(Debug)] pub struct MvexAtom { /// Atom header. pub header: AtomHeader, /// Movie extends header, optional. pub mehd: Option, /// Track extends box, one per track. pub trexs: Vec, } impl Atom for MvexAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut mehd = None; let mut trexs = Vec::new(); while let Some(header) = iter.next()? { match header.atype { AtomType::MovieExtendsHeader => { mehd = Some(iter.read_atom::()?); } AtomType::TrackExtends => { let trex = iter.read_atom::()?; trexs.push(trex); } _ => (), } } Ok(MvexAtom { header, mehd, trexs }) } } symphonia-format-isomp4-0.5.4/src/atoms/mvhd.rs000064400000000000000000000046171046102023000175660ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; use crate::fp::FpU8; /// Movie header atom. #[derive(Debug)] pub struct MvhdAtom { /// Atom header. pub header: AtomHeader, /// The creation time. pub ctime: u64, /// The modification time. pub mtime: u64, /// Timescale for the movie expressed as the number of units per second. pub timescale: u32, /// The duration of the movie in `timescale` units. pub duration: u64, /// The preferred volume to play the movie. pub volume: FpU8, } impl Atom for MvhdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, _) = AtomHeader::read_extra(reader)?; let mut mvhd = MvhdAtom { header, ctime: 0, mtime: 0, timescale: 0, duration: 0, volume: Default::default(), }; // Version 0 uses 32-bit time values, verion 1 used 64-bit values. match version { 0 => { mvhd.ctime = u64::from(reader.read_be_u32()?); mvhd.mtime = u64::from(reader.read_be_u32()?); mvhd.timescale = reader.read_be_u32()?; // 0xffff_ffff is a special case. mvhd.duration = match reader.read_be_u32()? { std::u32::MAX => std::u64::MAX, duration => u64::from(duration), }; } 1 => { mvhd.ctime = reader.read_be_u64()?; mvhd.mtime = reader.read_be_u64()?; mvhd.timescale = reader.read_be_u32()?; mvhd.duration = reader.read_be_u64()?; } _ => return decode_error("isomp4: invalid mvhd version"), } // Ignore the preferred playback rate. let _ = reader.read_be_u32()?; // Preferred volume. mvhd.volume = FpU8::parse_raw(reader.read_be_u16()?); // Remaining fields are ignored. Ok(mvhd) } } symphonia-format-isomp4-0.5.4/src/atoms/opus.rs000064400000000000000000000050631046102023000176120ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::codecs::{CodecParameters, CODEC_TYPE_OPUS}; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub struct OpusAtom { /// Atom header. header: AtomHeader, /// Opus extra data (identification header). extra_data: Box<[u8]>, } impl Atom for OpusAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { const OPUS_MAGIC: &[u8] = b"OpusHead"; const OPUS_MAGIC_LEN: usize = OPUS_MAGIC.len(); const MIN_OPUS_EXTRA_DATA_SIZE: usize = OPUS_MAGIC_LEN + 11; const MAX_OPUS_EXTRA_DATA_SIZE: usize = MIN_OPUS_EXTRA_DATA_SIZE + 257; // Offset of the Opus version number in the extra data. const OPUS_EXTRADATA_VERSION_OFFSET: usize = OPUS_MAGIC_LEN; // The dops atom contains an Opus identification header excluding the OpusHead magic // signature. Therefore, the atom data length should be atleast as long as the shortest // Opus identification header. let data_len = header.data_len as usize; if data_len < MIN_OPUS_EXTRA_DATA_SIZE - OPUS_MAGIC_LEN { return decode_error("isomp4 (opus): opus identification header too short"); } if data_len > MAX_OPUS_EXTRA_DATA_SIZE - OPUS_MAGIC_LEN { return decode_error("isomp4 (opus): opus identification header too large"); } let mut extra_data = vec![0; OPUS_MAGIC_LEN + data_len].into_boxed_slice(); // The Opus magic is excluded in the atom, but the extra data must start with it. extra_data[..OPUS_MAGIC_LEN].copy_from_slice(OPUS_MAGIC); // Read the extra data from the atom. reader.read_buf_exact(&mut extra_data[OPUS_MAGIC_LEN..])?; // Verify the version number is 0. if extra_data[OPUS_EXTRADATA_VERSION_OFFSET] != 0 { return unsupported_error("isomp4 (opus): unsupported opus version"); } Ok(OpusAtom { header, extra_data }) } } impl OpusAtom { pub fn fill_codec_params(&self, codec_params: &mut CodecParameters) { codec_params.for_codec(CODEC_TYPE_OPUS).with_extra_data(self.extra_data.clone()); } } symphonia-format-isomp4-0.5.4/src/atoms/sidx.rs000064400000000000000000000050541046102023000175730ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub enum ReferenceType { Segment, Media, } #[derive(Debug)] pub struct SidxReference { pub reference_type: ReferenceType, pub reference_size: u32, pub subsegment_duration: u32, // pub starts_with_sap: bool, // pub sap_type: u8, // pub sap_delta_time: u32, } /// Segment index atom. #[derive(Debug)] pub struct SidxAtom { /// Atom header. header: AtomHeader, pub reference_id: u32, pub timescale: u32, pub earliest_pts: u64, pub first_offset: u64, pub references: Vec, } impl Atom for SidxAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { // The anchor point for segment offsets is the first byte after this atom. let anchor = reader.pos() + header.data_len; let (version, _) = AtomHeader::read_extra(reader)?; let reference_id = reader.read_be_u32()?; let timescale = reader.read_be_u32()?; let (earliest_pts, first_offset) = match version { 0 => (u64::from(reader.read_be_u32()?), anchor + u64::from(reader.read_be_u32()?)), 1 => (reader.read_be_u64()?, anchor + reader.read_be_u64()?), _ => { return decode_error("isomp4: invalid sidx version"); } }; let _reserved = reader.read_be_u16()?; let reference_count = reader.read_be_u16()?; let mut references = Vec::new(); for _ in 0..reference_count { let reference = reader.read_be_u32()?; let subsegment_duration = reader.read_be_u32()?; let reference_type = match (reference & 0x8000_0000) != 0 { false => ReferenceType::Media, true => ReferenceType::Segment, }; let reference_size = reference & !0x8000_0000; // Ignore SAP let _ = reader.read_be_u32()?; references.push(SidxReference { reference_type, reference_size, subsegment_duration }); } Ok(SidxAtom { header, reference_id, timescale, earliest_pts, first_offset, references }) } } symphonia-format-isomp4-0.5.4/src/atoms/smhd.rs000064400000000000000000000017501046102023000175560ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; use crate::fp::FpI8; /// Sound header atom. #[derive(Debug)] pub struct SmhdAtom { /// Atom header. header: AtomHeader, /// Stereo balance. pub balance: FpI8, } impl Atom for SmhdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; // Stereo balance let balance = FpI8::parse_raw(reader.read_be_u16()? as i16); // Reserved. let _ = reader.read_be_u16()?; Ok(SmhdAtom { header, balance }) } } symphonia-format-isomp4-0.5.4/src/atoms/stbl.rs000064400000000000000000000063471046102023000175760ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType}; use crate::atoms::{Co64Atom, StcoAtom, StscAtom, StsdAtom, StszAtom, SttsAtom}; use log::warn; /// Sample table atom. #[derive(Debug)] pub struct StblAtom { /// Atom header. header: AtomHeader, pub stsd: StsdAtom, pub stts: SttsAtom, pub stsc: StscAtom, pub stsz: StszAtom, pub stco: Option, pub co64: Option, } impl Atom for StblAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut stsd = None; let mut stts = None; let mut stsc = None; let mut stsz = None; let mut stco = None; let mut co64 = None; while let Some(header) = iter.next()? { match header.atype { AtomType::SampleDescription => { stsd = Some(iter.read_atom::()?); } AtomType::TimeToSample => { stts = Some(iter.read_atom::()?); } AtomType::CompositionTimeToSample => { // Composition time to sample atom is only required for video. warn!("ignoring ctts atom."); } AtomType::SyncSample => { // Sync sample atom is only required for video. warn!("ignoring stss atom."); } AtomType::SampleToChunk => { stsc = Some(iter.read_atom::()?); } AtomType::SampleSize => { stsz = Some(iter.read_atom::()?); } AtomType::ChunkOffset => { stco = Some(iter.read_atom::()?); } AtomType::ChunkOffset64 => { co64 = Some(iter.read_atom::()?); } _ => (), } } if stsd.is_none() { return decode_error("isomp4: missing stsd atom"); } if stts.is_none() { return decode_error("isomp4: missing stts atom"); } if stsc.is_none() { return decode_error("isomp4: missing stsc atom"); } if stsz.is_none() { return decode_error("isomp4: missing stsz atom"); } if stco.is_none() && co64.is_none() { // This is a spec. violation, but some m4a files appear to lack these atoms. warn!("missing stco or co64 atom"); } Ok(StblAtom { header, stsd: stsd.unwrap(), stts: stts.unwrap(), stsc: stsc.unwrap(), stsz: stsz.unwrap(), stco, co64, }) } } symphonia-format-isomp4-0.5.4/src/atoms/stco.rs000064400000000000000000000021001046102023000175610ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; /// Chunk offset atom (32-bit version). #[derive(Debug)] pub struct StcoAtom { /// Atom header. header: AtomHeader, pub chunk_offsets: Vec, } impl Atom for StcoAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let entry_count = reader.read_be_u32()?; // TODO: Apply a limit. let mut chunk_offsets = Vec::with_capacity(entry_count as usize); for _ in 0..entry_count { chunk_offsets.push(reader.read_be_u32()?); } Ok(StcoAtom { header, chunk_offsets }) } } symphonia-format-isomp4-0.5.4/src/atoms/stsc.rs000064400000000000000000000070731046102023000176030ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub struct StscEntry { pub first_chunk: u32, pub first_sample: u32, pub samples_per_chunk: u32, pub sample_desc_index: u32, } /// Sample to Chunk Atom #[derive(Debug)] pub struct StscAtom { /// Atom header. header: AtomHeader, /// Entries. pub entries: Vec, } impl StscAtom { /// Finds the `StscEntry` for the sample indicated by `sample_num`. Note, `sample_num` is indexed /// relative to the `StscAtom`. Complexity is O(log2 N). pub fn find_entry_for_sample(&self, sample_num: u32) -> Option<&StscEntry> { let mut left = 1; let mut right = self.entries.len(); while left < right { let mid = left + (right - left) / 2; let entry = self.entries.get(mid).unwrap(); if entry.first_sample < sample_num { left = mid + 1; } else { right = mid; } } // The index found above (left) is the exclusive upper bound of all entries where // first_sample < sample_num. Therefore, the entry to return has an index of left-1. The // index will never equal 0 so this is safe. If the table were empty, left == 1, thus calling // get with an index of 0, and safely returning None. self.entries.get(left - 1) } } impl Atom for StscAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let entry_count = reader.read_be_u32()?; // TODO: Apply a limit. let mut entries = Vec::with_capacity(entry_count as usize); for _ in 0..entry_count { entries.push(StscEntry { first_chunk: reader.read_be_u32()? - 1, first_sample: 0, samples_per_chunk: reader.read_be_u32()?, sample_desc_index: reader.read_be_u32()?, }); } // Post-process entries to check for errors and calculate the file sample. if entry_count > 0 { for i in 0..entry_count as usize - 1 { // Validate that first_chunk is monotonic across all entries. if entries[i + 1].first_chunk < entries[i].first_chunk { return decode_error("isomp4: stsc entry first chunk not monotonic"); } // Validate that samples per chunk is > 0. Could the entry be ignored? if entries[i].samples_per_chunk == 0 { return decode_error("isomp4: stsc entry has 0 samples per chunk"); } let n = entries[i + 1].first_chunk - entries[i].first_chunk; entries[i + 1].first_sample = entries[i].first_sample + (n * entries[i].samples_per_chunk); } // Validate that samples per chunk is > 0. Could the entry be ignored? if entries[entry_count as usize - 1].samples_per_chunk == 0 { return decode_error("isomp4: stsc entry has 0 samples per chunk"); } } Ok(StscAtom { header, entries }) } } symphonia-format-isomp4-0.5.4/src/atoms/stsd.rs000064400000000000000000000453451046102023000176100ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::audio::Channels; use symphonia_core::codecs::{CodecParameters, CodecType, CODEC_TYPE_MP3, CODEC_TYPE_NULL}; use symphonia_core::codecs::{CODEC_TYPE_PCM_F32BE, CODEC_TYPE_PCM_F32LE}; use symphonia_core::codecs::{CODEC_TYPE_PCM_F64BE, CODEC_TYPE_PCM_F64LE}; use symphonia_core::codecs::{CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S16LE}; use symphonia_core::codecs::{CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S24LE}; use symphonia_core::codecs::{CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S32LE}; use symphonia_core::codecs::{CODEC_TYPE_PCM_S8, CODEC_TYPE_PCM_U8}; use symphonia_core::codecs::{CODEC_TYPE_PCM_U16BE, CODEC_TYPE_PCM_U16LE}; use symphonia_core::codecs::{CODEC_TYPE_PCM_U24BE, CODEC_TYPE_PCM_U24LE}; use symphonia_core::codecs::{CODEC_TYPE_PCM_U32BE, CODEC_TYPE_PCM_U32LE}; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{AlacAtom, Atom, AtomHeader, AtomType, EsdsAtom, FlacAtom, OpusAtom, WaveAtom}; use crate::fp::FpU16; use super::AtomIterator; /// Sample description atom. #[derive(Debug)] pub struct StsdAtom { /// Atom header. header: AtomHeader, /// Sample entry. sample_entry: SampleEntry, } impl Atom for StsdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let n_entries = reader.read_be_u32()?; if n_entries == 0 { return decode_error("isomp4: missing sample entry"); } if n_entries > 1 { return unsupported_error("isomp4: more than 1 sample entry"); } let sample_entry_header = AtomHeader::read(reader)?; let sample_entry = match sample_entry_header.atype { AtomType::Mp4a | AtomType::Alac | AtomType::Flac | AtomType::Opus | AtomType::Mp3 | AtomType::Lpcm | AtomType::QtWave | AtomType::ALaw | AtomType::MuLaw | AtomType::U8SampleEntry | AtomType::S16LeSampleEntry | AtomType::S16BeSampleEntry | AtomType::S24SampleEntry | AtomType::S32SampleEntry | AtomType::F32SampleEntry | AtomType::F64SampleEntry => read_audio_sample_entry(reader, sample_entry_header)?, _ => { // Potentially video, subtitles, etc. SampleEntry::Other } }; Ok(StsdAtom { header, sample_entry }) } } impl StsdAtom { /// Fill the provided `CodecParameters` using the sample entry. pub fn fill_codec_params(&self, codec_params: &mut CodecParameters) { // Audio sample entry. if let SampleEntry::Audio(ref entry) = self.sample_entry { // General audio parameters. codec_params.with_sample_rate(entry.sample_rate as u32); // Codec-specific parameters. match entry.codec_specific { Some(AudioCodecSpecific::Esds(ref esds)) => { esds.fill_codec_params(codec_params); } Some(AudioCodecSpecific::Alac(ref alac)) => { alac.fill_codec_params(codec_params); } Some(AudioCodecSpecific::Flac(ref flac)) => { flac.fill_codec_params(codec_params); } Some(AudioCodecSpecific::Opus(ref opus)) => { opus.fill_codec_params(codec_params); } Some(AudioCodecSpecific::Mp3) => { codec_params.for_codec(CODEC_TYPE_MP3); } Some(AudioCodecSpecific::Pcm(ref pcm)) => { // PCM codecs. codec_params .for_codec(pcm.codec_type) .with_bits_per_coded_sample(pcm.bits_per_coded_sample) .with_bits_per_sample(pcm.bits_per_sample) .with_max_frames_per_packet(pcm.frames_per_packet) .with_channels(pcm.channels); } _ => (), } } } } #[derive(Debug)] pub struct Pcm { pub codec_type: CodecType, pub bits_per_sample: u32, pub bits_per_coded_sample: u32, pub frames_per_packet: u64, pub channels: Channels, } #[derive(Debug)] pub enum AudioCodecSpecific { /// MPEG Elementary Stream descriptor. Esds(EsdsAtom), /// Apple Lossless Audio Codec (ALAC). Alac(AlacAtom), /// Free Lossless Audio Codec (FLAC). Flac(FlacAtom), /// Opus. Opus(OpusAtom), /// MP3. Mp3, /// PCM codecs. Pcm(Pcm), } #[derive(Debug)] pub struct AudioSampleEntry { pub num_channels: u32, pub sample_size: u16, pub sample_rate: f64, pub codec_specific: Option, } #[derive(Debug)] pub enum SampleEntry { Audio(AudioSampleEntry), // Video, // Metadata, Other, } /// Gets if the sample entry atom is for a PCM codec. fn is_pcm_codec(atype: AtomType) -> bool { // PCM data in version 0 and 1 is signalled by the sample entry atom type. In version 2, the // atom type for PCM data is always LPCM. atype == AtomType::Lpcm || pcm_codec_type(atype) != CODEC_TYPE_NULL } /// Gets the PCM codec from the sample entry atom type for version 0 and 1 sample entries. fn pcm_codec_type(atype: AtomType) -> CodecType { match atype { AtomType::U8SampleEntry => CODEC_TYPE_PCM_U8, AtomType::S16LeSampleEntry => CODEC_TYPE_PCM_S16LE, AtomType::S16BeSampleEntry => CODEC_TYPE_PCM_S16BE, AtomType::S24SampleEntry => CODEC_TYPE_PCM_S24LE, AtomType::S32SampleEntry => CODEC_TYPE_PCM_S32LE, AtomType::F32SampleEntry => CODEC_TYPE_PCM_F32LE, AtomType::F64SampleEntry => CODEC_TYPE_PCM_F64LE, _ => CODEC_TYPE_NULL, } } /// Determines the number of bytes per PCM sample for a PCM codec type. fn bytes_per_pcm_sample(pcm_codec_type: CodecType) -> u32 { match pcm_codec_type { CODEC_TYPE_PCM_S8 | CODEC_TYPE_PCM_U8 => 1, CODEC_TYPE_PCM_S16BE | CODEC_TYPE_PCM_S16LE => 2, CODEC_TYPE_PCM_U16BE | CODEC_TYPE_PCM_U16LE => 2, CODEC_TYPE_PCM_S24BE | CODEC_TYPE_PCM_S24LE => 3, CODEC_TYPE_PCM_U24BE | CODEC_TYPE_PCM_U24LE => 3, CODEC_TYPE_PCM_S32BE | CODEC_TYPE_PCM_S32LE => 4, CODEC_TYPE_PCM_U32BE | CODEC_TYPE_PCM_U32LE => 4, CODEC_TYPE_PCM_F32BE | CODEC_TYPE_PCM_F32LE => 4, CODEC_TYPE_PCM_F64BE | CODEC_TYPE_PCM_F64LE => 8, _ => unreachable!(), } } /// Gets the PCM codec from the LPCM parameters in the version 2 sample entry atom. fn lpcm_codec_type(bits_per_sample: u32, lpcm_flags: u32) -> CodecType { let is_floating_point = lpcm_flags & 0x1 != 0; let is_big_endian = lpcm_flags & 0x2 != 0; let is_signed = lpcm_flags & 0x4 != 0; if is_floating_point { // Floating-point sample format. match bits_per_sample { 32 => { if is_big_endian { CODEC_TYPE_PCM_F32BE } else { CODEC_TYPE_PCM_F32LE } } 64 => { if is_big_endian { CODEC_TYPE_PCM_F64BE } else { CODEC_TYPE_PCM_F64LE } } _ => CODEC_TYPE_NULL, } } else { // Integer sample format. if is_signed { // Signed-integer sample format. match bits_per_sample { 8 => CODEC_TYPE_PCM_S8, 16 => { if is_big_endian { CODEC_TYPE_PCM_S16BE } else { CODEC_TYPE_PCM_S16LE } } 24 => { if is_big_endian { CODEC_TYPE_PCM_S24BE } else { CODEC_TYPE_PCM_S24LE } } 32 => { if is_big_endian { CODEC_TYPE_PCM_S32BE } else { CODEC_TYPE_PCM_S32LE } } _ => CODEC_TYPE_NULL, } } else { // Unsigned-integer sample format. match bits_per_sample { 8 => CODEC_TYPE_PCM_U8, 16 => { if is_big_endian { CODEC_TYPE_PCM_U16BE } else { CODEC_TYPE_PCM_U16LE } } 24 => { if is_big_endian { CODEC_TYPE_PCM_U24BE } else { CODEC_TYPE_PCM_U24LE } } 32 => { if is_big_endian { CODEC_TYPE_PCM_U32BE } else { CODEC_TYPE_PCM_U32LE } } _ => CODEC_TYPE_NULL, } } } } /// Gets the audio channels for a version 0 or 1 sample entry. fn pcm_channels(num_channels: u32) -> Result { match num_channels { 1 => Ok(Channels::FRONT_LEFT), 2 => Ok(Channels::FRONT_LEFT | Channels::FRONT_RIGHT), _ => decode_error("isomp4: invalid number of channels"), } } /// Gets the audio channels for a version 2 LPCM sample entry. fn lpcm_channels(num_channels: u32) -> Result { if num_channels < 1 { return decode_error("isomp4: invalid number of channels"); } if num_channels > 32 { return unsupported_error("isomp4: maximum 32 channels"); } // TODO: For LPCM, the channels are "auxilary". They do not have a speaker assignment. Symphonia // does not have a way to represent this yet. let channel_mask = !((!0 << 1) << (num_channels - 1)); match Channels::from_bits(channel_mask) { Some(channels) => Ok(channels), _ => unsupported_error("isomp4: unsupported number of channels"), } } fn read_audio_sample_entry( reader: &mut B, mut header: AtomHeader, ) -> Result { // An audio sample entry atom is derived from a base sample entry atom. The audio sample entry // atom contains the fields of the base sample entry first, then the audio sample entry fields // next. After those fields, a number of other atoms are nested, including the mandatory // codec-specific atom. Though the codec-specific atom is nested within the (audio) sample entry // atom, the (audio) sample entry atom uses the atom type of the codec-specific atom. This is // odd in-that the final structure will appear to have the codec-specific atom nested within // itself, which is not actually the case. let data_start_pos = reader.pos(); // First 6 bytes of all sample entries should be all 0. reader.ignore_bytes(6)?; // Sample entry data reference. let _ = reader.read_be_u16()?; // The version of the audio sample entry. let version = reader.read_be_u16()?; // Skip revision and vendor. reader.ignore_bytes(6)?; let mut num_channels = u32::from(reader.read_be_u16()?); let sample_size = reader.read_be_u16()?; // Skip compression ID and packet size. reader.ignore_bytes(4)?; let mut sample_rate = f64::from(FpU16::parse_raw(reader.read_be_u32()?)); let is_pcm_codec = is_pcm_codec(header.atype); let mut codec_specific = match version { 0 => { // Version 0. if is_pcm_codec { let codec_type = pcm_codec_type(header.atype); let bits_per_sample = 8 * bytes_per_pcm_sample(codec_type); // Validate the codec-derived bytes-per-sample equals the declared bytes-per-sample. if u32::from(sample_size) != bits_per_sample { return decode_error("isomp4: invalid pcm sample size"); } // The original fields describe the PCM sample format. Some(AudioCodecSpecific::Pcm(Pcm { codec_type: pcm_codec_type(header.atype), bits_per_sample, bits_per_coded_sample: bits_per_sample, frames_per_packet: 1, channels: pcm_channels(num_channels)?, })) } else { None } } 1 => { // Version 1. // The number of frames (ISO/MP4 samples) per packet. For PCM codecs, this is always 1. let _frames_per_packet = reader.read_be_u32()?; // The number of bytes per PCM audio sample. This value supersedes sample_size. For // non-PCM codecs, this value is not useful. let bytes_per_audio_sample = reader.read_be_u32()?; // The number of bytes per PCM audio frame (ISO/MP4 sample). For non-PCM codecs, this // value is not useful. let _bytes_per_frame = reader.read_be_u32()?; // The next value, as defined, is seemingly non-sensical. let _ = reader.read_be_u32()?; if is_pcm_codec { let codec_type = pcm_codec_type(header.atype); let codec_bytes_per_sample = bytes_per_pcm_sample(codec_type); // Validate the codec-derived bytes-per-sample equals the declared bytes-per-sample. if bytes_per_audio_sample != codec_bytes_per_sample { return decode_error("isomp4: invalid pcm bytes per sample"); } // The new fields describe the PCM sample format and supersede the original version // 0 fields. Some(AudioCodecSpecific::Pcm(Pcm { codec_type, bits_per_sample: 8 * codec_bytes_per_sample, bits_per_coded_sample: 8 * codec_bytes_per_sample, frames_per_packet: 1, channels: pcm_channels(num_channels)?, })) } else { None } } 2 => { // Version 2. reader.ignore_bytes(4)?; sample_rate = reader.read_be_f64()?; num_channels = reader.read_be_u32()?; if reader.read_be_u32()? != 0x7f00_0000 { return decode_error("isomp4: audio sample entry v2 reserved must be 0x7f00_0000"); } // The following fields are only useful for PCM codecs. let bits_per_sample = reader.read_be_u32()?; let lpcm_flags = reader.read_be_u32()?; let _bytes_per_packet = reader.read_be_u32()?; let lpcm_frames_per_packet = reader.read_be_u32()?; // This is only valid if this is a PCM codec. let codec_type = lpcm_codec_type(bits_per_sample, lpcm_flags); if is_pcm_codec && codec_type != CODEC_TYPE_NULL { // Like version 1, the new fields describe the PCM sample format and supersede the // original version 0 fields. Some(AudioCodecSpecific::Pcm(Pcm { codec_type, bits_per_sample, bits_per_coded_sample: bits_per_sample, frames_per_packet: u64::from(lpcm_frames_per_packet), channels: lpcm_channels(num_channels)?, })) } else { None } } _ => { return unsupported_error("isomp4: unknown sample entry version"); } }; // Need to account for the data already read from the atom. header.data_len -= reader.pos() - data_start_pos; let mut iter = AtomIterator::new(reader, header); while let Some(entry_header) = iter.next()? { match entry_header.atype { AtomType::Esds => { // MP4A/ESDS codec-specific atom. if header.atype != AtomType::Mp4a || codec_specific.is_some() { return decode_error("isomp4: invalid sample entry"); } codec_specific = Some(AudioCodecSpecific::Esds(iter.read_atom::()?)); } AtomType::Alac => { // ALAC codec-specific atom. if header.atype != AtomType::Alac || codec_specific.is_some() { return decode_error("isomp4: invalid sample entry"); } codec_specific = Some(AudioCodecSpecific::Alac(iter.read_atom::()?)); } AtomType::FlacDsConfig => { // FLAC codec-specific atom. if header.atype != AtomType::Flac || codec_specific.is_some() { return decode_error("isomp4: invalid sample entry"); } codec_specific = Some(AudioCodecSpecific::Flac(iter.read_atom::()?)); } AtomType::OpusDsConfig => { // Opus codec-specific atom. if header.atype != AtomType::Opus || codec_specific.is_some() { return decode_error("isomp4: invalid sample entry"); } codec_specific = Some(AudioCodecSpecific::Opus(iter.read_atom::()?)); } AtomType::QtWave => { // The QuickTime WAVE (aka. siDecompressionParam) atom may contain many different // types of sub-atoms to store decoder parameters. let wave = iter.read_atom::()?; if let Some(esds) = wave.esds { if codec_specific.is_some() { return decode_error("isomp4: invalid sample entry"); } codec_specific = Some(AudioCodecSpecific::Esds(esds)); } } _ => (), } } // A MP3 sample entry has no codec-specific atom. if header.atype == AtomType::Mp3 { if codec_specific.is_some() { return decode_error("isomp4: invalid sample entry"); } codec_specific = Some(AudioCodecSpecific::Mp3); } Ok(SampleEntry::Audio(AudioSampleEntry { num_channels, sample_size, sample_rate, codec_specific, })) } symphonia-format-isomp4-0.5.4/src/atoms/stss.rs000064400000000000000000000012421046102023000176130ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub struct StssAtom { /// Atom header. header: AtomHeader, } impl Atom for StssAtom { fn header(&self) -> AtomHeader { self.header } fn read(_reader: &mut B, _header: AtomHeader) -> Result { todo!() } } symphonia-format-isomp4-0.5.4/src/atoms/stsz.rs000064400000000000000000000030171046102023000176240ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub enum SampleSize { Constant(u32), Variable(Vec), } /// Sample Size Atom #[derive(Debug)] pub struct StszAtom { /// Atom header. header: AtomHeader, /// The total number of samples. pub sample_count: u32, /// A vector of `sample_count` sample sizes, or a constant size for all samples. pub sample_sizes: SampleSize, } impl Atom for StszAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let sample_size = reader.read_be_u32()?; let sample_count = reader.read_be_u32()?; let sample_sizes = if sample_size == 0 { // TODO: Apply a limit. let mut entries = Vec::with_capacity(sample_count as usize); for _ in 0..sample_count { entries.push(reader.read_be_u32()?); } SampleSize::Variable(entries) } else { SampleSize::Constant(sample_size) }; Ok(StszAtom { header, sample_count, sample_sizes }) } } symphonia-format-isomp4-0.5.4/src/atoms/stts.rs000064400000000000000000000067171046102023000176300ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; #[derive(Debug)] pub struct SampleDurationEntry { pub sample_count: u32, pub sample_delta: u32, } /// Time-to-sample atom. #[derive(Debug)] pub struct SttsAtom { /// Atom header. header: AtomHeader, pub entries: Vec, pub total_duration: u64, } impl SttsAtom { /// Get the timestamp and duration for the sample indicated by `sample_num`. Note, `sample_num` /// is indexed relative to the `SttsAtom`. Complexity of this function in O(N). pub fn find_timing_for_sample(&self, sample_num: u32) -> Option<(u64, u32)> { let mut ts = 0; let mut next_entry_first_sample = 0; // The Stts atom compactly encodes a mapping between number of samples and sample duration. // Iterate through each entry until the entry containing the next sample is found. The next // packet timestamp is then the sum of the product of sample count and sample duration for // the n-1 iterated entries, plus the product of the number of consumed samples in the n-th // iterated entry and sample duration. for entry in &self.entries { next_entry_first_sample += entry.sample_count; if sample_num < next_entry_first_sample { let entry_sample_offset = sample_num + entry.sample_count - next_entry_first_sample; ts += u64::from(entry.sample_delta) * u64::from(entry_sample_offset); return Some((ts, entry.sample_delta)); } ts += u64::from(entry.sample_count) * u64::from(entry.sample_delta); } None } /// Get the sample that contains the timestamp indicated by `ts`. Note, the returned `sample_num` /// is indexed relative to the `SttsAtom`. Complexity of this function in O(N). pub fn find_sample_for_timestamp(&self, ts: u64) -> Option { let mut ts_accum = 0; let mut sample_num = 0; for entry in &self.entries { let delta = u64::from(entry.sample_delta) * u64::from(entry.sample_count); if ts_accum + delta > ts { sample_num += ((ts - ts_accum) / u64::from(entry.sample_delta)) as u32; return Some(sample_num); } ts_accum += delta; sample_num += entry.sample_count; } None } } impl Atom for SttsAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; let entry_count = reader.read_be_u32()?; let mut total_duration = 0; // TODO: Limit table length. let mut entries = Vec::with_capacity(entry_count as usize); for _ in 0..entry_count { let sample_count = reader.read_be_u32()?; let sample_delta = reader.read_be_u32()?; total_duration += u64::from(sample_count) * u64::from(sample_delta); entries.push(SampleDurationEntry { sample_count, sample_delta }); } Ok(SttsAtom { header, entries, total_duration }) } } symphonia-format-isomp4-0.5.4/src/atoms/tfhd.rs000064400000000000000000000046571046102023000175610ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; /// Track fragment header atom. #[derive(Debug)] pub struct TfhdAtom { /// Atom header. header: AtomHeader, pub track_id: u32, pub base_data_offset: Option, pub sample_desc_idx: Option, pub default_sample_duration: Option, pub default_sample_size: Option, pub default_sample_flags: Option, /// If true, there are no samples for this time duration. pub duration_is_empty: bool, /// If true, the base data offset for this track is the first byte of the parent containing moof /// atom. pub default_base_is_moof: bool, } impl Atom for TfhdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, flags) = AtomHeader::read_extra(reader)?; let track_id = reader.read_be_u32()?; let base_data_offset = match flags & 0x1 { 0 => None, _ => Some(reader.read_be_u64()?), }; let sample_desc_idx = match flags & 0x2 { 0 => None, _ => Some(reader.read_be_u32()?), }; let default_sample_duration = match flags & 0x8 { 0 => None, _ => Some(reader.read_be_u32()?), }; let default_sample_size = match flags & 0x10 { 0 => None, _ => Some(reader.read_be_u32()?), }; let default_sample_flags = match flags & 0x20 { 0 => None, _ => Some(reader.read_be_u32()?), }; let duration_is_empty = (flags & 0x1_0000) != 0; // The default-base-is-moof flag is ignored if the base-data-offset flag is set. let default_base_is_moof = (flags & 0x1 == 0) && (flags & 0x2_0000 != 0); Ok(TfhdAtom { header, track_id, base_data_offset, sample_desc_idx, default_sample_duration, default_sample_size, default_sample_flags, duration_is_empty, default_base_is_moof, }) } } symphonia-format-isomp4-0.5.4/src/atoms/tkhd.rs000064400000000000000000000052101046102023000175500ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; use crate::fp::FpU8; /// Track header atom. #[derive(Debug)] pub struct TkhdAtom { /// Atom header. header: AtomHeader, /// Track header flags. pub flags: u32, /// Creation time. pub ctime: u64, /// Modification time. pub mtime: u64, /// Track identifier. pub id: u32, /// Track duration in the timescale units specified in the movie header. This value is equal to /// the sum of the durations of all the track's edits. pub duration: u64, /// Layer. pub layer: u16, /// Grouping identifier. pub alternate_group: u16, /// Preferred volume for track playback. pub volume: FpU8, } impl Atom for TkhdAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (version, flags) = AtomHeader::read_extra(reader)?; let mut tkhd = TkhdAtom { header, flags, ctime: 0, mtime: 0, id: 0, duration: 0, layer: 0, alternate_group: 0, volume: Default::default(), }; // Version 0 uses 32-bit time values, verion 1 used 64-bit values. match version { 0 => { tkhd.ctime = u64::from(reader.read_be_u32()?); tkhd.mtime = u64::from(reader.read_be_u32()?); tkhd.id = reader.read_be_u32()?; let _ = reader.read_be_u32()?; // Reserved tkhd.duration = u64::from(reader.read_be_u32()?); } 1 => { tkhd.ctime = reader.read_be_u64()?; tkhd.mtime = reader.read_be_u64()?; tkhd.id = reader.read_be_u32()?; let _ = reader.read_be_u32()?; // Reserved tkhd.duration = reader.read_be_u64()?; } _ => return decode_error("isomp4: invalid tkhd version"), } // Reserved let _ = reader.read_be_u64()?; tkhd.layer = reader.read_be_u16()?; tkhd.alternate_group = reader.read_be_u16()?; tkhd.volume = FpU8::parse_raw(reader.read_be_u16()?); // The remainder of the header is only useful for video tracks. Ok(tkhd) } } symphonia-format-isomp4-0.5.4/src/atoms/traf.rs000064400000000000000000000035321046102023000175570ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, TfhdAtom, TrunAtom}; /// Track fragment atom. #[derive(Debug)] pub struct TrafAtom { /// Atom header. header: AtomHeader, /// Track fragment header. pub tfhd: TfhdAtom, /// Track fragment sample runs. pub truns: Vec, /// The total number of samples in this track fragment. pub total_sample_count: u32, } impl Atom for TrafAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut tfhd = None; let mut truns = Vec::new(); let mut iter = AtomIterator::new(reader, header); let mut total_sample_count = 0; while let Some(header) = iter.next()? { match header.atype { AtomType::TrackFragmentHeader => { tfhd = Some(iter.read_atom::()?); } AtomType::TrackFragmentRun => { let trun = iter.read_atom::()?; // Increment the total sample count. total_sample_count += trun.sample_count; truns.push(trun); } _ => (), } } // Tfhd is mandatory. if tfhd.is_none() { return decode_error("isomp4: missing tfhd atom"); } Ok(TrafAtom { header, tfhd: tfhd.unwrap(), truns, total_sample_count }) } } symphonia-format-isomp4-0.5.4/src/atoms/trak.rs000064400000000000000000000034411046102023000175630ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, EdtsAtom, MdiaAtom, TkhdAtom}; /// Track atom. #[derive(Debug)] pub struct TrakAtom { /// Atom header. header: AtomHeader, /// Track header atom. pub tkhd: TkhdAtom, /// Optional, edit list atom. pub edts: Option, /// Media atom. pub mdia: MdiaAtom, } impl Atom for TrakAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut tkhd = None; let mut edts = None; let mut mdia = None; while let Some(header) = iter.next()? { match header.atype { AtomType::TrackHeader => { tkhd = Some(iter.read_atom::()?); } AtomType::Edit => { edts = Some(iter.read_atom::()?); } AtomType::Media => { mdia = Some(iter.read_atom::()?); } _ => (), } } if tkhd.is_none() { return decode_error("isomp4: missing tkhd atom"); } if mdia.is_none() { return decode_error("isomp4: missing mdia atom"); } Ok(TrakAtom { header, tkhd: tkhd.unwrap(), edts, mdia: mdia.unwrap() }) } } symphonia-format-isomp4-0.5.4/src/atoms/trex.rs000064400000000000000000000025731046102023000176110ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader}; /// Track extends atom. #[derive(Debug)] pub struct TrexAtom { /// Atom header. header: AtomHeader, /// Track this atom describes. pub track_id: u32, /// Default sample description index. pub default_sample_desc_idx: u32, /// Default sample duration. pub default_sample_duration: u32, /// Default sample size. pub default_sample_size: u32, /// Default sample flags. pub default_sample_flags: u32, } impl Atom for TrexAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, _) = AtomHeader::read_extra(reader)?; Ok(TrexAtom { header, track_id: reader.read_be_u32()?, default_sample_desc_idx: reader.read_be_u32()?, default_sample_duration: reader.read_be_u32()?, default_sample_size: reader.read_be_u32()?, default_sample_flags: reader.read_be_u32()?, }) } } symphonia-format-isomp4-0.5.4/src/atoms/trun.rs000064400000000000000000000263001046102023000176110ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Result}; use symphonia_core::io::ReadBytes; use symphonia_core::util::bits; use crate::atoms::{Atom, AtomHeader}; /// Track fragment run atom. #[derive(Debug)] pub struct TrunAtom { /// Atom header. header: AtomHeader, /// Extended header flags. flags: u32, /// Data offset of this run. pub data_offset: Option, /// Number of samples in this run. pub sample_count: u32, /// Sample flags for the first sample only. pub first_sample_flags: Option, /// Sample duration for each sample in this run. pub sample_duration: Vec, /// Sample size for each sample in this run. pub sample_size: Vec, /// Sample flags for each sample in this run. pub sample_flags: Vec, /// The total size of all samples in this run. 0 if the sample size flag is not set. total_sample_size: u64, /// The total duration of all samples in this run. 0 if the sample duration flag is not set. total_sample_duration: u64, } impl TrunAtom { // Track fragment run atom flags. const DATA_OFFSET_PRESENT: u32 = 0x1; const FIRST_SAMPLE_FLAGS_PRESENT: u32 = 0x4; const SAMPLE_DURATION_PRESENT: u32 = 0x100; const SAMPLE_SIZE_PRESENT: u32 = 0x200; const SAMPLE_FLAGS_PRESENT: u32 = 0x400; const SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT: u32 = 0x800; /// Indicates if sample durations are provided. pub fn is_sample_duration_present(&self) -> bool { self.flags & TrunAtom::SAMPLE_DURATION_PRESENT != 0 } // Indicates if the duration of the first sample is provided. pub fn is_first_sample_duration_present(&self) -> bool { match self.first_sample_flags { Some(flags) => flags & TrunAtom::FIRST_SAMPLE_FLAGS_PRESENT != 0, None => false, } } /// Indicates if sample sizes are provided. pub fn is_sample_size_present(&self) -> bool { self.flags & TrunAtom::SAMPLE_SIZE_PRESENT != 0 } /// Indicates if the size for the first sample is provided. pub fn is_first_sample_size_present(&self) -> bool { match self.first_sample_flags { Some(flags) => flags & TrunAtom::SAMPLE_SIZE_PRESENT != 0, None => false, } } /// Indicates if sample flags are provided. #[allow(dead_code)] pub fn are_sample_flags_present(&self) -> bool { self.flags & TrunAtom::SAMPLE_FLAGS_PRESENT != 0 } /// Indicates if sample composition time offsets are provided. #[allow(dead_code)] pub fn are_sample_composition_time_offsets_present(&self) -> bool { self.flags & TrunAtom::SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT != 0 } /// Gets the total duration of all samples. pub fn total_duration(&self, default_dur: u32) -> u64 { if self.is_sample_duration_present() { self.total_sample_duration } else { // The duration of all samples in the track fragment are not explictly known. if self.sample_count > 0 && self.is_first_sample_duration_present() { // The first sample has an explictly recorded duration. u64::from(self.sample_duration[0]) + u64::from(self.sample_count - 1) * u64::from(default_dur) } else { // All samples have the default duration. u64::from(self.sample_count) * u64::from(default_dur) } } } /// Gets the total size of all samples. pub fn total_size(&self, default_size: u32) -> u64 { if self.is_sample_size_present() { self.total_sample_size } else if self.sample_count > 0 && self.is_first_sample_size_present() { u64::from(self.sample_size[0]) + u64::from(self.sample_count - 1) * u64::from(default_size) } else { u64::from(self.sample_count) * u64::from(default_size) } } /// Get the timestamp and duration of a sample. The desired sample is specified by the /// trun-relative sample number, `sample_num_rel`. pub fn sample_timing(&self, sample_num_rel: u32, default_dur: u32) -> (u64, u32) { debug_assert!(sample_num_rel < self.sample_count); if self.is_sample_duration_present() { // All sample durations are unique. let ts = if sample_num_rel > 0 { self.sample_duration[..sample_num_rel as usize] .iter() .map(|&s| u64::from(s)) .sum::() } else { 0 }; let dur = self.sample_duration[sample_num_rel as usize]; (ts, dur) } else { // The duration of all samples in the track fragment are not unique. let ts = if sample_num_rel > 0 && self.is_first_sample_duration_present() { // The first sample has a unique duration. u64::from(self.sample_duration[0]) + u64::from(sample_num_rel - 1) * u64::from(default_dur) } else { // Zero or more samples with identical durations. u64::from(sample_num_rel) * u64::from(default_dur) }; (ts, default_dur) } } /// Get the size of a sample. The desired sample is specified by the trun-relative sample /// number, `sample_num_rel`. pub fn sample_size(&self, sample_num_rel: u32, default_size: u32) -> u32 { debug_assert!(sample_num_rel < self.sample_count); if self.is_sample_size_present() { self.sample_size[sample_num_rel as usize] } else if sample_num_rel == 0 && self.is_first_sample_size_present() { self.sample_size[0] } else { default_size } } /// Get the byte offset and size of a sample. The desired sample is specified by the /// trun-relative sample number, `sample_num_rel`. pub fn sample_offset(&self, sample_num_rel: u32, default_size: u32) -> (u64, u32) { debug_assert!(sample_num_rel < self.sample_count); if self.is_sample_size_present() { // All sample sizes are unique. let offset = if sample_num_rel > 0 { self.sample_size[..sample_num_rel as usize] .iter() .map(|&s| u64::from(s)) .sum::() } else { 0 }; (offset, self.sample_size[sample_num_rel as usize]) } else { // The size of all samples in the track are not unique. let offset = if sample_num_rel > 0 && self.is_first_sample_size_present() { // The first sample has a unique size. u64::from(self.sample_size[0]) + u64::from(sample_num_rel - 1) * u64::from(default_size) } else { // Zero or more identically sized samples. u64::from(sample_num_rel) * u64::from(default_size) }; (offset, default_size) } } /// Get the sample number (relative to the trun) of the sample that contains timestamp `ts`. pub fn ts_sample(&self, ts_rel: u64, default_dur: u32) -> u32 { let mut sample_num = 0; let mut ts_delta = ts_rel; if self.is_sample_duration_present() { // If the sample durations are present, then each sample duration is independently // stored. Sum sample durations until the delta is reached. for &dur in &self.sample_duration { if u64::from(dur) > ts_delta { break; } ts_delta -= u64::from(dur); sample_num += 1; } } else { if self.sample_count > 0 && self.is_first_sample_duration_present() { // The first sample duration is unique. let first_sample_dur = u64::from(self.sample_duration[0]); if ts_delta >= first_sample_dur { ts_delta -= first_sample_dur; sample_num += 1; } else { ts_delta -= ts_delta; } } sample_num += ts_delta.checked_div(u64::from(default_dur)).unwrap_or(0) as u32; } sample_num } } impl Atom for TrunAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let (_, flags) = AtomHeader::read_extra(reader)?; let sample_count = reader.read_be_u32()?; let data_offset = match flags & TrunAtom::DATA_OFFSET_PRESENT { 0 => None, _ => Some(bits::sign_extend_leq32_to_i32(reader.read_be_u32()?, 32)), }; let first_sample_flags = match flags & TrunAtom::FIRST_SAMPLE_FLAGS_PRESENT { 0 => None, _ => Some(reader.read_be_u32()?), }; // If the first-sample-flags-present flag is set, then the sample-flags-present flag should // not be set. The samples after the first shall use the default sample flags defined in the // tfhd or mvex atoms. if first_sample_flags.is_some() && (flags & TrunAtom::SAMPLE_FLAGS_PRESENT != 0) { return decode_error( "isomp4: sample-flag-present and first-sample-flags-present flags are set", ); } let mut sample_duration = Vec::new(); let mut sample_size = Vec::new(); let mut sample_flags = Vec::new(); let mut total_sample_size = 0; let mut total_sample_duration = 0; // TODO: Apply a limit. for _ in 0..sample_count { if (flags & TrunAtom::SAMPLE_DURATION_PRESENT) != 0 { let duration = reader.read_be_u32()?; total_sample_duration += u64::from(duration); sample_duration.push(duration); } if (flags & TrunAtom::SAMPLE_SIZE_PRESENT) != 0 { let size = reader.read_be_u32()?; total_sample_size += u64::from(size); sample_size.push(size); } if (flags & TrunAtom::SAMPLE_FLAGS_PRESENT) != 0 { sample_flags.push(reader.read_be_u32()?); } // Ignoring composition time for now since it's a video thing... if (flags & TrunAtom::SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT) != 0 { // For version 0, this is a u32. // For version 1, this is a i32. let _ = reader.read_be_u32()?; } } Ok(TrunAtom { header, flags, data_offset, sample_count, first_sample_flags, sample_duration, sample_size, sample_flags, total_sample_size, total_sample_duration, }) } } symphonia-format-isomp4-0.5.4/src/atoms/udta.rs000064400000000000000000000026441046102023000175630ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use symphonia_core::meta::MetadataRevision; use crate::atoms::{Atom, AtomHeader, AtomIterator, AtomType, MetaAtom}; /// User data atom. #[derive(Debug)] pub struct UdtaAtom { /// Atom header. header: AtomHeader, /// Metadata atom. pub meta: Option, } impl UdtaAtom { /// If metadata was read, consumes the metadata and returns it. pub fn take_metadata(&mut self) -> Option { self.meta.as_mut().and_then(|meta| meta.take_metadata()) } } impl Atom for UdtaAtom { fn header(&self) -> AtomHeader { self.header } #[allow(clippy::single_match)] fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut meta = None; while let Some(header) = iter.next()? { match header.atype { AtomType::Meta => { meta = Some(iter.read_atom::()?); } _ => (), } } Ok(UdtaAtom { header, meta }) } } symphonia-format-isomp4-0.5.4/src/atoms/wave.rs000064400000000000000000000020241046102023000175600ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::Result; use symphonia_core::io::ReadBytes; use crate::atoms::{Atom, AtomHeader, EsdsAtom}; use super::{AtomIterator, AtomType}; #[derive(Debug)] pub struct WaveAtom { /// Atom header. header: AtomHeader, pub esds: Option, } impl Atom for WaveAtom { fn header(&self) -> AtomHeader { self.header } fn read(reader: &mut B, header: AtomHeader) -> Result { let mut iter = AtomIterator::new(reader, header); let mut esds = None; while let Some(header) = iter.next()? { if header.atype == AtomType::Esds { esds = Some(iter.read_atom::()?); } } Ok(WaveAtom { header, esds }) } } symphonia-format-isomp4-0.5.4/src/demuxer.rs000064400000000000000000000560161046102023000171560ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::{errors::end_of_stream_error, support_format}; use symphonia_core::codecs::CodecParameters; use symphonia_core::errors::{decode_error, seek_error, unsupported_error, Result, SeekErrorKind}; use symphonia_core::formats::prelude::*; use symphonia_core::io::{MediaSource, MediaSourceStream, ReadBytes, SeekBuffered}; use symphonia_core::meta::{Metadata, MetadataLog}; use symphonia_core::probe::{Descriptor, Instantiate, QueryDescriptor}; use symphonia_core::units::Time; use std::io::{Seek, SeekFrom}; use std::sync::Arc; use crate::atoms::{AtomIterator, AtomType}; use crate::atoms::{FtypAtom, MetaAtom, MoofAtom, MoovAtom, MvexAtom, SidxAtom, TrakAtom}; use crate::stream::*; use log::{debug, info, trace, warn}; pub struct TrackState { codec_params: CodecParameters, /// The track number. track_num: usize, /// The current segment. cur_seg: usize, /// The current sample index relative to the track. next_sample: u32, /// The current sample byte position relative to the start of the track. next_sample_pos: u64, } impl TrackState { #[allow(clippy::single_match)] pub fn new(track_num: usize, trak: &TrakAtom) -> Self { let mut codec_params = CodecParameters::new(); codec_params .with_time_base(TimeBase::new(1, trak.mdia.mdhd.timescale)) .with_n_frames(trak.mdia.mdhd.duration); // Fill the codec parameters using the sample description atom. trak.mdia.minf.stbl.stsd.fill_codec_params(&mut codec_params); Self { codec_params, track_num, cur_seg: 0, next_sample: 0, next_sample_pos: 0 } } pub fn codec_params(&self) -> CodecParameters { self.codec_params.clone() } } /// Information regarding the next sample. #[derive(Debug)] struct NextSampleInfo { /// The track number of the next sample. track_num: usize, /// The timestamp of the next sample. ts: u64, /// The timestamp expressed in seconds. time: Time, /// The duration of the next sample. dur: u32, /// The segment containing the next sample. seg_idx: usize, } /// Information regarding a sample. #[derive(Debug)] struct SampleDataInfo { /// The position of the sample in the track. pos: u64, /// The length of the sample. len: u32, } /// ISO Base Media File Format (MP4, M4A, MOV, etc.) demultiplexer. /// /// `IsoMp4Reader` implements a demuxer for the ISO Base Media File Format. pub struct IsoMp4Reader { iter: AtomIterator, tracks: Vec, cues: Vec, metadata: MetadataLog, /// Segments of the movie. Sorted in ascending order by sequence number. segs: Vec>, /// State tracker for each track. track_states: Vec, /// Optional, movie extends atom used for fragmented streams. mvex: Option>, } impl IsoMp4Reader { /// Idempotently gets information regarding the next sample of the media stream. This function /// selects the next sample with the lowest timestamp of all tracks. fn next_sample_info(&self) -> Result> { let mut earliest = None; // TODO: Consider returning samples based on lowest byte position in the track instead of // timestamp. This may be important if video tracks are ever decoded (i.e., DTS vs. PTS). for (state, track) in self.track_states.iter().zip(&self.tracks) { // Get the timebase of the track used to calculate the presentation time. let tb = track.codec_params.time_base.unwrap(); // Get the next timestamp for the next sample of the current track. The next sample may // be in a future segment. for (seg_idx_delta, seg) in self.segs[state.cur_seg..].iter().enumerate() { // Try to get the timestamp for the next sample of the track from the segment. if let Some(timing) = seg.sample_timing(state.track_num, state.next_sample)? { // Calculate the presentation time using the timestamp. let sample_time = tb.calc_time(timing.ts); // Compare the presentation time of the sample from this track to other tracks, // and select the track with the earliest presentation time. match earliest { Some(NextSampleInfo { track_num: _, ts: _, time, dur: _, seg_idx: _ }) if time <= sample_time => { // Earliest is less than or equal to the track's next sample // presentation time. No need to update earliest. } _ => { // Earliest was either None, or greater than the track's next sample // presentation time. Update earliest. earliest = Some(NextSampleInfo { track_num: state.track_num, ts: timing.ts, time: sample_time, dur: timing.dur, seg_idx: seg_idx_delta + state.cur_seg, }); } } // Either the next sample of the track had the earliest presentation time seen // thus far, or it was greater than those from other tracks, but there is no // reason to check samples in future segments. break; } } } Ok(earliest) } fn consume_next_sample(&mut self, info: &NextSampleInfo) -> Result> { // Get the track state. let track = &mut self.track_states[info.track_num]; // Get the segment associated with the sample. let seg = &self.segs[info.seg_idx]; // Get the sample data descriptor. let sample_data_desc = seg.sample_data(track.track_num, track.next_sample, false)?; // The sample base position in the sample data descriptor remains constant if the sample // followed immediately after the previous sample. In this case, the track state's // next_sample_pos is the position of the current sample. If the base position has jumped, // then the base position is the position of current the sample. let pos = if sample_data_desc.base_pos > track.next_sample_pos { sample_data_desc.base_pos } else { track.next_sample_pos }; // Advance the track's current segment to the next sample's segment. track.cur_seg = info.seg_idx; // Advance the track's next sample number and position. track.next_sample += 1; track.next_sample_pos = pos + u64::from(sample_data_desc.size); Ok(Some(SampleDataInfo { pos, len: sample_data_desc.size })) } fn try_read_more_segments(&mut self) -> Result<()> { // Continue iterating over atoms until a segment (a moof + mdat atom pair) is found. All // other atoms will be ignored. while let Some(header) = self.iter.next_no_consume()? { match header.atype { AtomType::MediaData => { // Consume the atom from the iterator so that on the next iteration a new atom // will be read. self.iter.consume_atom(); return Ok(()); } AtomType::MovieFragment => { let moof = self.iter.read_atom::()?; // A moof segment can only be created if the mvex atom is present. if let Some(mvex) = &self.mvex { // Get the last segment. Note, there will always be one segment because the // moov atom is converted into a segment when the reader is instantiated. let last_seg = self.segs.last().unwrap(); // Create a new segment for the moof atom. let seg = MoofSegment::new(moof, mvex.clone(), last_seg.as_ref()); // Segments should have a monotonic sequence number. if seg.sequence_num() <= last_seg.sequence_num() { warn!("moof fragment has a non-monotonic sequence number."); } // Push the segment. self.segs.push(Box::new(seg)); } else { // TODO: This is a fatal error. return decode_error("isomp4: moof atom present without mvex atom"); } } _ => { trace!("skipping atom: {:?}.", header.atype); self.iter.consume_atom(); } } } // If no atoms were returned above, then the end-of-stream has been reached. end_of_stream_error() } fn seek_track_by_time(&mut self, track_num: usize, time: Time) -> Result { // Convert time to timestamp for the track. if let Some(track) = self.tracks.get(track_num) { let tb = track.codec_params.time_base.unwrap(); self.seek_track_by_ts(track_num, tb.calc_timestamp(time)) } else { seek_error(SeekErrorKind::Unseekable) } } fn seek_track_by_ts(&mut self, track_num: usize, ts: u64) -> Result { debug!("seeking track={} to frame_ts={}", track_num, ts); struct SeekLocation { seg_idx: usize, sample_num: u32, } let mut seek_loc = None; let mut seg_skip = 0; loop { // Iterate over all segments and attempt to find the segment and sample number that // contains the desired timestamp. Skip segments already examined. for (seg_idx, seg) in self.segs.iter().enumerate().skip(seg_skip) { if let Some(sample_num) = seg.ts_sample(track_num, ts)? { seek_loc = Some(SeekLocation { seg_idx, sample_num }); break; } // Mark the segment as examined. seg_skip = seg_idx + 1; } // If a seek location is found, break. if seek_loc.is_some() { break; } // Otherwise, try to read more segments from the stream. self.try_read_more_segments()?; } if let Some(seek_loc) = seek_loc { let seg = &self.segs[seek_loc.seg_idx]; // Get the sample information. let data_desc = seg.sample_data(track_num, seek_loc.sample_num, true)?; // Update the track's next sample information to point to the seeked sample. let track = &mut self.track_states[track_num]; track.cur_seg = seek_loc.seg_idx; track.next_sample = seek_loc.sample_num; track.next_sample_pos = data_desc.base_pos + data_desc.offset.unwrap(); // Get the actual timestamp for this sample. let timing = seg.sample_timing(track_num, seek_loc.sample_num)?.unwrap(); debug!( "seeked track={} to packet_ts={} (delta={})", track_num, timing.ts, timing.ts as i64 - ts as i64 ); Ok(SeekedTo { track_id: track_num as u32, required_ts: ts, actual_ts: timing.ts }) } else { // Timestamp was not found. seek_error(SeekErrorKind::OutOfRange) } } } impl QueryDescriptor for IsoMp4Reader { fn query() -> &'static [Descriptor] { &[support_format!( "isomp4", "ISO Base Media File Format", &["mp4", "m4a", "m4p", "m4b", "m4r", "m4v", "mov"], &["video/mp4", "audio/m4a"], &[b"ftyp"] // Top-level atoms )] } fn score(_context: &[u8]) -> u8 { 255 } } impl FormatReader for IsoMp4Reader { fn try_new(mut mss: MediaSourceStream, _options: &FormatOptions) -> Result { // To get to beginning of the atom. mss.seek_buffered_rel(-4); let is_seekable = mss.is_seekable(); let mut ftyp = None; let mut moov = None; let mut sidx = None; // Get the total length of the stream, if possible. let total_len = if is_seekable { let pos = mss.pos(); let len = mss.seek(SeekFrom::End(0))?; mss.seek(SeekFrom::Start(pos))?; info!("stream is seekable with len={} bytes.", len); Some(len) } else { None }; let mut metadata = MetadataLog::default(); // Parse all atoms if the stream is seekable, otherwise parse all atoms up-to the mdat atom. let mut iter = AtomIterator::new_root(mss, total_len); while let Some(header) = iter.next()? { // Top-level atoms. match header.atype { AtomType::FileType => { ftyp = Some(iter.read_atom::()?); } AtomType::Movie => { moov = Some(iter.read_atom::()?); } AtomType::SegmentIndex => { // If the stream is not seekable, then it can only be assumed that the first // segment index atom is indeed the first segment index because the format // reader cannot practically skip past this point. if !is_seekable { sidx = Some(iter.read_atom::()?); break; } else { // If the stream is seekable, examine all segment indexes and select the // index with the earliest presentation timestamp to be the first. let new_sidx = iter.read_atom::()?; let is_earlier = match &sidx { Some(sidx) => new_sidx.earliest_pts < sidx.earliest_pts, _ => true, }; if is_earlier { sidx = Some(new_sidx); } } } AtomType::MediaData | AtomType::MovieFragment => { // The mdat atom contains the codec bitstream data. For segmented streams, a // moof + mdat pair is required for playback. If the source is unseekable then // the format reader cannot skip past these atoms without dropping samples. if !is_seekable { // If the moov atom hasn't been seen before the moof and/or mdat atom, and // the stream is not seekable, then the mp4 is not streamable. if moov.is_none() || ftyp.is_none() { warn!("mp4 is not streamable."); } // The remainder of the stream will be read incrementally. break; } } AtomType::Meta => { // Read the metadata atom and append it to the log. let mut meta = iter.read_atom::()?; if let Some(rev) = meta.take_metadata() { metadata.push(rev); } } AtomType::Free => (), AtomType::Skip => (), _ => { info!("skipping top-level atom: {:?}.", header.atype); } } } if ftyp.is_none() { return unsupported_error("isomp4: missing ftyp atom"); } if moov.is_none() { return unsupported_error("isomp4: missing moov atom"); } // If the stream was seekable, then all atoms in the media source stream were scanned. Seek // back to the first mdat atom for playback. If the stream is not seekable, then the atom // iterator is currently positioned at the first mdat atom. if is_seekable { let mut mss = iter.into_inner(); mss.seek(SeekFrom::Start(0))?; iter = AtomIterator::new_root(mss, total_len); while let Some(header) = iter.next_no_consume()? { match header.atype { AtomType::MediaData | AtomType::MovieFragment => break, _ => (), } iter.consume_atom(); } } let mut moov = moov.unwrap(); if moov.is_fragmented() { // If a Segment Index (sidx) atom was found, add the segments contained within. if sidx.is_some() { info!("stream is segmented with a segment index."); } else { info!("stream is segmented without a segment index."); } } if let Some(rev) = moov.take_metadata() { metadata.push(rev); } // Instantiate a TrackState for each track in the stream. let track_states = moov .traks .iter() .enumerate() .map(|(t, trak)| TrackState::new(t, trak)) .collect::>(); // Instantiate a Tracks for all tracks above. let tracks = track_states .iter() .map(|track| Track::new(track.track_num as u32, track.codec_params())) .collect(); // A Movie Extends (mvex) atom is required to support segmented streams. If the mvex atom is // present, wrap it in an Arc so it can be shared amongst all segments. let mvex = moov.mvex.take().map(Arc::new); // The number of tracks specified in the moov atom must match the number in the mvex atom. if let Some(mvex) = &mvex { if mvex.trexs.len() != moov.traks.len() { return decode_error("isomp4: mvex and moov track number mismatch"); } } let segs: Vec> = vec![Box::new(MoovSegment::new(moov))]; Ok(IsoMp4Reader { iter, tracks, cues: Default::default(), metadata, track_states, segs, mvex, }) } fn next_packet(&mut self) -> Result { // Get the index of the track with the next-nearest (minimum) timestamp. let next_sample_info = loop { // Using the current set of segments, try to get the next sample info. if let Some(info) = self.next_sample_info()? { break info; } else { // No more segments. If the stream is unseekable, it may be the case that there are // more segments coming. Iterate atoms until a new segment is found or the // end-of-stream is reached. self.try_read_more_segments()?; } }; // Get the position and length information of the next sample. let sample_info = self.consume_next_sample(&next_sample_info)?.unwrap(); let reader = self.iter.inner_mut(); // Attempt a fast seek within the buffer cache. if reader.seek_buffered(sample_info.pos) != sample_info.pos { if reader.is_seekable() { // Fallback to a slow seek if the stream is seekable. reader.seek(SeekFrom::Start(sample_info.pos))?; } else if sample_info.pos > reader.pos() { // The stream is not seekable but the desired seek position is ahead of the reader's // current position, thus the seek can be emulated by ignoring the bytes up to the // the desired seek position. reader.ignore_bytes(sample_info.pos - reader.pos())?; } else { // The stream is not seekable and the desired seek position falls outside the lower // bound of the buffer cache. This sample cannot be read. return decode_error("isomp4: packet out-of-bounds for a non-seekable stream"); } } Ok(Packet::new_from_boxed_slice( next_sample_info.track_num as u32, next_sample_info.ts, u64::from(next_sample_info.dur), reader.read_boxed_slice_exact(sample_info.len as usize)?, )) } fn metadata(&mut self) -> Metadata<'_> { self.metadata.metadata() } fn cues(&self) -> &[Cue] { &self.cues } fn tracks(&self) -> &[Track] { &self.tracks } fn seek(&mut self, _mode: SeekMode, to: SeekTo) -> Result { if self.tracks.is_empty() { return seek_error(SeekErrorKind::Unseekable); } match to { SeekTo::TimeStamp { ts, track_id } => { let selected_track_id = track_id as usize; // The seek timestamp is in timebase units specific to the selected track. Get the // selected track and use the timebase to convert the timestamp into time units so // that the other tracks can be seeked. if let Some(selected_track) = self.tracks().get(selected_track_id) { // Convert to time units. let time = selected_track.codec_params.time_base.unwrap().calc_time(ts); // Seek all tracks excluding the primary track to the desired time. for t in 0..self.track_states.len() { if t != selected_track_id { self.seek_track_by_time(t, time)?; } } // Seek the primary track and return the result. self.seek_track_by_ts(selected_track_id, ts) } else { seek_error(SeekErrorKind::Unseekable) } } SeekTo::Time { time, track_id } => { // Select the first track if a selected track was not provided. let selected_track_id = track_id.unwrap_or(0) as usize; // Seek all tracks excluding the selected track and discard the result. for t in 0..self.track_states.len() { if t != selected_track_id { self.seek_track_by_time(t, time)?; } } // Seek the primary track and return the result. self.seek_track_by_time(selected_track_id, time) } } } fn into_inner(self: Box) -> MediaSourceStream { self.iter.into_inner() } } symphonia-format-isomp4-0.5.4/src/fourcc.rs000064400000000000000000000015401046102023000167560ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::fmt; /// Four character codes for typical Ftyps (reference: http://ftyps.com/). #[derive(PartialEq, Eq, Clone, Copy)] #[repr(transparent)] pub struct FourCc { val: [u8; 4], } impl FourCc { /// Construct a new FourCC code from the given byte array. pub fn new(val: [u8; 4]) -> Self { Self { val } } } impl fmt::Debug for FourCc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match std::str::from_utf8(&self.val) { Ok(name) => f.write_str(name), _ => write!(f, "{:x?}", self.val), } } } symphonia-format-isomp4-0.5.4/src/fp.rs000064400000000000000000000031521046102023000161030ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. /// An unsigned 16.16-bit fixed point value. #[derive(Copy, Clone, Debug, Default)] pub struct FpU16(u32); impl FpU16 { pub fn new(val: u16) -> Self { Self(u32::from(val) << 16) } pub fn parse_raw(val: u32) -> Self { Self(val) } } impl From for f64 { fn from(fp: FpU16) -> Self { f64::from(fp.0) / f64::from(1u32 << 16) } } /// An unsigned 8.8-bit fixed point value. #[derive(Copy, Clone, Debug, Default)] pub struct FpU8(u16); impl FpU8 { pub fn new(val: u8) -> Self { Self(u16::from(val) << 8) } pub fn parse_raw(val: u16) -> Self { Self(val) } } impl From for f64 { fn from(fp: FpU8) -> Self { f64::from(fp.0) / f64::from(1u16 << 8) } } impl From for f32 { fn from(fp: FpU8) -> Self { f32::from(fp.0) / f32::from(1u16 << 8) } } /// An unsigned 8.8-bit fixed point value. #[derive(Copy, Clone, Debug, Default)] pub struct FpI8(i16); impl FpI8 { pub fn new(val: i8) -> Self { Self(i16::from(val) * 0x100) } pub fn parse_raw(val: i16) -> Self { Self(val) } } impl From for f64 { fn from(fp: FpI8) -> Self { f64::from(fp.0) / f64::from(1u16 << 8) } } impl From for f32 { fn from(fp: FpI8) -> Self { f32::from(fp.0) / f32::from(1u16 << 8) } } symphonia-format-isomp4-0.5.4/src/lib.rs000064400000000000000000000012171046102023000162440ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. #![warn(rust_2018_idioms)] // The following lints are allowed in all Symphonia crates. Please see clippy.toml for their // justification. #![allow(clippy::comparison_chain)] #![allow(clippy::excessive_precision)] #![allow(clippy::identity_op)] #![allow(clippy::manual_range_contains)] mod atoms; mod demuxer; mod fourcc; mod fp; mod stream; pub use demuxer::IsoMp4Reader; symphonia-format-isomp4-0.5.4/src/stream.rs000064400000000000000000000372141046102023000167770ustar 00000000000000// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use symphonia_core::errors::{decode_error, Error, Result}; use crate::atoms::{stsz::SampleSize, Co64Atom, MoofAtom, MoovAtom, MvexAtom, StcoAtom, TrafAtom}; use std::ops::Range; use std::sync::Arc; /// Sample data information. pub struct SampleDataDesc { /// The starting byte position within the media data of the group of samples that contains the /// sample described. pub base_pos: u64, /// The offset relative to the base position of the sample described. pub offset: Option, /// The size of the sample. pub size: u32, } /// Timing information for one sample. pub struct SampleTiming { /// The timestamp of the sample. pub ts: u64, /// The duration of the sample. pub dur: u32, } pub trait StreamSegment: Send + Sync { /// Gets the sequence number of this segment. fn sequence_num(&self) -> u32; /// Gets the first and last sample numbers for the track `track_num`. fn track_sample_range(&self, track_num: usize) -> Range; /// Gets the first and last sample timestamps for the track `track_num`. fn track_ts_range(&self, track_num: usize) -> Range; /// Get the timestamp and duration for the sample indicated by `sample_num` for the track /// `track_num`. fn sample_timing(&self, track_num: usize, sample_num: u32) -> Result>; /// Get the sample number of the sample containing the timestamp indicated by `ts` for track // `track_num`. fn ts_sample(&self, track_num: usize, ts: u64) -> Result>; /// Get the byte position of the group of samples containing the sample indicated by /// `sample_num` for track `track_num`, and it's size. /// /// Optionally, the offset of the sample relative to the aforementioned byte position can be /// returned. fn sample_data( &self, track_num: usize, sample_num: u32, get_offset: bool, ) -> Result; } /// Track-to-stream sequencing information. #[derive(Copy, Clone, Debug, Default)] struct SequenceInfo { /// The sample number of the first sample of a track in a fragment. first_sample: u32, /// The timestamp of the first sample of a track in a fragment. first_ts: u64, /// The total duration of all samples of a track in a fragment. total_sample_duration: u64, /// The total sample count of a track in a fragment. total_sample_count: u32, /// If present in the moof segment, this is the index of the track fragment atom for the track /// this sequence information is associated with. traf_idx: Option, } pub struct MoofSegment { moof: MoofAtom, mvex: Arc, seq: Vec, } impl MoofSegment { /// Instantiate a new segment from a `MoofAtom`. pub fn new(moof: MoofAtom, mvex: Arc, prev: &dyn StreamSegment) -> MoofSegment { let mut seq = Vec::with_capacity(mvex.trexs.len()); // Calculate the sequence information for each track, even if not present in the fragment. for (track_num, trex) in mvex.trexs.iter().enumerate() { let mut info = SequenceInfo { first_sample: prev.track_sample_range(track_num).end, first_ts: prev.track_ts_range(track_num).end, ..Default::default() }; // Find the track fragment for the track. for (traf_idx, traf) in moof.trafs.iter().enumerate() { if trex.track_id != traf.tfhd.track_id { continue; } // Calculate the total duration of all runs in the fragment for the track. let default_dur = traf.tfhd.default_sample_duration.unwrap_or(trex.default_sample_duration); for trun in traf.truns.iter() { info.total_sample_duration += trun.total_duration(default_dur); } info.total_sample_count = traf.total_sample_count; info.traf_idx = Some(traf_idx); } seq.push(info); } MoofSegment { moof, mvex, seq } } /// Try to get the Track Fragment atom associated with the track identified by `track_num`. fn try_get_traf(&self, track_num: usize) -> Option<&TrafAtom> { debug_assert!(track_num < self.seq.len()); self.seq[track_num].traf_idx.map(|idx| &self.moof.trafs[idx]) } } impl StreamSegment for MoofSegment { fn sequence_num(&self) -> u32 { self.moof.mfhd.sequence_number } fn sample_timing(&self, track_num: usize, sample_num: u32) -> Result> { // Get the track fragment associated with track_num. let traf = match self.try_get_traf(track_num) { Some(traf) => traf, None => return Ok(None), }; let mut sample_num_rel = sample_num - self.seq[track_num].first_sample; let mut trun_ts_offset = self.seq[track_num].first_ts; let default_dur = traf .tfhd .default_sample_duration .unwrap_or(self.mvex.trexs[track_num].default_sample_duration); for trun in traf.truns.iter() { // If the sample is contained within the this track run, get the timing of of the // sample. if sample_num_rel < trun.sample_count { let (ts, dur) = trun.sample_timing(sample_num_rel, default_dur); return Ok(Some(SampleTiming { ts: trun_ts_offset + ts, dur })); } let trun_dur = trun.total_duration(default_dur); sample_num_rel -= trun.sample_count; trun_ts_offset += trun_dur; } Ok(None) } fn ts_sample(&self, track_num: usize, ts: u64) -> Result> { // Get the track fragment associated with track_num. let traf = match self.try_get_traf(track_num) { Some(traf) => traf, None => return Ok(None), }; let mut sample_num = self.seq[track_num].first_sample; let mut ts_accum = self.seq[track_num].first_ts; let default_dur = traf .tfhd .default_sample_duration .unwrap_or(self.mvex.trexs[track_num].default_sample_duration); for trun in traf.truns.iter() { // Get the total duration of this track run. let trun_dur = trun.total_duration(default_dur); // If the timestamp after the track run is greater than the desired timestamp, then the // desired sample must be in this run of samples. if ts_accum + trun_dur > ts { sample_num += trun.ts_sample(ts - ts_accum, default_dur); return Ok(Some(sample_num)); } sample_num += trun.sample_count; ts_accum += trun_dur; } Ok(None) } fn sample_data( &self, track_num: usize, sample_num: u32, get_offset: bool, ) -> Result { // Get the track fragment associated with track_num. let traf = self.try_get_traf(track_num).unwrap(); // If an explicit anchor-point is set, then use that for the position, otherwise use the // first-byte of the enclosing moof atom. let traf_base_pos = match traf.tfhd.base_data_offset { Some(pos) => pos, _ => self.moof.moof_base_pos, }; let mut sample_num_rel = sample_num - self.seq[track_num].first_sample; let mut trun_offset = traf_base_pos; let default_size = traf.tfhd.default_sample_size.unwrap_or(self.mvex.trexs[track_num].default_sample_size); for trun in traf.truns.iter() { // If a data offset is present for this track fragment run, then calculate the new base // position for the run. When a data offset is not present, do nothing because this run // follows the previous run. if let Some(offset) = trun.data_offset { // The offset for the run is relative to the anchor-point defined in the track // fragment header. trun_offset = if offset.is_negative() { traf_base_pos - u64::from(offset.wrapping_abs() as u32) } else { traf_base_pos + offset as u64 }; } if sample_num_rel < trun.sample_count { let (offset, size) = if get_offset { // Get the size and offset of the sample. let (offset, size) = trun.sample_offset(sample_num_rel, default_size); (Some(offset), size) } else { // Just get the size of the sample. let size = trun.sample_size(sample_num_rel, default_size); (None, size) }; return Ok(SampleDataDesc { base_pos: trun_offset, size, offset }); } // Get the total size of the track fragment run. let trun_size = trun.total_size(default_size); sample_num_rel -= trun.sample_count; trun_offset += trun_size; } decode_error("isomp4: invalid sample index") } fn track_sample_range(&self, track_num: usize) -> Range { debug_assert!(track_num < self.seq.len()); let track = &self.seq[track_num]; track.first_sample..track.first_sample + track.total_sample_count } fn track_ts_range(&self, track_num: usize) -> Range { debug_assert!(track_num < self.seq.len()); let track = &self.seq[track_num]; track.first_ts..track.first_ts + track.total_sample_duration } } fn get_chunk_offset( stco: &Option, co64: &Option, chunk: usize, ) -> Result> { // Get the offset from either the stco or co64 atoms. if let Some(stco) = stco.as_ref() { // 32-bit offset if let Some(offset) = stco.chunk_offsets.get(chunk) { Ok(Some(u64::from(*offset))) } else { decode_error("isomp4: missing stco entry") } } else if let Some(co64) = co64.as_ref() { // 64-bit offset if let Some(offset) = co64.chunk_offsets.get(chunk) { Ok(Some(*offset)) } else { decode_error("isomp4: missing co64 entry") } } else { // This should never happen because it is mandatory to have either a stco or co64 atom. decode_error("isomp4: missing stco or co64 atom") } } pub struct MoovSegment { moov: MoovAtom, } impl MoovSegment { /// Instantiate a segment from the provide moov atom. pub fn new(moov: MoovAtom) -> MoovSegment { MoovSegment { moov } } } impl StreamSegment for MoovSegment { fn sequence_num(&self) -> u32 { // The segment defined by the moov atom is always 0. 0 } fn sample_timing(&self, track_num: usize, sample_num: u32) -> Result> { // Get the trak atom associated with track_num. debug_assert!(track_num < self.moov.traks.len()); let trak = &self.moov.traks[track_num]; // Find the sample timing. Note, complexity of O(N). let timing = trak.mdia.minf.stbl.stts.find_timing_for_sample(sample_num); if let Some((ts, dur)) = timing { Ok(Some(SampleTiming { ts, dur })) } else { Ok(None) } } fn ts_sample(&self, track_num: usize, ts: u64) -> Result> { // Get the trak atom associated with track_num. debug_assert!(track_num < self.moov.traks.len()); let trak = &self.moov.traks[track_num]; // Find the sample timestamp. Note, complexity of O(N). Ok(trak.mdia.minf.stbl.stts.find_sample_for_timestamp(ts)) } fn sample_data( &self, track_num: usize, sample_num: u32, get_offset: bool, ) -> Result { // Get the trak atom associated with track_num. debug_assert!(track_num < self.moov.traks.len()); let trak = &self.moov.traks[track_num]; // Get the constituent tables. let stsz = &trak.mdia.minf.stbl.stsz; let stsc = &trak.mdia.minf.stbl.stsc; let stco = &trak.mdia.minf.stbl.stco; let co64 = &trak.mdia.minf.stbl.co64; // Find the sample-to-chunk mapping. Note, complexity of O(log N). let group = stsc .find_entry_for_sample(sample_num) .ok_or(Error::DecodeError("invalid sample index"))?; // Index of the sample relative to the chunk group. let sample_in_group = sample_num - group.first_sample; // Index of the chunk containing the sample relative to the chunk group. let chunk_in_group = sample_in_group / group.samples_per_chunk; // Index of the chunk containing the sample relative to the entire stream. let chunk_in_stream = group.first_chunk + chunk_in_group; // Get the byte position of the first sample of the chunk containing the sample. let base_pos = get_chunk_offset(stco, co64, chunk_in_stream as usize)?.unwrap(); // Determine the absolute sample byte position if requested by calculating the offset of // the sample from the base position of the chunk. let offset = if get_offset { // Index of the sample relative to the chunk containing the sample. let sample_in_chunk = sample_in_group - (chunk_in_group * group.samples_per_chunk); // Calculat the byte offset of the sample relative to the chunk containing it. let offset = match stsz.sample_sizes { SampleSize::Constant(size) => { // Constant size samples can be calculated directly. u64::from(sample_in_chunk) * u64::from(size) } SampleSize::Variable(ref entries) => { // For variable size samples, sum the sizes of all the samples preceeding the // desired sample in the chunk. let chunk_first_sample = (sample_num - sample_in_chunk) as usize; if let Some(samples) = entries.get(chunk_first_sample..sample_num as usize) { samples.iter().map(|&size| u64::from(size)).sum() } else { return decode_error("isomp4: missing one or more stsz entries"); } } }; Some(offset) } else { None }; // Get the size in bytes of the sample. let size = match stsz.sample_sizes { SampleSize::Constant(size) => size, SampleSize::Variable(ref entries) => { if let Some(size) = entries.get(sample_num as usize) { *size } else { return decode_error("isomp4: missing stsz entry"); } } }; Ok(SampleDataDesc { base_pos, size, offset }) } fn track_sample_range(&self, track_num: usize) -> Range { debug_assert!(track_num < self.moov.traks.len()); 0..self.moov.traks[track_num].mdia.minf.stbl.stsz.sample_count } fn track_ts_range(&self, track_num: usize) -> Range { debug_assert!(track_num < self.moov.traks.len()); 0..self.moov.traks[track_num].mdia.minf.stbl.stts.total_duration } }