Skip to main content

apple_cf/cm/
format_description.rs

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