1use core::ffi::CStr;
2use std::borrow::Cow;
3use std::ffi::CString;
4use std::ptr::NonNull;
5
6use crate::AVDictionaryFlags;
7use crate::error::{FfmpegError, FfmpegErrorCode};
8use crate::ffi::*;
9use crate::smart_object::SmartPtr;
10
11pub struct Dictionary {
13 ptr: SmartPtr<AVDictionary>,
14}
15
16unsafe impl Send for Dictionary {}
18
19impl Default for Dictionary {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl std::fmt::Debug for Dictionary {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 let mut map = f.debug_map();
28
29 for (key, value) in self.iter() {
30 map.entry(&key, &value);
31 }
32
33 map.finish()
34 }
35}
36
37impl Clone for Dictionary {
38 fn clone(&self) -> Self {
39 let mut dict = Self::new();
40
41 Self::clone_from(&mut dict, self);
42
43 dict
44 }
45
46 fn clone_from(&mut self, source: &Self) {
47 FfmpegErrorCode::from(unsafe { av_dict_copy(self.as_mut_ptr_ref(), source.as_ptr(), 0) })
49 .result()
50 .expect("Failed to clone dictionary");
51 }
52}
53
54pub trait CStringLike<'a> {
84 fn into_c_str(self) -> Option<Cow<'a, CStr>>;
86}
87
88impl<'a> CStringLike<'a> for String {
89 fn into_c_str(self) -> Option<Cow<'a, CStr>> {
90 if self.is_empty() {
91 return None;
92 }
93
94 Some(Cow::Owned(CString::new(Vec::from(self)).ok()?))
95 }
96}
97
98impl<'a> CStringLike<'a> for &str {
99 fn into_c_str(self) -> Option<Cow<'a, CStr>> {
100 if self.is_empty() {
101 return None;
102 }
103
104 Some(Cow::Owned(CString::new(self.as_bytes().to_vec()).ok()?))
105 }
106}
107
108impl<'a> CStringLike<'a> for &'a CStr {
109 fn into_c_str(self) -> Option<Cow<'a, CStr>> {
110 if self.is_empty() {
111 return None;
112 }
113
114 Some(Cow::Borrowed(self))
115 }
116}
117
118impl<'a> CStringLike<'a> for CString {
119 fn into_c_str(self) -> Option<Cow<'a, CStr>> {
120 if self.is_empty() {
121 return None;
122 }
123
124 Some(Cow::Owned(self))
125 }
126}
127
128impl Dictionary {
129 pub const fn new() -> Self {
131 Self {
132 ptr: SmartPtr::null(|ptr| {
134 unsafe { av_dict_free(ptr) }
136 }),
137 }
138 }
139
140 pub const unsafe fn from_ptr_ref(ptr: *mut AVDictionary) -> Self {
147 Self {
148 ptr: unsafe { SmartPtr::wrap(ptr as _, |_| {}) },
151 }
152 }
153
154 pub const unsafe fn from_ptr_owned(ptr: *mut AVDictionary) -> Self {
160 let destructor = |ptr: &mut *mut AVDictionary| {
161 unsafe { av_dict_free(ptr) }
163 };
164
165 Self {
166 ptr: unsafe { SmartPtr::wrap(ptr, destructor) },
168 }
169 }
170
171 pub fn set<'a>(&mut self, key: impl CStringLike<'a>, value: impl CStringLike<'a>) -> Result<(), FfmpegError> {
174 let key = key.into_c_str().ok_or(FfmpegError::Arguments("key cannot be empty"))?;
175 let value = value.into_c_str().ok_or(FfmpegError::Arguments("value cannot be empty"))?;
176
177 FfmpegErrorCode(unsafe { av_dict_set(self.ptr.as_mut(), key.as_ptr() as *const _, value.as_ptr() as *const _, 0) })
179 .result()?;
180 Ok(())
181 }
182
183 pub fn get<'a>(&self, key: impl CStringLike<'a>) -> Option<&CStr> {
186 let key = key.into_c_str()?;
187
188 let mut entry =
189 NonNull::new(unsafe {
191 av_dict_get(
192 self.as_ptr(),
193 key.as_ptr() as *const _,
194 std::ptr::null_mut(),
195 AVDictionaryFlags::IgnoreSuffix.into(),
196 )
197 })?;
198
199 let mut_ref = unsafe { entry.as_mut() };
201
202 Some(unsafe { CStr::from_ptr(mut_ref.value as *const _) })
204 }
205
206 pub fn is_empty(&self) -> bool {
208 self.iter().next().is_none()
209 }
210
211 pub const fn iter(&self) -> DictionaryIterator {
213 DictionaryIterator::new(self)
214 }
215
216 pub const fn as_ptr(&self) -> *const AVDictionary {
218 self.ptr.as_ptr()
219 }
220
221 pub const fn as_mut_ptr_ref(&mut self) -> &mut *mut AVDictionary {
223 self.ptr.as_mut()
224 }
225
226 pub fn leak(self) -> *mut AVDictionary {
228 self.ptr.into_inner()
229 }
230
231 pub fn extend<'a, K, V>(&mut self, iter: impl IntoIterator<Item = (K, V)>) -> Result<(), FfmpegError>
233 where
234 K: CStringLike<'a>,
235 V: CStringLike<'a>,
236 {
237 for (key, value) in iter {
238 self.set(key, value)?;
240 }
241
242 Ok(())
243 }
244
245 pub fn try_from_iter<'a, K, V>(iter: impl IntoIterator<Item = (K, V)>) -> Result<Self, FfmpegError>
247 where
248 K: CStringLike<'a>,
249 V: CStringLike<'a>,
250 {
251 let mut dict = Self::new();
252 dict.extend(iter)?;
253 Ok(dict)
254 }
255}
256
257pub struct DictionaryIterator<'a> {
259 dict: &'a Dictionary,
260 entry: *mut AVDictionaryEntry,
261}
262
263impl<'a> DictionaryIterator<'a> {
264 const fn new(dict: &'a Dictionary) -> Self {
266 Self {
267 dict,
268 entry: std::ptr::null_mut(),
269 }
270 }
271}
272
273impl<'a> Iterator for DictionaryIterator<'a> {
274 type Item = (&'a CStr, &'a CStr);
275
276 fn next(&mut self) -> Option<Self::Item> {
277 self.entry = unsafe {
279 av_dict_get(
280 self.dict.as_ptr(),
281 c"".as_ptr() as *const _,
283 self.entry,
284 AVDictionaryFlags::IgnoreSuffix.into(),
285 )
286 };
287
288 let mut entry = NonNull::new(self.entry)?;
289
290 let entry_ref = unsafe { entry.as_mut() };
292
293 let key = unsafe { CStr::from_ptr(entry_ref.key as *const _) };
295 let value = unsafe { CStr::from_ptr(entry_ref.value as *const _) };
297
298 Some((key, value))
299 }
300}
301
302impl<'a> IntoIterator for &'a Dictionary {
303 type IntoIter = DictionaryIterator<'a>;
304 type Item = <DictionaryIterator<'a> as Iterator>::Item;
305
306 fn into_iter(self) -> Self::IntoIter {
307 DictionaryIterator::new(self)
308 }
309}
310
311#[cfg(test)]
312#[cfg_attr(all(test, coverage_nightly), coverage(off))]
313mod tests {
314
315 use std::collections::HashMap;
316 use std::ffi::CStr;
317
318 use crate::dict::Dictionary;
319
320 fn sort_hashmap<K: Ord, V>(map: std::collections::HashMap<K, V>) -> std::collections::BTreeMap<K, V> {
321 map.into_iter().collect()
322 }
323
324 #[test]
325 fn test_dict_default_and_items() {
326 let mut dict = Dictionary::default();
327
328 assert!(dict.is_empty(), "Default dictionary should be empty");
329 assert!(dict.as_ptr().is_null(), "Default dictionary pointer should be null");
330
331 dict.set(c"key1", c"value1").expect("Failed to set key1");
332 dict.set(c"key2", c"value2").expect("Failed to set key2");
333 dict.set(c"key3", c"value3").expect("Failed to set key3");
334
335 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
336
337 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
338 {
339 "key1": "value1",
340 "key2": "value2",
341 "key3": "value3",
342 }
343 "#);
344 }
345
346 #[test]
347 fn test_dict_set_empty_key() {
348 let mut dict = Dictionary::new();
349 assert!(dict.set(c"", c"value1").is_err());
350 }
351
352 #[test]
353 fn test_dict_clone_empty() {
354 let empty_dict = Dictionary::new();
355 let cloned_dict = empty_dict.clone();
356
357 assert!(cloned_dict.is_empty(), "Cloned dictionary should be empty");
358 assert!(empty_dict.is_empty(), "Original dictionary should remain empty");
359 }
360
361 #[test]
362 fn test_dict_clone_non_empty() {
363 let mut dict = Dictionary::new();
364 dict.set(c"key1", c"value1").expect("Failed to set key1");
365 dict.set(c"key2", c"value2").expect("Failed to set key2");
366 let mut clone = dict.clone();
367
368 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
369 let clone_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&clone);
370
371 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
372 {
373 "key1": "value1",
374 "key2": "value2",
375 }
376 "#);
377 insta::assert_debug_snapshot!(sort_hashmap(clone_hm), @r#"
378 {
379 "key1": "value1",
380 "key2": "value2",
381 }
382 "#);
383
384 clone
385 .set(c"key3", c"value3")
386 .expect("Failed to set key3 in cloned dictionary");
387
388 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
389 let clone_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&clone);
390 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
391 {
392 "key1": "value1",
393 "key2": "value2",
394 }
395 "#);
396 insta::assert_debug_snapshot!(sort_hashmap(clone_hm), @r#"
397 {
398 "key1": "value1",
399 "key2": "value2",
400 "key3": "value3",
401 }
402 "#);
403 }
404
405 #[test]
406 fn test_dict_get() {
407 let mut dict = Dictionary::new();
408 assert!(
409 dict.get(c"nonexistent_key").is_none(),
410 "Getting a nonexistent key from an empty dictionary should return None"
411 );
412
413 dict.set(c"key1", c"value1").expect("Failed to set key1");
414 dict.set(c"key2", c"value2").expect("Failed to set key2");
415 assert_eq!(dict.get(c"key1"), Some(c"value1"), "The value for 'key1' should be 'value1'");
416 assert_eq!(dict.get(c"key2"), Some(c"value2"), "The value for 'key2' should be 'value2'");
417
418 assert!(dict.get(c"key3").is_none(), "Getting a nonexistent key should return None");
419
420 dict.set(c"special_key!", c"special_value")
421 .expect("Failed to set special_key!");
422 assert_eq!(
423 dict.get(c"special_key!"),
424 Some(c"special_value"),
425 "The value for 'special_key!' should be 'special_value'"
426 );
427
428 assert!(
429 dict.get(c"").is_none(),
430 "Getting an empty key should return None (empty keys are not allowed)"
431 );
432 }
433
434 #[test]
435 fn test_from_hashmap_for_dictionary() {
436 let mut hash_map = std::collections::HashMap::new();
437 hash_map.insert("key1".to_string(), "value1".to_string());
438 hash_map.insert("key2".to_string(), "value2".to_string());
439 hash_map.insert("key3".to_string(), "value3".to_string());
440 let dict = Dictionary::try_from_iter(hash_map).expect("Failed to create dictionary from hashmap");
441
442 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
443 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
444 {
445 "key1": "value1",
446 "key2": "value2",
447 "key3": "value3",
448 }
449 "#);
450 }
451
452 #[test]
453 fn test_empty_string() {
454 let mut dict = Dictionary::new();
455 assert!(dict.set(c"", c"abc").is_err());
456 assert!(dict.set(c"abc", c"").is_err());
457 assert!(dict.get(c"").is_none());
458 assert!(dict.set("".to_owned(), "abc".to_owned()).is_err());
459 assert!(dict.set("abc".to_owned(), "".to_owned()).is_err());
460 assert!(dict.get("").is_none());
461 assert!(dict.set(c"".to_owned(), c"abc".to_owned()).is_err());
462 assert!(dict.set(c"abc".to_owned(), c"".to_owned()).is_err());
463 assert!(dict.get(c"").is_none());
464 }
465}