Skip to main content

apple_cf/cm/
format_description.rs

1//! `CMFormatDescription` - Media format description
2
3#![allow(dead_code)]
4
5use crate::{
6    cf::{CFArray, CFDictionary},
7    ffi,
8};
9use std::{fmt, ops::Deref};
10
11/// Owned wrapper around `CMFormatDescriptionRef`.
12pub struct CMFormatDescription(*mut std::ffi::c_void);
13
14impl PartialEq for CMFormatDescription {
15    fn eq(&self, other: &Self) -> bool {
16        self.0 == other.0
17    }
18}
19
20impl Eq for CMFormatDescription {}
21
22impl std::hash::Hash for CMFormatDescription {
23    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
24        unsafe {
25            let hash_value = ffi::cm_format_description_hash(self.0);
26            hash_value.hash(state);
27        }
28    }
29}
30
31/// Common media type constants
32pub mod media_types {
33    use crate::utils::four_char_code::FourCharCode;
34
35    /// Video media type ('vide')
36    pub const VIDEO: FourCharCode = FourCharCode::from_bytes(*b"vide");
37    /// Audio media type ('soun')
38    pub const AUDIO: FourCharCode = FourCharCode::from_bytes(*b"soun");
39    /// Muxed media type ('mux ')
40    pub const MUXED: FourCharCode = FourCharCode::from_bytes(*b"mux ");
41    /// Text/subtitle media type ('text')
42    pub const TEXT: FourCharCode = FourCharCode::from_bytes(*b"text");
43    /// Closed caption media type ('clcp')
44    pub const CLOSED_CAPTION: FourCharCode = FourCharCode::from_bytes(*b"clcp");
45    /// Metadata media type ('meta')
46    pub const METADATA: FourCharCode = FourCharCode::from_bytes(*b"meta");
47    /// Timecode media type ('tmcd')
48    pub const TIMECODE: FourCharCode = FourCharCode::from_bytes(*b"tmcd");
49}
50
51/// Common codec type constants
52pub mod codec_types {
53    use crate::utils::four_char_code::FourCharCode;
54
55    // Video codecs
56    /// H.264/AVC ('avc1')
57    pub const H264: FourCharCode = FourCharCode::from_bytes(*b"avc1");
58    /// HEVC/H.265 ('hvc1')
59    pub const HEVC: FourCharCode = FourCharCode::from_bytes(*b"hvc1");
60    /// HEVC/H.265 alternative ('hev1')
61    pub const HEVC_2: FourCharCode = FourCharCode::from_bytes(*b"hev1");
62    /// JPEG ('jpeg')
63    pub const JPEG: FourCharCode = FourCharCode::from_bytes(*b"jpeg");
64    /// Apple `ProRes` 422 ('apcn')
65    pub const PRORES_422: FourCharCode = FourCharCode::from_bytes(*b"apcn");
66    /// Apple `ProRes` 4444 ('ap4h')
67    pub const PRORES_4444: FourCharCode = FourCharCode::from_bytes(*b"ap4h");
68
69    // Audio codecs
70    /// AAC ('aac ')
71    pub const AAC: FourCharCode = FourCharCode::from_bytes(*b"aac ");
72    /// Linear PCM ('lpcm')
73    pub const LPCM: FourCharCode = FourCharCode::from_bytes(*b"lpcm");
74    /// Apple Lossless ('alac')
75    pub const ALAC: FourCharCode = FourCharCode::from_bytes(*b"alac");
76    /// Opus ('opus')
77    pub const OPUS: FourCharCode = FourCharCode::from_bytes(*b"opus");
78    /// FLAC ('flac')
79    pub const FLAC: FourCharCode = FourCharCode::from_bytes(*b"flac");
80}
81
82/// Metadata format subtypes (`CMMetadataFormatType`).
83pub mod metadata_format_types {
84    use crate::utils::four_char_code::FourCharCode;
85
86    /// `SHOUTCast` / `ICY` metadata ('icy ').
87    pub const ICY: FourCharCode = FourCharCode::from_bytes(*b"icy ");
88    /// ID3 metadata ('id3 ').
89    pub const ID3: FourCharCode = FourCharCode::from_bytes(*b"id3 ");
90    /// Boxed metadata ('mebx').
91    pub const BOXED: FourCharCode = FourCharCode::from_bytes(*b"mebx");
92    /// Event message metadata ('emsg').
93    pub const EMSG: FourCharCode = FourCharCode::from_bytes(*b"emsg");
94}
95
96macro_rules! cfstring_constant_fn {
97    ($vis:vis fn $name:ident => $ffi_name:ident) => {
98        #[must_use]
99        $vis fn $name() -> CFString {
100            let ptr = unsafe { ffi::$ffi_name() };
101            CFString::from_raw(ptr).expect(concat!(stringify!($ffi_name), " returned NULL"))
102        }
103    };
104}
105
106/// `CMFormatDescription` extension keys related to metadata descriptions.
107pub mod format_description_extension_keys {
108    use crate::{cf::CFString, ffi};
109
110    cfstring_constant_fn!(pub fn metadata_key_table => cm_metadata_format_description_extension_key_metadata_key_table);
111}
112
113/// `kCMMetadataFormatDescriptionKey_*` constants.
114pub mod metadata_description_keys {
115    use crate::{cf::CFString, ffi};
116
117    cfstring_constant_fn!(pub fn conforming_data_types => cm_metadata_format_description_key_conforming_data_types);
118    cfstring_constant_fn!(pub fn data_type => cm_metadata_format_description_key_data_type);
119    cfstring_constant_fn!(pub fn data_type_namespace => cm_metadata_format_description_key_data_type_namespace);
120    cfstring_constant_fn!(pub fn language_tag => cm_metadata_format_description_key_language_tag);
121    cfstring_constant_fn!(pub fn local_id => cm_metadata_format_description_key_local_id);
122    cfstring_constant_fn!(pub fn namespace => cm_metadata_format_description_key_namespace);
123    cfstring_constant_fn!(pub fn setup_data => cm_metadata_format_description_key_setup_data);
124    cfstring_constant_fn!(pub fn structural_dependency => cm_metadata_format_description_key_structural_dependency);
125    cfstring_constant_fn!(pub fn value => cm_metadata_format_description_key_value);
126}
127
128/// `kCMMetadataFormatDescriptionMetadataSpecificationKey_*` constants.
129pub mod metadata_specification_keys {
130    use crate::{cf::CFString, ffi};
131
132    cfstring_constant_fn!(pub fn data_type => cm_metadata_format_description_metadata_specification_key_data_type);
133    cfstring_constant_fn!(pub fn extended_language_tag => cm_metadata_format_description_metadata_specification_key_extended_language_tag);
134    cfstring_constant_fn!(pub fn identifier => cm_metadata_format_description_metadata_specification_key_identifier);
135    cfstring_constant_fn!(pub fn setup_data => cm_metadata_format_description_metadata_specification_key_setup_data);
136    cfstring_constant_fn!(pub fn structural_dependency => cm_metadata_format_description_metadata_specification_key_structural_dependency);
137}
138
139/// `kCMMetadataFormatDescription_StructuralDependencyKey_*` constants.
140pub mod metadata_structural_dependency_keys {
141    use crate::{cf::CFString, ffi};
142
143    cfstring_constant_fn!(pub fn dependency_is_invalid_flag => cm_metadata_format_description_structural_dependency_key_dependency_is_invalid_flag);
144}
145
146impl CMFormatDescription {
147    /// Wraps a +1 retained `CMFormatDescriptionRef` and returns `None` for null.
148    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
149        if ptr.is_null() {
150            None
151        } else {
152            Some(Self(ptr))
153        }
154    }
155
156    /// Wraps a raw `CMFormatDescriptionRef` by taking ownership without retaining it.
157    ///
158    /// # Safety
159    /// The caller must ensure `ptr` is a valid +1 retained `CMFormatDescriptionRef`.
160    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
161        Self(ptr)
162    }
163
164    /// Returns the wrapped raw `CMFormatDescriptionRef`.
165    #[must_use]
166    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
167        self.0
168    }
169
170    /// Get the media type as a raw u32 value
171    #[must_use]
172    pub fn media_type_raw(&self) -> u32 {
173        unsafe { ffi::cm_format_description_get_media_type(self.0) }
174    }
175
176    /// Get the media type as `FourCharCode`
177    #[must_use]
178    pub fn media_type(&self) -> crate::utils::four_char_code::FourCharCode {
179        crate::utils::four_char_code::FourCharCode::from(self.media_type_raw())
180    }
181
182    /// Get the media subtype (codec type) as a raw u32 value
183    #[must_use]
184    pub fn media_subtype_raw(&self) -> u32 {
185        unsafe { ffi::cm_format_description_get_media_subtype(self.0) }
186    }
187
188    /// Get the media subtype as `FourCharCode`
189    #[must_use]
190    pub fn media_subtype(&self) -> crate::utils::four_char_code::FourCharCode {
191        crate::utils::four_char_code::FourCharCode::from(self.media_subtype_raw())
192    }
193
194    /// Get format description extensions
195    #[must_use]
196    pub fn extensions(&self) -> Option<*const std::ffi::c_void> {
197        unsafe {
198            let ptr = ffi::cm_format_description_get_extensions(self.0);
199            if ptr.is_null() {
200                None
201            } else {
202                Some(ptr)
203            }
204        }
205    }
206
207    /// Check if this is a video format description
208    #[must_use]
209    pub fn is_video(&self) -> bool {
210        self.media_type() == media_types::VIDEO
211    }
212
213    /// Check if this is an audio format description
214    #[must_use]
215    pub fn is_audio(&self) -> bool {
216        self.media_type() == media_types::AUDIO
217    }
218
219    /// Check if this is a muxed format description
220    #[must_use]
221    pub fn is_muxed(&self) -> bool {
222        self.media_type() == media_types::MUXED
223    }
224
225    /// Check if this is a text/subtitle format description
226    #[must_use]
227    pub fn is_text(&self) -> bool {
228        self.media_type() == media_types::TEXT
229    }
230
231    /// Check if this is a closed caption format description
232    #[must_use]
233    pub fn is_closed_caption(&self) -> bool {
234        self.media_type() == media_types::CLOSED_CAPTION
235    }
236
237    /// Check if this is a metadata format description
238    #[must_use]
239    pub fn is_metadata(&self) -> bool {
240        self.media_type() == media_types::METADATA
241    }
242
243    /// Check if this is a timecode format description
244    #[must_use]
245    pub fn is_timecode(&self) -> bool {
246        self.media_type() == media_types::TIMECODE
247    }
248
249    /// Get a human-readable string for the media type
250    #[must_use]
251    pub fn media_type_string(&self) -> String {
252        self.media_type().display()
253    }
254
255    /// Get a human-readable string for the media subtype (codec)
256    #[must_use]
257    pub fn media_subtype_string(&self) -> String {
258        self.media_subtype().display()
259    }
260
261    /// Check if the codec is H.264
262    #[must_use]
263    pub fn is_h264(&self) -> bool {
264        self.media_subtype() == codec_types::H264
265    }
266
267    /// Check if the codec is HEVC/H.265
268    #[must_use]
269    pub fn is_hevc(&self) -> bool {
270        let subtype = self.media_subtype();
271        subtype == codec_types::HEVC || subtype == codec_types::HEVC_2
272    }
273
274    /// Check if the codec is AAC
275    #[must_use]
276    pub fn is_aac(&self) -> bool {
277        self.media_subtype() == codec_types::AAC
278    }
279
280    /// Check if the codec is PCM
281    #[must_use]
282    pub fn is_pcm(&self) -> bool {
283        self.media_subtype() == codec_types::LPCM
284    }
285
286    /// Check if the codec is `ProRes`
287    #[must_use]
288    pub fn is_prores(&self) -> bool {
289        let subtype = self.media_subtype();
290        subtype == codec_types::PRORES_422 || subtype == codec_types::PRORES_4444
291    }
292
293    /// Check if the codec is Apple Lossless (ALAC)
294    #[must_use]
295    pub fn is_alac(&self) -> bool {
296        self.media_subtype() == codec_types::ALAC
297    }
298
299    // Audio format description methods
300
301    /// Get the audio sample rate in Hz
302    ///
303    /// Returns `None` if this is not an audio format description.
304    #[must_use]
305    pub fn audio_sample_rate(&self) -> Option<f64> {
306        if !self.is_audio() {
307            return None;
308        }
309        let rate = unsafe { ffi::cm_format_description_get_audio_sample_rate(self.0) };
310        if rate > 0.0 {
311            Some(rate)
312        } else {
313            None
314        }
315    }
316
317    /// Get the number of audio channels
318    ///
319    /// Returns `None` if this is not an audio format description.
320    #[must_use]
321    pub fn audio_channel_count(&self) -> Option<u32> {
322        if !self.is_audio() {
323            return None;
324        }
325        let count = unsafe { ffi::cm_format_description_get_audio_channel_count(self.0) };
326        if count > 0 {
327            Some(count)
328        } else {
329            None
330        }
331    }
332
333    /// Get the bits per audio channel
334    ///
335    /// Returns `None` if this is not an audio format description.
336    #[must_use]
337    pub fn audio_bits_per_channel(&self) -> Option<u32> {
338        if !self.is_audio() {
339            return None;
340        }
341        let bits = unsafe { ffi::cm_format_description_get_audio_bits_per_channel(self.0) };
342        if bits > 0 {
343            Some(bits)
344        } else {
345            None
346        }
347    }
348
349    /// Get the bytes per audio frame
350    ///
351    /// Returns `None` if this is not an audio format description.
352    #[must_use]
353    pub fn audio_bytes_per_frame(&self) -> Option<u32> {
354        if !self.is_audio() {
355            return None;
356        }
357        let bytes = unsafe { ffi::cm_format_description_get_audio_bytes_per_frame(self.0) };
358        if bytes > 0 {
359            Some(bytes)
360        } else {
361            None
362        }
363    }
364
365    /// Get the audio format flags
366    ///
367    /// Returns `None` if this is not an audio format description.
368    #[must_use]
369    pub fn audio_format_flags(&self) -> Option<u32> {
370        if !self.is_audio() {
371            return None;
372        }
373        Some(unsafe { ffi::cm_format_description_get_audio_format_flags(self.0) })
374    }
375
376    /// Check if audio is float format (based on format flags)
377    #[must_use]
378    pub fn audio_is_float(&self) -> bool {
379        // kAudioFormatFlagIsFloat = 1
380        self.audio_format_flags().is_some_and(|f| f & 1 != 0)
381    }
382
383    /// Check if audio is big-endian (based on format flags)
384    #[must_use]
385    pub fn audio_is_big_endian(&self) -> bool {
386        // kAudioFormatFlagIsBigEndian = 2
387        self.audio_format_flags().is_some_and(|f| f & 2 != 0)
388    }
389}
390
391impl Clone for CMFormatDescription {
392    fn clone(&self) -> Self {
393        unsafe {
394            let ptr = ffi::cm_format_description_retain(self.0);
395            Self(ptr)
396        }
397    }
398}
399
400impl Drop for CMFormatDescription {
401    fn drop(&mut self) {
402        unsafe {
403            ffi::cm_format_description_release(self.0);
404        }
405    }
406}
407
408// SAFETY: `CMFormatDescriptionRef` is a Core Foundation type; Apple documents
409// its retain/release as thread-safe and the description data is immutable after
410// creation.
411unsafe impl Send for CMFormatDescription {}
412unsafe impl Sync for CMFormatDescription {}
413
414impl fmt::Debug for CMFormatDescription {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        f.debug_struct("CMFormatDescription")
417            .field("media_type", &self.media_type_string())
418            .field("codec", &self.media_subtype_string())
419            .finish()
420    }
421}
422
423impl fmt::Display for CMFormatDescription {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        write!(
426            f,
427            "CMFormatDescription(type: 0x{:08X}, subtype: 0x{:08X})",
428            self.media_type_raw(),
429            self.media_subtype_raw()
430        )
431    }
432}
433
434/// Metadata-specific wrapper around `CMFormatDescriptionRef`.
435#[derive(Clone, PartialEq, Eq, Hash)]
436pub struct CMMetadataFormatDescription(CMFormatDescription);
437
438impl CMMetadataFormatDescription {
439    /// Wraps a +1 retained raw metadata format-description pointer and returns `None` for null.
440    #[must_use]
441    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
442        CMFormatDescription::from_raw(ptr).map(Self)
443    }
444
445    /// Wraps a raw metadata `CMFormatDescriptionRef` by taking ownership without retaining it.
446    ///
447    /// # Safety
448    /// The caller must ensure `ptr` is a valid +1 retained metadata `CMFormatDescriptionRef`.
449    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
450        Self(CMFormatDescription::from_ptr(ptr))
451    }
452
453    /// Returns the wrapped raw metadata format-description pointer.
454    #[must_use]
455    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
456        self.0.as_ptr()
457    }
458
459    /// Access the metadata description as a plain `CMFormatDescription`.
460    #[must_use]
461    pub const fn as_format_description(&self) -> &CMFormatDescription {
462        &self.0
463    }
464
465    /// Consume the metadata wrapper and return the underlying `CMFormatDescription`.
466    #[must_use]
467    pub fn into_format_description(self) -> CMFormatDescription {
468        self.0
469    }
470
471    /// Create a metadata format description from an optional array of key dictionaries.
472    ///
473    /// # Errors
474    ///
475    /// Returns the `OSStatus` reported by Core Media if the description could not be created.
476    pub fn create_with_keys(
477        metadata_type: crate::utils::four_char_code::FourCharCode,
478        keys: Option<&CFArray>,
479    ) -> Result<Self, i32> {
480        let mut ptr = std::ptr::null_mut();
481        let status = unsafe {
482            ffi::cm_metadata_format_description_create_with_keys(
483                metadata_type.into(),
484                keys.map_or(std::ptr::null_mut(), CFArray::as_ptr),
485                &mut ptr,
486            )
487        };
488        if status == 0 && !ptr.is_null() {
489            Self::from_raw(ptr).ok_or(status)
490        } else {
491            Err(status)
492        }
493    }
494
495    /// Create a boxed metadata format description from metadata specification dictionaries.
496    ///
497    /// # Errors
498    ///
499    /// Returns the `OSStatus` reported by Core Media if the description could not be created.
500    pub fn create_with_metadata_specifications(
501        metadata_type: crate::utils::four_char_code::FourCharCode,
502        metadata_specifications: &CFArray,
503    ) -> Result<Self, i32> {
504        let mut ptr = std::ptr::null_mut();
505        let status = unsafe {
506            ffi::cm_metadata_format_description_create_with_metadata_specifications(
507                metadata_type.into(),
508                metadata_specifications.as_ptr(),
509                &mut ptr,
510            )
511        };
512        if status == 0 && !ptr.is_null() {
513            Self::from_raw(ptr).ok_or(status)
514        } else {
515            Err(status)
516        }
517    }
518
519    /// Extend an existing metadata description with additional metadata specifications.
520    ///
521    /// # Errors
522    ///
523    /// Returns the `OSStatus` reported by Core Media if the extended description could not be created.
524    pub fn extend_with_metadata_specifications(
525        &self,
526        metadata_specifications: &CFArray,
527    ) -> Result<Self, i32> {
528        let mut ptr = std::ptr::null_mut();
529        let status = unsafe {
530            ffi::cm_metadata_format_description_create_with_description_and_metadata_specifications(
531                self.as_ptr(),
532                metadata_specifications.as_ptr(),
533                &mut ptr,
534            )
535        };
536        if status == 0 && !ptr.is_null() {
537            Self::from_raw(ptr).ok_or(status)
538        } else {
539            Err(status)
540        }
541    }
542
543    /// Merge two metadata format descriptions into a new description.
544    ///
545    /// # Errors
546    ///
547    /// Returns the `OSStatus` reported by Core Media if the merged description could not be created.
548    pub fn merge(&self, other: &Self) -> Result<Self, i32> {
549        let mut ptr = std::ptr::null_mut();
550        let status = unsafe {
551            ffi::cm_metadata_format_description_create_by_merging_descriptions(
552                self.as_ptr(),
553                other.as_ptr(),
554                &mut ptr,
555            )
556        };
557        if status == 0 && !ptr.is_null() {
558            Self::from_raw(ptr).ok_or(status)
559        } else {
560            Err(status)
561        }
562    }
563
564    /// Copy the metadata identifiers declared by this description.
565    #[must_use]
566    pub fn identifiers(&self) -> Option<CFArray> {
567        let ptr = unsafe { ffi::cm_metadata_format_description_get_identifiers(self.as_ptr()) };
568        CFArray::from_raw(ptr)
569    }
570
571    /// Copy the metadata key dictionary for `local_id`, if present.
572    #[must_use]
573    pub fn key_with_local_id(&self, local_id: u32) -> Option<CFDictionary> {
574        let ptr = unsafe {
575            ffi::cm_metadata_format_description_get_key_with_local_id(self.as_ptr(), local_id)
576        };
577        CFDictionary::from_raw(ptr)
578    }
579}
580
581impl Deref for CMMetadataFormatDescription {
582    type Target = CMFormatDescription;
583
584    fn deref(&self) -> &Self::Target {
585        &self.0
586    }
587}
588
589impl TryFrom<CMFormatDescription> for CMMetadataFormatDescription {
590    type Error = CMFormatDescription;
591
592    fn try_from(value: CMFormatDescription) -> Result<Self, Self::Error> {
593        if value.is_metadata() {
594            Ok(Self(value))
595        } else {
596            Err(value)
597        }
598    }
599}
600
601impl From<CMMetadataFormatDescription> for CMFormatDescription {
602    fn from(value: CMMetadataFormatDescription) -> Self {
603        value.0
604    }
605}
606
607impl fmt::Debug for CMMetadataFormatDescription {
608    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609        f.debug_struct("CMMetadataFormatDescription")
610            .field("media_type", &self.media_type_string())
611            .field("codec", &self.media_subtype_string())
612            .finish()
613    }
614}
615
616impl fmt::Display for CMMetadataFormatDescription {
617    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618        fmt::Display::fmt(&self.0, f)
619    }
620}