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