Skip to main content

rff_core/
dict.rs

1//! A small ordered string→string map for codec/format options — the role
2//! `AVDictionary` plays in FFmpeg (e.g. `-crf 23`, `-preset fast`). Ordered so
3//! that iteration (and any serialized form) is deterministic.
4
5use std::collections::BTreeMap;
6
7/// An ordered collection of `key = value` options.
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
9pub struct Dictionary {
10    entries: BTreeMap<String, String>,
11}
12
13impl Dictionary {
14    pub fn new() -> Dictionary {
15        Dictionary::default()
16    }
17
18    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
19        self.entries.insert(key.into(), value.into());
20    }
21
22    pub fn get(&self, key: &str) -> Option<&str> {
23        self.entries.get(key).map(String::as_str)
24    }
25
26    /// Parse an integer-valued option, returning `None` if absent or malformed.
27    pub fn get_int(&self, key: &str) -> Option<i64> {
28        self.get(key).and_then(|v| v.parse().ok())
29    }
30
31    /// Parse a bitrate-valued option with FFmpeg's `k`/`M` suffixes
32    /// (`128k` → 128 000, `1M` → 1 000 000; a bare number is taken as-is).
33    /// Case-insensitive; returns `None` if absent or malformed.
34    pub fn get_bitrate(&self, key: &str) -> Option<i64> {
35        let v = self.get(key)?.trim();
36        let (num, mult) = match v.chars().last() {
37            Some('k') | Some('K') => (&v[..v.len() - 1], 1_000i64),
38            Some('m') | Some('M') => (&v[..v.len() - 1], 1_000_000i64),
39            _ => (v, 1),
40        };
41        num.trim().parse::<f64>().ok().map(|n| (n * mult as f64) as i64)
42    }
43
44    pub fn is_empty(&self) -> bool {
45        self.entries.is_empty()
46    }
47
48    pub fn len(&self) -> usize {
49        self.entries.len()
50    }
51
52    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
53        self.entries.iter().map(|(k, v)| (k.as_str(), v.as_str()))
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::Dictionary;
60
61    #[test]
62    fn get_bitrate_parses_suffixes() {
63        let mut d = Dictionary::new();
64        d.set("b", "128k");
65        assert_eq!(d.get_bitrate("b"), Some(128_000));
66        d.set("b", "1M");
67        assert_eq!(d.get_bitrate("b"), Some(1_000_000));
68        d.set("b", "24000");
69        assert_eq!(d.get_bitrate("b"), Some(24_000));
70        d.set("b", "1.5M");
71        assert_eq!(d.get_bitrate("b"), Some(1_500_000));
72        d.set("b", "96K");
73        assert_eq!(d.get_bitrate("b"), Some(96_000));
74        assert_eq!(d.get_bitrate("missing"), None);
75        d.set("b", "garbage");
76        assert_eq!(d.get_bitrate("b"), None);
77    }
78}