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            unsafe { CFString::from_raw(ptr) }
102                .expect(concat!(stringify!($ffi_name), " returned NULL"))
103        }
104    };
105}
106
107/// `CMFormatDescription` extension keys related to metadata descriptions.
108pub mod format_description_extension_keys {
109    use crate::{cf::CFString, ffi};
110
111    cfstring_constant_fn!(pub fn metadata_key_table => cm_metadata_format_description_extension_key_metadata_key_table);
112}
113
114/// `kCMMetadataFormatDescriptionKey_*` constants.
115pub mod metadata_description_keys {
116    use crate::{cf::CFString, ffi};
117
118    cfstring_constant_fn!(pub fn conforming_data_types => cm_metadata_format_description_key_conforming_data_types);
119    cfstring_constant_fn!(pub fn data_type => cm_metadata_format_description_key_data_type);
120    cfstring_constant_fn!(pub fn data_type_namespace => cm_metadata_format_description_key_data_type_namespace);
121    cfstring_constant_fn!(pub fn language_tag => cm_metadata_format_description_key_language_tag);
122    cfstring_constant_fn!(pub fn local_id => cm_metadata_format_description_key_local_id);
123    cfstring_constant_fn!(pub fn namespace => cm_metadata_format_description_key_namespace);
124    cfstring_constant_fn!(pub fn setup_data => cm_metadata_format_description_key_setup_data);
125    cfstring_constant_fn!(pub fn structural_dependency => cm_metadata_format_description_key_structural_dependency);
126    cfstring_constant_fn!(pub fn value => cm_metadata_format_description_key_value);
127}
128
129/// `kCMMetadataFormatDescriptionMetadataSpecificationKey_*` constants.
130pub mod metadata_specification_keys {
131    use crate::{cf::CFString, ffi};
132
133    cfstring_constant_fn!(pub fn data_type => cm_metadata_format_description_metadata_specification_key_data_type);
134    cfstring_constant_fn!(pub fn extended_language_tag => cm_metadata_format_description_metadata_specification_key_extended_language_tag);
135    cfstring_constant_fn!(pub fn identifier => cm_metadata_format_description_metadata_specification_key_identifier);
136    cfstring_constant_fn!(pub fn setup_data => cm_metadata_format_description_metadata_specification_key_setup_data);
137    cfstring_constant_fn!(pub fn structural_dependency => cm_metadata_format_description_metadata_specification_key_structural_dependency);
138}
139
140/// `kCMMetadataFormatDescription_StructuralDependencyKey_*` constants.
141pub mod metadata_structural_dependency_keys {
142    use crate::{cf::CFString, ffi};
143
144    cfstring_constant_fn!(pub fn dependency_is_invalid_flag => cm_metadata_format_description_structural_dependency_key_dependency_is_invalid_flag);
145}
146
147impl CMFormatDescription {
148    /// Adopts a +1 retained `CMFormatDescriptionRef` and returns `None` for null.
149    ///
150    /// # Safety
151    ///
152    /// A non-null `ptr` must be a live `CMFormatDescriptionRef` of the exact
153    /// type carrying one retain transferred to this wrapper. The caller must
154    /// not release or separately adopt that transferred retain.
155    pub unsafe fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
156        if ptr.is_null() {
157            None
158        } else {
159            Some(Self(ptr))
160        }
161    }
162
163    /// Retains a +0 borrowed `CMFormatDescriptionRef` and returns an owned wrapper.
164    ///
165    /// # Safety
166    ///
167    /// A non-null `ptr` must be a live `CMFormatDescriptionRef` of the exact
168    /// type for the duration of the retain call.
169    #[must_use]
170    pub unsafe fn from_raw_borrowed(ptr: *mut std::ffi::c_void) -> Option<Self> {
171        if ptr.is_null() {
172            None
173        } else {
174            let retained = unsafe { ffi::cm_format_description_retain(ptr) };
175            unsafe { Self::from_raw(retained) }
176        }
177    }
178
179    /// Wraps a raw `CMFormatDescriptionRef` by taking ownership without retaining it.
180    ///
181    /// # Safety
182    /// `ptr` must be a non-null, live `CMFormatDescriptionRef` of the exact
183    /// type carrying one retain transferred to this wrapper.
184    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
185        Self(ptr)
186    }
187
188    /// Borrows the raw +0 `CMFormatDescriptionRef` while `self` remains alive.
189    #[must_use]
190    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
191        self.0
192    }
193
194    /// Get the media type as a raw u32 value
195    #[must_use]
196    pub fn media_type_raw(&self) -> u32 {
197        unsafe { ffi::cm_format_description_get_media_type(self.0) }
198    }
199
200    /// Get the media type as `FourCharCode`
201    #[must_use]
202    pub fn media_type(&self) -> crate::utils::four_char_code::FourCharCode {
203        crate::utils::four_char_code::FourCharCode::from(self.media_type_raw())
204    }
205
206    /// Get the media subtype (codec type) as a raw u32 value
207    #[must_use]
208    pub fn media_subtype_raw(&self) -> u32 {
209        unsafe { ffi::cm_format_description_get_media_subtype(self.0) }
210    }
211
212    /// Get the media subtype as `FourCharCode`
213    #[must_use]
214    pub fn media_subtype(&self) -> crate::utils::four_char_code::FourCharCode {
215        crate::utils::four_char_code::FourCharCode::from(self.media_subtype_raw())
216    }
217
218    /// Get format description extensions
219    #[must_use]
220    pub fn extensions(&self) -> Option<*const std::ffi::c_void> {
221        unsafe {
222            let ptr = ffi::cm_format_description_get_extensions(self.0);
223            if ptr.is_null() {
224                None
225            } else {
226                Some(ptr)
227            }
228        }
229    }
230
231    /// Check if this is a video format description
232    #[must_use]
233    pub fn is_video(&self) -> bool {
234        self.media_type() == media_types::VIDEO
235    }
236
237    /// Check if this is an audio format description
238    #[must_use]
239    pub fn is_audio(&self) -> bool {
240        self.media_type() == media_types::AUDIO
241    }
242
243    /// Check if this is a muxed format description
244    #[must_use]
245    pub fn is_muxed(&self) -> bool {
246        self.media_type() == media_types::MUXED
247    }
248
249    /// Check if this is a text/subtitle format description
250    #[must_use]
251    pub fn is_text(&self) -> bool {
252        self.media_type() == media_types::TEXT
253    }
254
255    /// Check if this is a closed caption format description
256    #[must_use]
257    pub fn is_closed_caption(&self) -> bool {
258        self.media_type() == media_types::CLOSED_CAPTION
259    }
260
261    /// Check if this is a metadata format description
262    #[must_use]
263    pub fn is_metadata(&self) -> bool {
264        self.media_type() == media_types::METADATA
265    }
266
267    /// Check if this is a timecode format description
268    #[must_use]
269    pub fn is_timecode(&self) -> bool {
270        self.media_type() == media_types::TIMECODE
271    }
272
273    /// Get a human-readable string for the media type
274    #[must_use]
275    pub fn media_type_string(&self) -> String {
276        self.media_type().display()
277    }
278
279    /// Get a human-readable string for the media subtype (codec)
280    #[must_use]
281    pub fn media_subtype_string(&self) -> String {
282        self.media_subtype().display()
283    }
284
285    /// Check if the codec is H.264
286    #[must_use]
287    pub fn is_h264(&self) -> bool {
288        self.media_subtype() == codec_types::H264
289    }
290
291    /// Check if the codec is HEVC/H.265
292    #[must_use]
293    pub fn is_hevc(&self) -> bool {
294        let subtype = self.media_subtype();
295        subtype == codec_types::HEVC || subtype == codec_types::HEVC_2
296    }
297
298    /// Check if the codec is AAC
299    #[must_use]
300    pub fn is_aac(&self) -> bool {
301        self.media_subtype() == codec_types::AAC
302    }
303
304    /// Check if the codec is PCM
305    #[must_use]
306    pub fn is_pcm(&self) -> bool {
307        self.media_subtype() == codec_types::LPCM
308    }
309
310    /// Check if the codec is `ProRes`
311    #[must_use]
312    pub fn is_prores(&self) -> bool {
313        let subtype = self.media_subtype();
314        subtype == codec_types::PRORES_422 || subtype == codec_types::PRORES_4444
315    }
316
317    /// Check if the codec is Apple Lossless (ALAC)
318    #[must_use]
319    pub fn is_alac(&self) -> bool {
320        self.media_subtype() == codec_types::ALAC
321    }
322
323    // Audio format description methods
324
325    /// Get the audio sample rate in Hz
326    ///
327    /// Returns `None` if this is not an audio format description.
328    #[must_use]
329    pub fn audio_sample_rate(&self) -> Option<f64> {
330        if !self.is_audio() {
331            return None;
332        }
333        let rate = unsafe { ffi::cm_format_description_get_audio_sample_rate(self.0) };
334        if rate > 0.0 {
335            Some(rate)
336        } else {
337            None
338        }
339    }
340
341    /// Get the number of audio channels
342    ///
343    /// Returns `None` if this is not an audio format description.
344    #[must_use]
345    pub fn audio_channel_count(&self) -> Option<u32> {
346        if !self.is_audio() {
347            return None;
348        }
349        let count = unsafe { ffi::cm_format_description_get_audio_channel_count(self.0) };
350        if count > 0 {
351            Some(count)
352        } else {
353            None
354        }
355    }
356
357    /// Get the bits per audio channel
358    ///
359    /// Returns `None` if this is not an audio format description.
360    #[must_use]
361    pub fn audio_bits_per_channel(&self) -> Option<u32> {
362        if !self.is_audio() {
363            return None;
364        }
365        let bits = unsafe { ffi::cm_format_description_get_audio_bits_per_channel(self.0) };
366        if bits > 0 {
367            Some(bits)
368        } else {
369            None
370        }
371    }
372
373    /// Get the bytes per audio frame
374    ///
375    /// Returns `None` if this is not an audio format description.
376    #[must_use]
377    pub fn audio_bytes_per_frame(&self) -> Option<u32> {
378        if !self.is_audio() {
379            return None;
380        }
381        let bytes = unsafe { ffi::cm_format_description_get_audio_bytes_per_frame(self.0) };
382        if bytes > 0 {
383            Some(bytes)
384        } else {
385            None
386        }
387    }
388
389    /// Get the audio format flags
390    ///
391    /// Returns `None` if this is not an audio format description.
392    #[must_use]
393    pub fn audio_format_flags(&self) -> Option<u32> {
394        if !self.is_audio() {
395            return None;
396        }
397        Some(unsafe { ffi::cm_format_description_get_audio_format_flags(self.0) })
398    }
399
400    /// Check if audio is float format (based on format flags)
401    #[must_use]
402    pub fn audio_is_float(&self) -> bool {
403        // kAudioFormatFlagIsFloat = 1
404        self.audio_format_flags().is_some_and(|f| f & 1 != 0)
405    }
406
407    /// Check if audio is big-endian (based on format flags)
408    #[must_use]
409    pub fn audio_is_big_endian(&self) -> bool {
410        // kAudioFormatFlagIsBigEndian = 2
411        self.audio_format_flags().is_some_and(|f| f & 2 != 0)
412    }
413}
414
415crate::utils::retained::cf_retained!(
416    CMFormatDescription,
417    retain = ffi::cm_format_description_retain,
418    release = ffi::cm_format_description_release,
419    drop = unchecked,
420);
421
422// SAFETY: `CMFormatDescriptionRef` is a Core Foundation type; Apple documents
423// its retain/release as thread-safe and the description data is immutable after
424// creation.
425unsafe impl Send for CMFormatDescription {}
426unsafe impl Sync for CMFormatDescription {}
427
428impl fmt::Debug for CMFormatDescription {
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        f.debug_struct("CMFormatDescription")
431            .field("media_type", &self.media_type_string())
432            .field("codec", &self.media_subtype_string())
433            .finish()
434    }
435}
436
437impl fmt::Display for CMFormatDescription {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        write!(
440            f,
441            "CMFormatDescription(type: 0x{:08X}, subtype: 0x{:08X})",
442            self.media_type_raw(),
443            self.media_subtype_raw()
444        )
445    }
446}
447
448/// Metadata-specific wrapper around `CMFormatDescriptionRef`.
449#[derive(Clone, PartialEq, Eq, Hash)]
450pub struct CMMetadataFormatDescription(CMFormatDescription);
451
452impl CMMetadataFormatDescription {
453    /// Adopts a +1 retained metadata format-description pointer.
454    ///
455    /// # Safety
456    ///
457    /// A non-null `ptr` must be a live metadata `CMFormatDescriptionRef`
458    /// carrying one retain transferred to this wrapper. The caller must not
459    /// release or separately adopt that transferred retain.
460    #[must_use]
461    pub unsafe fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
462        unsafe { CMFormatDescription::from_raw(ptr) }.map(Self)
463    }
464
465    /// Retains a +0 borrowed metadata format-description pointer.
466    ///
467    /// # Safety
468    ///
469    /// A non-null `ptr` must be a live metadata `CMFormatDescriptionRef` for
470    /// the duration of the retain call.
471    #[must_use]
472    pub unsafe fn from_raw_borrowed(ptr: *mut std::ffi::c_void) -> Option<Self> {
473        unsafe { CMFormatDescription::from_raw_borrowed(ptr) }.map(Self)
474    }
475
476    /// Wraps a raw metadata `CMFormatDescriptionRef` by taking ownership without retaining it.
477    ///
478    /// # Safety
479    /// `ptr` must be a non-null, live metadata `CMFormatDescriptionRef`
480    /// carrying one retain transferred to this wrapper.
481    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
482        Self(unsafe { CMFormatDescription::from_ptr(ptr) })
483    }
484
485    /// Borrows the raw +0 metadata format-description pointer while `self` remains alive.
486    #[must_use]
487    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
488        self.0.as_ptr()
489    }
490
491    /// Access the metadata description as a plain `CMFormatDescription`.
492    #[must_use]
493    pub const fn as_format_description(&self) -> &CMFormatDescription {
494        &self.0
495    }
496
497    /// Consume the metadata wrapper and return the underlying `CMFormatDescription`.
498    #[must_use]
499    pub fn into_format_description(self) -> CMFormatDescription {
500        self.0
501    }
502
503    /// Create a metadata format description from an optional array of key dictionaries.
504    ///
505    /// # Errors
506    ///
507    /// Returns the `OSStatus` reported by Core Media if the description could not be created.
508    pub fn create_with_keys(
509        metadata_type: crate::utils::four_char_code::FourCharCode,
510        keys: Option<&CFArray>,
511    ) -> Result<Self, i32> {
512        let mut ptr = std::ptr::null_mut();
513        let status = unsafe {
514            ffi::cm_metadata_format_description_create_with_keys(
515                metadata_type.into(),
516                keys.map_or(std::ptr::null_mut(), CFArray::as_ptr),
517                &mut ptr,
518            )
519        };
520        if status == 0 && !ptr.is_null() {
521            unsafe { Self::from_raw(ptr) }.ok_or(status)
522        } else {
523            Err(status)
524        }
525    }
526
527    /// Create a boxed metadata format description from metadata specification dictionaries.
528    ///
529    /// # Errors
530    ///
531    /// Returns the `OSStatus` reported by Core Media if the description could not be created.
532    pub fn create_with_metadata_specifications(
533        metadata_type: crate::utils::four_char_code::FourCharCode,
534        metadata_specifications: &CFArray,
535    ) -> Result<Self, i32> {
536        let mut ptr = std::ptr::null_mut();
537        let status = unsafe {
538            ffi::cm_metadata_format_description_create_with_metadata_specifications(
539                metadata_type.into(),
540                metadata_specifications.as_ptr(),
541                &mut ptr,
542            )
543        };
544        if status == 0 && !ptr.is_null() {
545            unsafe { Self::from_raw(ptr) }.ok_or(status)
546        } else {
547            Err(status)
548        }
549    }
550
551    /// Extend an existing metadata description with additional metadata specifications.
552    ///
553    /// # Errors
554    ///
555    /// Returns the `OSStatus` reported by Core Media if the extended description could not be created.
556    pub fn extend_with_metadata_specifications(
557        &self,
558        metadata_specifications: &CFArray,
559    ) -> Result<Self, i32> {
560        let mut ptr = std::ptr::null_mut();
561        let status = unsafe {
562            ffi::cm_metadata_format_description_create_with_description_and_metadata_specifications(
563                self.as_ptr(),
564                metadata_specifications.as_ptr(),
565                &mut ptr,
566            )
567        };
568        if status == 0 && !ptr.is_null() {
569            unsafe { Self::from_raw(ptr) }.ok_or(status)
570        } else {
571            Err(status)
572        }
573    }
574
575    /// Merge two metadata format descriptions into a new description.
576    ///
577    /// # Errors
578    ///
579    /// Returns the `OSStatus` reported by Core Media if the merged description could not be created.
580    pub fn merge(&self, other: &Self) -> Result<Self, i32> {
581        let mut ptr = std::ptr::null_mut();
582        let status = unsafe {
583            ffi::cm_metadata_format_description_create_by_merging_descriptions(
584                self.as_ptr(),
585                other.as_ptr(),
586                &mut ptr,
587            )
588        };
589        if status == 0 && !ptr.is_null() {
590            unsafe { Self::from_raw(ptr) }.ok_or(status)
591        } else {
592            Err(status)
593        }
594    }
595
596    /// Copy the metadata identifiers declared by this description.
597    #[must_use]
598    pub fn identifiers(&self) -> Option<CFArray> {
599        let ptr = unsafe { ffi::cm_metadata_format_description_get_identifiers(self.as_ptr()) };
600        unsafe { CFArray::from_raw(ptr) }
601    }
602
603    /// Copy the metadata key dictionary for `local_id`, if present.
604    #[must_use]
605    pub fn key_with_local_id(&self, local_id: u32) -> Option<CFDictionary> {
606        let ptr = unsafe {
607            ffi::cm_metadata_format_description_get_key_with_local_id(self.as_ptr(), local_id)
608        };
609        unsafe { CFDictionary::from_raw(ptr) }
610    }
611}
612
613impl Deref for CMMetadataFormatDescription {
614    type Target = CMFormatDescription;
615
616    fn deref(&self) -> &Self::Target {
617        &self.0
618    }
619}
620
621impl TryFrom<CMFormatDescription> for CMMetadataFormatDescription {
622    type Error = CMFormatDescription;
623
624    fn try_from(value: CMFormatDescription) -> Result<Self, Self::Error> {
625        if value.is_metadata() {
626            Ok(Self(value))
627        } else {
628            Err(value)
629        }
630    }
631}
632
633impl From<CMMetadataFormatDescription> for CMFormatDescription {
634    fn from(value: CMMetadataFormatDescription) -> Self {
635        value.0
636    }
637}
638
639impl fmt::Debug for CMMetadataFormatDescription {
640    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
641        f.debug_struct("CMMetadataFormatDescription")
642            .field("media_type", &self.media_type_string())
643            .field("codec", &self.media_subtype_string())
644            .finish()
645    }
646}
647
648impl fmt::Display for CMMetadataFormatDescription {
649    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650        fmt::Display::fmt(&self.0, f)
651    }
652}