]>
Commit | Line | Data |
---|---|---|
a0ddfb3d KS |
1 | //! Options support. |
2 | //! | |
3 | //! This module contains the definitions for options and names for common options. | |
4 | //! Options are used to set custom parameters in e.g. decoders or muxers. | |
5 | //! | |
6 | //! As a rule target for options should provide a list of supported options and ignore unknown options. | |
7 | ||
8 | use std::sync::Arc; | |
8a7d01e2 | 9 | use std::fmt; |
a0ddfb3d | 10 | |
ee0ca773 | 11 | /// Common name for keyframe interval option. |
b36f412c | 12 | pub const KEYFRAME_OPTION: &str = "key_int"; |
ee0ca773 | 13 | /// Common description for keyframe interval option. |
b36f412c | 14 | pub const KEYFRAME_OPTION_DESC: &str = "Keyframe interval (0 - automatic)"; |
ee0ca773 | 15 | |
0975e7e7 | 16 | /// Common name for frame skipping mode. |
b36f412c | 17 | pub const FRAME_SKIP_OPTION: &str = "frame_skip"; |
0975e7e7 | 18 | /// Common description for frame skipping mode. |
b36f412c | 19 | pub const FRAME_SKIP_OPTION_DESC: &str = "Frame skipping mode"; |
0975e7e7 | 20 | /// Frame skipping option value for no skipped frames. |
b36f412c | 21 | pub const FRAME_SKIP_OPTION_VAL_NONE: &str = "none"; |
0975e7e7 | 22 | /// Frame skipping option value for decoding only keyframes. |
b36f412c | 23 | pub const FRAME_SKIP_OPTION_VAL_KEYFRAME: &str = "keyframes"; |
0975e7e7 | 24 | /// Frame skipping option value for decoding only intra frames. |
b36f412c | 25 | pub const FRAME_SKIP_OPTION_VAL_INTRA: &str = "intra"; |
0975e7e7 | 26 | |
a0ddfb3d KS |
27 | /// A list specifying option parsing and validating errors. |
28 | #[derive(Clone,Copy,Debug,PartialEq)] | |
29 | pub enum OptionError { | |
30 | /// Input is not intended for the current option definition. | |
31 | WrongName, | |
32 | /// Option value is not in the expected format. | |
33 | InvalidFormat, | |
34 | /// Option value was not in the range. | |
35 | InvalidValue, | |
36 | /// Parse error. | |
37 | ParseError, | |
38 | } | |
39 | ||
40 | /// A specialised `Result` type for option parsing/validation. | |
41 | pub type OptionResult<T> = Result<T, OptionError>; | |
42 | ||
8a7d01e2 KS |
43 | /// Option definition type. |
44 | #[derive(Debug)] | |
45 | pub enum NAOptionDefinitionType { | |
46 | /// Option may just be present. | |
47 | None, | |
48 | /// Option is a boolean value. | |
49 | Bool, | |
50 | /// Option is an integer with optional minimum and maximum value. | |
51 | Int(Option<i64>, Option<i64>), | |
52 | /// Option is a floating point number with optional minimum and maximum value. | |
53 | Float(Option<f64>, Option<f64>), | |
54 | /// Option is a string with an optional list of allowed values. | |
55 | String(Option<&'static [&'static str]>), | |
56 | /// Option is some binary data. | |
57 | Data, | |
58 | } | |
59 | ||
a0ddfb3d KS |
60 | /// Option definition. |
61 | #[derive(Debug)] | |
62 | pub struct NAOptionDefinition { | |
63 | /// Option name. | |
64 | pub name: &'static str, | |
65 | /// Option meaning. | |
66 | pub description: &'static str, | |
8a7d01e2 KS |
67 | /// Option type. |
68 | pub opt_type: NAOptionDefinitionType, | |
a0ddfb3d KS |
69 | } |
70 | ||
71 | impl NAOptionDefinition { | |
72 | /// Tries to parse input string(s) as an option and returns new option and number of arguments used (1 or 2) on success. | |
b36f412c | 73 | pub fn parse(&self, name: &str, value: Option<&String>) -> OptionResult<(NAOption, usize)> { |
a0ddfb3d KS |
74 | let no_name = "no".to_owned() + self.name; |
75 | let opt_no_name = "--no".to_owned() + self.name; | |
b36f412c | 76 | if name == no_name || name == opt_no_name { |
8a7d01e2 KS |
77 | match self.opt_type { |
78 | NAOptionDefinitionType::Bool => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)), | |
a0ddfb3d KS |
79 | _ => return Err(OptionError::InvalidFormat), |
80 | }; | |
81 | } | |
82 | let opt_name = "--".to_owned() + self.name; | |
b36f412c | 83 | if self.name != name && opt_name != name { |
a0ddfb3d KS |
84 | return Err(OptionError::WrongName); |
85 | } | |
8a7d01e2 KS |
86 | match self.opt_type { |
87 | NAOptionDefinitionType::None => Ok((NAOption { name: self.name, value: NAValue::None }, 1)), | |
88 | NAOptionDefinitionType::Bool => Ok((NAOption { name: self.name, value: NAValue::Bool(true) }, 1)), | |
89 | NAOptionDefinitionType::Int(_, _) => { | |
a0ddfb3d KS |
90 | if let Some(str) = value { |
91 | let ret = str.parse::<i64>(); | |
92 | if let Ok(val) = ret { | |
8a7d01e2 | 93 | let opt = NAOption { name: self.name, value: NAValue::Int(val) }; |
a0ddfb3d KS |
94 | self.check(&opt)?; |
95 | Ok((opt, 2)) | |
96 | } else { | |
97 | Err(OptionError::ParseError) | |
98 | } | |
99 | } else { | |
100 | Err(OptionError::ParseError) | |
101 | } | |
102 | }, | |
8a7d01e2 | 103 | NAOptionDefinitionType::Float(_, _) => { |
a0ddfb3d KS |
104 | if let Some(str) = value { |
105 | let ret = str.parse::<f64>(); | |
106 | if let Ok(val) = ret { | |
107 | let opt = NAOption { name: self.name, value: NAValue::Float(val) }; | |
108 | self.check(&opt)?; | |
109 | Ok((opt, 2)) | |
110 | } else { | |
111 | Err(OptionError::ParseError) | |
112 | } | |
113 | } else { | |
114 | Err(OptionError::ParseError) | |
115 | } | |
116 | }, | |
8a7d01e2 | 117 | NAOptionDefinitionType::String(_) => { |
a0ddfb3d KS |
118 | if let Some(str) = value { |
119 | let opt = NAOption { name: self.name, value: NAValue::String(str.to_string()) }; | |
120 | self.check(&opt)?; | |
121 | Ok((opt, 2)) | |
122 | } else { | |
123 | Err(OptionError::ParseError) | |
124 | } | |
125 | }, | |
126 | _ => unimplemented!(), | |
127 | } | |
128 | } | |
129 | /// Checks whether input option conforms to the definition i.e. whether it has proper format and it lies in range. | |
130 | pub fn check(&self, option: &NAOption) -> OptionResult<()> { | |
131 | if option.name != self.name { | |
132 | return Err(OptionError::WrongName); | |
133 | } | |
134 | match option.value { | |
135 | NAValue::None => Ok(()), | |
136 | NAValue::Bool(_) => Ok(()), | |
137 | NAValue::Int(intval) => { | |
8a7d01e2 KS |
138 | match self.opt_type { |
139 | NAOptionDefinitionType::Int(minval, maxval) => { | |
140 | if let Some(minval) = minval { | |
141 | if intval < minval { return Err(OptionError::InvalidValue); } | |
142 | } | |
143 | if let Some(maxval) = maxval { | |
144 | if intval > maxval { return Err(OptionError::InvalidValue); } | |
145 | } | |
146 | }, | |
147 | NAOptionDefinitionType::Float(minval, maxval) => { | |
148 | let fval = intval as f64; | |
149 | if let Some(minval) = minval { | |
150 | if fval < minval { return Err(OptionError::InvalidValue); } | |
151 | } | |
152 | if let Some(maxval) = maxval { | |
153 | if fval > maxval { return Err(OptionError::InvalidValue); } | |
154 | } | |
155 | }, | |
156 | _ => return Err(OptionError::InvalidFormat), | |
157 | }; | |
a0ddfb3d KS |
158 | Ok(()) |
159 | }, | |
160 | NAValue::Float(fval) => { | |
8a7d01e2 KS |
161 | match self.opt_type { |
162 | NAOptionDefinitionType::Int(minval, maxval) => { | |
163 | let intval = fval as i64; | |
164 | if let Some(minval) = minval { | |
165 | if intval < minval { return Err(OptionError::InvalidValue); } | |
166 | } | |
167 | if let Some(maxval) = maxval { | |
168 | if intval > maxval { return Err(OptionError::InvalidValue); } | |
169 | } | |
170 | }, | |
171 | NAOptionDefinitionType::Float(minval, maxval) => { | |
172 | if let Some(minval) = minval { | |
173 | if fval < minval { return Err(OptionError::InvalidValue); } | |
174 | } | |
175 | if let Some(maxval) = maxval { | |
176 | if fval > maxval { return Err(OptionError::InvalidValue); } | |
177 | } | |
178 | }, | |
179 | _ => return Err(OptionError::InvalidFormat), | |
180 | }; | |
a0ddfb3d KS |
181 | Ok(()) |
182 | }, | |
183 | NAValue::String(ref cur_str) => { | |
8a7d01e2 | 184 | if let NAOptionDefinitionType::String(Some(ref strings)) = self.opt_type { |
a0ddfb3d KS |
185 | for str in strings.iter() { |
186 | if cur_str == str { | |
187 | return Ok(()); | |
188 | } | |
189 | } | |
190 | Err(OptionError::InvalidValue) | |
191 | } else { | |
192 | Ok(()) | |
193 | } | |
194 | }, | |
195 | NAValue::Data(_) => Ok(()), | |
196 | } | |
197 | } | |
8a7d01e2 KS |
198 | } |
199 | ||
200 | impl fmt::Display for NAOptionDefinition { | |
201 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
202 | match self.opt_type { | |
203 | NAOptionDefinitionType::None => write!(f, "{}: {}", self.name, self.description), | |
204 | NAOptionDefinitionType::Bool => write!(f, "[no]{}: {}", self.name, self.description), | |
205 | NAOptionDefinitionType::String(ref str_list) => { | |
206 | if let Some(ref opts) = str_list { | |
207 | write!(f, "{} {}: {}", self.name, opts.join("|"), self.description) | |
208 | } else { | |
209 | write!(f, "{} <string>: {}", self.name, self.description) | |
210 | } | |
a0ddfb3d | 211 | }, |
8a7d01e2 KS |
212 | NAOptionDefinitionType::Int(minval, maxval) => { |
213 | let range = match (&minval, &maxval) { | |
214 | (Some(minval), None) => format!("{}-..", minval), | |
215 | (None, Some(maxval)) => format!("..-{}", maxval), | |
216 | (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval), | |
217 | _ => "<integer>".to_string(), | |
218 | }; | |
219 | write!(f, "{} {}: {}", self.name, range, self.description) | |
a0ddfb3d | 220 | }, |
8a7d01e2 KS |
221 | NAOptionDefinitionType::Float(minval, maxval) => { |
222 | let range = match (&minval, &maxval) { | |
223 | (Some(minval), None) => format!("{}-..", minval), | |
224 | (None, Some(maxval)) => format!("..-{}", maxval), | |
225 | (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval), | |
226 | _ => "<float>".to_string(), | |
227 | }; | |
228 | write!(f, "{} {}: {}", self.name, range, self.description) | |
a0ddfb3d | 229 | }, |
8a7d01e2 | 230 | NAOptionDefinitionType::Data => write!(f, "{} <binary data>: {}", self.name, self.description), |
a0ddfb3d KS |
231 | } |
232 | } | |
233 | } | |
234 | ||
235 | /// Option. | |
236 | #[derive(Clone,Debug,PartialEq)] | |
237 | pub struct NAOption { | |
238 | /// Option name. | |
239 | pub name: &'static str, | |
240 | /// Option value. | |
241 | pub value: NAValue, | |
242 | } | |
243 | ||
244 | /// A list of accepted option values. | |
245 | #[derive(Debug,Clone,PartialEq)] | |
246 | pub enum NAValue { | |
247 | /// Empty value. | |
248 | None, | |
249 | /// Boolean value. | |
250 | Bool(bool), | |
251 | /// Integer value. | |
8a7d01e2 | 252 | Int(i64), |
a0ddfb3d KS |
253 | /// Floating point value. |
254 | Float(f64), | |
255 | /// String value. | |
256 | String(String), | |
257 | /// Binary data value. | |
258 | Data(Arc<Vec<u8>>), | |
259 | } | |
260 | ||
261 | /// Trait for all objects that handle `NAOption`. | |
262 | pub trait NAOptionHandler { | |
263 | /// Returns the options recognised by current object. | |
264 | fn get_supported_options(&self) -> &[NAOptionDefinition]; | |
265 | /// Passes options for the object to set (or ignore). | |
266 | fn set_options(&mut self, options: &[NAOption]); | |
267 | /// Queries the current option value in the object (if present). | |
268 | fn query_option_value(&self, name: &str) -> Option<NAValue>; | |
269 | } | |
270 | ||
271 | #[cfg(test)] | |
272 | mod test { | |
273 | use super::*; | |
274 | #[test] | |
275 | fn test_option_validation() { | |
276 | let option = NAOption {name: "option", value: NAValue::Int(42) }; | |
8a7d01e2 | 277 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Int(None, None) }; |
a0ddfb3d | 278 | assert!(def.check(&option).is_ok()); |
8a7d01e2 | 279 | def.opt_type = NAOptionDefinitionType::Int(None, Some(30)); |
a0ddfb3d | 280 | assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); |
8a7d01e2 | 281 | def.opt_type = NAOptionDefinitionType::Int(Some(40), None); |
a0ddfb3d KS |
282 | assert!(def.check(&option).is_ok()); |
283 | def.name = "option2"; | |
284 | assert_eq!(def.check(&option), Err(OptionError::WrongName)); | |
285 | let option = NAOption {name: "option", value: NAValue::String("test".to_string()) }; | |
8a7d01e2 | 286 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::String(None) }; |
a0ddfb3d | 287 | assert!(def.check(&option).is_ok()); |
8a7d01e2 | 288 | def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test string"])); |
a0ddfb3d | 289 | assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); |
8a7d01e2 | 290 | def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test"])); |
a0ddfb3d KS |
291 | assert!(def.check(&option).is_ok()); |
292 | } | |
293 | #[test] | |
294 | fn test_option_parsing() { | |
8a7d01e2 | 295 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Float(None, None) }; |
b36f412c KS |
296 | assert_eq!(def.parse("--option", None), Err(OptionError::ParseError)); |
297 | assert_eq!(def.parse("--nooption", None), Err(OptionError::InvalidFormat)); | |
298 | assert_eq!(def.parse("--option", Some(&"42".to_string())), | |
a0ddfb3d | 299 | Ok((NAOption{name:"option",value: NAValue::Float(42.0)}, 2))); |
8a7d01e2 | 300 | def.opt_type = NAOptionDefinitionType::Float(None, Some(40.0)); |
b36f412c | 301 | assert_eq!(def.parse("--option", Some(&"42".to_string())), |
a0ddfb3d | 302 | Err(OptionError::InvalidValue)); |
8a7d01e2 | 303 | let def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Bool }; |
b36f412c | 304 | assert_eq!(def.parse("option", None), |
a0ddfb3d | 305 | Ok((NAOption{name: "option", value: NAValue::Bool(true) }, 1))); |
b36f412c | 306 | assert_eq!(def.parse("nooption", None), |
a0ddfb3d KS |
307 | Ok((NAOption{name: "option", value: NAValue::Bool(false) }, 1))); |
308 | } | |
309 | } |