symphonia-format-isomp4-0.5.2/.cargo_vcs_info.json0000644000000001650000000000100155500ustar { "git": { "sha1": "412f44daab39920beeb81d78b0e4271b263d33e9" }, "path_in_vcs": "symphonia-format-isomp4" }symphonia-format-isomp4-0.5.2/Cargo.toml0000644000000023430000000000100135460ustar # 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.2" 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.2" [dependencies.symphonia-metadata] version = "0.5.2" [dependencies.symphonia-utils-xiph] version = "0.5.2" symphonia-format-isomp4-0.5.2/Cargo.toml.orig000064400000000000000000000014111046102023000172220ustar 00000000000000[package] name = "symphonia-format-isomp4" version = "0.5.2" 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.2", path = "../symphonia-core" } symphonia-metadata = { version = "0.5.2", path = "../symphonia-metadata" } symphonia-utils-xiph = { version = "0.5.2", path = "../symphonia-utils-xiph" }symphonia-format-isomp4-0.5.2/README.md000064400000000000000000000014061046102023000156160ustar 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 an open-source project and contributions are very welcome! If you would like to make a large contribution, please raise an issue ahead of time to make sure your efforts fit into the project goals, and that no duplication of efforts occurs. All contributors will be credited within the CONTRIBUTORS file. symphonia-format-isomp4-0.5.2/src/atoms/alac.rs000064400000000000000000000035451046102023000175250ustar 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.2/src/atoms/co64.rs000064400000000000000000000021001046102023000173620ustar 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.2/src/atoms/ctts.rs000064400000000000000000000012751046102023000176000ustar 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.2/src/atoms/edts.rs000064400000000000000000000021541046102023000175570ustar 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.2/src/atoms/elst.rs000064400000000000000000000040321046102023000175640ustar 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.2/src/atoms/esds.rs000064400000000000000000000247571046102023000175730ustar 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.2/src/atoms/flac.rs000064400000000000000000000051631046102023000175300ustar 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.2/src/atoms/ftyp.rs000064400000000000000000000030551046102023000176030ustar 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.2/src/atoms/hdlr.rs000064400000000000000000000042241046102023000175510ustar 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.2/src/atoms/ilst.rs000064400000000000000000000604671046102023000176060ustar 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.2/src/atoms/mdhd.rs000064400000000000000000000050521046102023000175340ustar 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.2/src/atoms/mdia.rs000064400000000000000000000034361046102023000175360ustar 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.2/src/atoms/mehd.rs000064400000000000000000000021471046102023000175370ustar 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.2/src/atoms/meta.rs000064400000000000000000000035051046102023000175470ustar 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.2/src/atoms/mfhd.rs000064400000000000000000000016361046102023000175420ustar 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.2/src/atoms/minf.rs000064400000000000000000000027641046102023000175600ustar 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.2/src/atoms/mod.rs000064400000000000000000000334551046102023000174070ustar 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; 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; 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.unwrap_or(std::u64::MAX) - self.next_atom_pos } len => 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.2/src/atoms/moof.rs000064400000000000000000000034171046102023000175630ustar 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.2/src/atoms/moov.rs000064400000000000000000000056101046102023000176000ustar 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.2/src/atoms/mvex.rs000064400000000000000000000026731046102023000176050ustar 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.2/src/atoms/mvhd.rs000064400000000000000000000046171046102023000175640ustar 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.2/src/atoms/opus.rs000064400000000000000000000050631046102023000176100ustar 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.2/src/atoms/sidx.rs000064400000000000000000000050541046102023000175710ustar 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.2/src/atoms/smhd.rs000064400000000000000000000017501046102023000175540ustar 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.2/src/atoms/stbl.rs000064400000000000000000000063471046102023000175740ustar 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.2/src/atoms/stco.rs000064400000000000000000000021001046102023000175570ustar 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.2/src/atoms/stsc.rs000064400000000000000000000070731046102023000176010ustar 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.2/src/atoms/stsd.rs000064400000000000000000000453451046102023000176060ustar 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.2/src/atoms/stss.rs000064400000000000000000000012421046102023000176110ustar 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.2/src/atoms/stsz.rs000064400000000000000000000030171046102023000176220ustar 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.2/src/atoms/stts.rs000064400000000000000000000067171046102023000176260ustar 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.2/src/atoms/tfhd.rs000064400000000000000000000046571046102023000175570ustar 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.2/src/atoms/tkhd.rs000064400000000000000000000052101046102023000175460ustar 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.2/src/atoms/traf.rs000064400000000000000000000035321046102023000175550ustar 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.2/src/atoms/trak.rs000064400000000000000000000034411046102023000175610ustar 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.2/src/atoms/trex.rs000064400000000000000000000025731046102023000176070ustar 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.2/src/atoms/trun.rs000064400000000000000000000263001046102023000176070ustar 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.2/src/atoms/udta.rs000064400000000000000000000026441046102023000175610ustar 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.2/src/atoms/wave.rs000064400000000000000000000020241046102023000175560ustar 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.2/src/demuxer.rs000064400000000000000000000560161046102023000171540ustar 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.2/src/fourcc.rs000064400000000000000000000015401046102023000167540ustar 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.2/src/fp.rs000064400000000000000000000031521046102023000161010ustar 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.2/src/lib.rs000064400000000000000000000012171046102023000162420ustar 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.2/src/stream.rs000064400000000000000000000372141046102023000167750ustar 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 } }