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