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