1use core::ffi::CStr;
2use std::borrow::Cow;
3use std::ffi::CString;
4use std::ptr::NonNull;
5
6use crate::error::{FfmpegError, FfmpegErrorCode};
7use crate::ffi::*;
8use crate::smart_object::SmartPtr;
9use crate::AVDictionaryFlags;
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 {
149 ptr: SmartPtr::wrap(ptr as _, |_| {}),
150 }
151 }
152
153 pub const unsafe fn from_ptr_owned(ptr: *mut AVDictionary) -> Self {
159 Self {
160 ptr: SmartPtr::wrap(ptr, |ptr| {
161 av_dict_free(ptr)
163 }),
164 }
165 }
166
167 pub fn set<'a>(&mut self, key: impl CStringLike<'a>, value: impl CStringLike<'a>) -> Result<(), FfmpegError> {
170 let key = key.into_c_str().ok_or(FfmpegError::Arguments("key cannot be empty"))?;
171 let value = value.into_c_str().ok_or(FfmpegError::Arguments("value cannot be empty"))?;
172
173 FfmpegErrorCode(unsafe { av_dict_set(self.ptr.as_mut(), key.as_ptr(), value.as_ptr(), 0) }).result()?;
175 Ok(())
176 }
177
178 pub fn get<'a>(&self, key: impl CStringLike<'a>) -> Option<&CStr> {
181 let key = key.into_c_str()?;
182
183 let mut entry =
184 NonNull::new(unsafe { av_dict_get(self.as_ptr(), key.as_ptr(), std::ptr::null_mut(), AVDictionaryFlags::IgnoreSuffix.into()) })?;
186
187 let mut_ref = unsafe { entry.as_mut() };
189
190 Some(unsafe { CStr::from_ptr(mut_ref.value) })
192 }
193
194 pub fn is_empty(&self) -> bool {
196 self.iter().next().is_none()
197 }
198
199 pub const fn iter(&self) -> DictionaryIterator {
201 DictionaryIterator::new(self)
202 }
203
204 pub const fn as_ptr(&self) -> *const AVDictionary {
206 self.ptr.as_ptr()
207 }
208
209 pub const fn as_mut_ptr_ref(&mut self) -> &mut *mut AVDictionary {
211 self.ptr.as_mut()
212 }
213
214 pub fn leak(self) -> *mut AVDictionary {
216 self.ptr.into_inner()
217 }
218
219 pub fn extend<'a, K, V>(&mut self, iter: impl IntoIterator<Item = (K, V)>) -> Result<(), FfmpegError>
221 where
222 K: CStringLike<'a>,
223 V: CStringLike<'a>,
224 {
225 for (key, value) in iter {
226 self.set(key, value)?;
228 }
229
230 Ok(())
231 }
232
233 pub fn try_from_iter<'a, K, V>(iter: impl IntoIterator<Item = (K, V)>) -> Result<Self, FfmpegError>
235 where
236 K: CStringLike<'a>,
237 V: CStringLike<'a>,
238 {
239 let mut dict = Self::new();
240 dict.extend(iter)?;
241 Ok(dict)
242 }
243}
244
245pub struct DictionaryIterator<'a> {
247 dict: &'a Dictionary,
248 entry: *mut AVDictionaryEntry,
249}
250
251impl<'a> DictionaryIterator<'a> {
252 const fn new(dict: &'a Dictionary) -> Self {
254 Self {
255 dict,
256 entry: std::ptr::null_mut(),
257 }
258 }
259}
260
261impl<'a> Iterator for DictionaryIterator<'a> {
262 type Item = (&'a CStr, &'a CStr);
263
264 fn next(&mut self) -> Option<Self::Item> {
265 self.entry = unsafe {
267 av_dict_get(
268 self.dict.as_ptr(),
269 &[0i8] as *const libc::c_char,
271 self.entry,
272 AVDictionaryFlags::IgnoreSuffix.into(),
273 )
274 };
275
276 let mut entry = NonNull::new(self.entry)?;
277
278 let entry_ref = unsafe { entry.as_mut() };
280
281 let key = unsafe { CStr::from_ptr(entry_ref.key) };
283 let value = unsafe { CStr::from_ptr(entry_ref.value) };
285
286 Some((key, value))
287 }
288}
289
290impl<'a> IntoIterator for &'a Dictionary {
291 type IntoIter = DictionaryIterator<'a>;
292 type Item = <DictionaryIterator<'a> as Iterator>::Item;
293
294 fn into_iter(self) -> Self::IntoIter {
295 DictionaryIterator::new(self)
296 }
297}
298
299#[cfg(test)]
300#[cfg_attr(all(test, coverage_nightly), coverage(off))]
301mod tests {
302
303 use std::collections::HashMap;
304 use std::ffi::CStr;
305
306 use crate::dict::Dictionary;
307
308 fn sort_hashmap<K: Ord, V>(map: std::collections::HashMap<K, V>) -> std::collections::BTreeMap<K, V> {
309 map.into_iter().collect()
310 }
311
312 #[test]
313 fn test_dict_default_and_items() {
314 let mut dict = Dictionary::default();
315
316 assert!(dict.is_empty(), "Default dictionary should be empty");
317 assert!(dict.as_ptr().is_null(), "Default dictionary pointer should be null");
318
319 dict.set(c"key1", c"value1").expect("Failed to set key1");
320 dict.set(c"key2", c"value2").expect("Failed to set key2");
321 dict.set(c"key3", c"value3").expect("Failed to set key3");
322
323 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
324
325 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
326 {
327 "key1": "value1",
328 "key2": "value2",
329 "key3": "value3",
330 }
331 "#);
332 }
333
334 #[test]
335 fn test_dict_set_empty_key() {
336 let mut dict = Dictionary::new();
337 assert!(dict.set(c"", c"value1").is_err());
338 }
339
340 #[test]
341 fn test_dict_clone_empty() {
342 let empty_dict = Dictionary::new();
343 let cloned_dict = empty_dict.clone();
344
345 assert!(cloned_dict.is_empty(), "Cloned dictionary should be empty");
346 assert!(empty_dict.is_empty(), "Original dictionary should remain empty");
347 }
348
349 #[test]
350 fn test_dict_clone_non_empty() {
351 let mut dict = Dictionary::new();
352 dict.set(c"key1", c"value1").expect("Failed to set key1");
353 dict.set(c"key2", c"value2").expect("Failed to set key2");
354 let mut clone = dict.clone();
355
356 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
357 let clone_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&clone);
358
359 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
360 {
361 "key1": "value1",
362 "key2": "value2",
363 }
364 "#);
365 insta::assert_debug_snapshot!(sort_hashmap(clone_hm), @r#"
366 {
367 "key1": "value1",
368 "key2": "value2",
369 }
370 "#);
371
372 clone
373 .set(c"key3", c"value3")
374 .expect("Failed to set key3 in cloned dictionary");
375
376 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
377 let clone_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&clone);
378 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
379 {
380 "key1": "value1",
381 "key2": "value2",
382 }
383 "#);
384 insta::assert_debug_snapshot!(sort_hashmap(clone_hm), @r#"
385 {
386 "key1": "value1",
387 "key2": "value2",
388 "key3": "value3",
389 }
390 "#);
391 }
392
393 #[test]
394 fn test_dict_get() {
395 let mut dict = Dictionary::new();
396 assert!(
397 dict.get(c"nonexistent_key").is_none(),
398 "Getting a nonexistent key from an empty dictionary should return None"
399 );
400
401 dict.set(c"key1", c"value1").expect("Failed to set key1");
402 dict.set(c"key2", c"value2").expect("Failed to set key2");
403 assert_eq!(dict.get(c"key1"), Some(c"value1"), "The value for 'key1' should be 'value1'");
404 assert_eq!(dict.get(c"key2"), Some(c"value2"), "The value for 'key2' should be 'value2'");
405
406 assert!(dict.get(c"key3").is_none(), "Getting a nonexistent key should return None");
407
408 dict.set(c"special_key!", c"special_value")
409 .expect("Failed to set special_key!");
410 assert_eq!(
411 dict.get(c"special_key!"),
412 Some(c"special_value"),
413 "The value for 'special_key!' should be 'special_value'"
414 );
415
416 assert!(
417 dict.get(c"").is_none(),
418 "Getting an empty key should return None (empty keys are not allowed)"
419 );
420 }
421
422 #[test]
423 fn test_from_hashmap_for_dictionary() {
424 let mut hash_map = std::collections::HashMap::new();
425 hash_map.insert("key1".to_string(), "value1".to_string());
426 hash_map.insert("key2".to_string(), "value2".to_string());
427 hash_map.insert("key3".to_string(), "value3".to_string());
428 let dict = Dictionary::try_from_iter(hash_map).expect("Failed to create dictionary from hashmap");
429
430 let dict_hm: std::collections::HashMap<&CStr, &CStr> = HashMap::from_iter(&dict);
431 insta::assert_debug_snapshot!(sort_hashmap(dict_hm), @r#"
432 {
433 "key1": "value1",
434 "key2": "value2",
435 "key3": "value3",
436 }
437 "#);
438 }
439
440 #[test]
441 fn test_empty_string() {
442 let mut dict = Dictionary::new();
443 assert!(dict.set(c"", c"abc").is_err());
444 assert!(dict.set(c"abc", c"").is_err());
445 assert!(dict.get(c"").is_none());
446 assert!(dict.set("".to_owned(), "abc".to_owned()).is_err());
447 assert!(dict.set("abc".to_owned(), "".to_owned()).is_err());
448 assert!(dict.get("").is_none());
449 assert!(dict.set(c"".to_owned(), c"abc".to_owned()).is_err());
450 assert!(dict.set(c"abc".to_owned(), c"".to_owned()).is_err());
451 assert!(dict.get(c"").is_none());
452 }
453}