core: support DeflateMode option setting
[nihav.git] / nihav-core / src / options.rs
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;
9 use std::fmt;
10
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
13 /// Common name for keyframe interval option.
14 pub const KEYFRAME_OPTION: &str = "key_int";
15 /// Common description for keyframe interval option.
16 pub const KEYFRAME_OPTION_DESC: &str = "Keyframe interval (0 - automatic)";
17
18 /// Common name for frame skipping mode.
19 pub const FRAME_SKIP_OPTION: &str = "frame_skip";
20 /// Common description for frame skipping mode.
21 pub const FRAME_SKIP_OPTION_DESC: &str = "Frame skipping mode";
22 /// Frame skipping option value for no skipped frames.
23 pub const FRAME_SKIP_OPTION_VAL_NONE: &str = "none";
24 /// Frame skipping option value for decoding only keyframes.
25 pub const FRAME_SKIP_OPTION_VAL_KEYFRAME: &str = "keyframes";
26 /// Frame skipping option value for decoding only intra frames.
27 pub const FRAME_SKIP_OPTION_VAL_INTRA: &str = "intra";
28
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
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
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,
69 /// Option type.
70 pub opt_type: NAOptionDefinitionType,
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.
75 pub fn parse(&self, name: &str, value: Option<&String>) -> OptionResult<(NAOption, usize)> {
76 let no_name = "no".to_owned() + self.name;
77 let opt_no_name = "--no".to_owned() + self.name;
78 if name == no_name || name == opt_no_name {
79 match self.opt_type {
80 NAOptionDefinitionType::Bool => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)),
81 _ => return Err(OptionError::InvalidFormat),
82 };
83 }
84 let opt_name = "--".to_owned() + self.name;
85 if self.name != name && opt_name != name {
86 return Err(OptionError::WrongName);
87 }
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(_, _) => {
92 if let Some(str) = value {
93 let ret = str.parse::<i64>();
94 if let Ok(val) = ret {
95 let opt = NAOption { name: self.name, value: NAValue::Int(val) };
96 self.check(&opt)?;
97 Ok((opt, 2))
98 } else {
99 Err(OptionError::ParseError)
100 }
101 } else {
102 Err(OptionError::ParseError)
103 }
104 },
105 NAOptionDefinitionType::Float(_, _) => {
106 if let Some(str) = value {
107 let ret = str.parse::<f64>();
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 },
119 NAOptionDefinitionType::String(_) => {
120 if let Some(str) = value {
121 let opt = NAOption { name: self.name, value: NAValue::String(str.to_string()) };
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) => {
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 };
160 Ok(())
161 },
162 NAValue::Float(fval) => {
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 };
183 Ok(())
184 },
185 NAValue::String(ref cur_str) => {
186 if let NAOptionDefinitionType::String(Some(ref strings)) = self.opt_type {
187 for str in strings.iter() {
188 if cur_str == str {
189 return Ok(());
190 }
191 }
192 Err(OptionError::InvalidValue)
193 } else {
194 Ok(())
195 }
196 },
197 NAValue::Data(_) => Ok(()),
198 }
199 }
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) => {
208 if let Some(ref opts) = str_list {
209 write!(f, "{} {}: {}", self.name, opts.join("|"), self.description)
210 } else {
211 write!(f, "{} <string>: {}", self.name, self.description)
212 }
213 },
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)
222 },
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)
231 },
232 NAOptionDefinitionType::Data => write!(f, "{} <binary data>: {}", self.name, self.description),
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.
254 Int(i64),
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) };
279 let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Int(None, None) };
280 assert!(def.check(&option).is_ok());
281 def.opt_type = NAOptionDefinitionType::Int(None, Some(30));
282 assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
283 def.opt_type = NAOptionDefinitionType::Int(Some(40), None);
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()) };
288 let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::String(None) };
289 assert!(def.check(&option).is_ok());
290 def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test string"]));
291 assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
292 def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test"]));
293 assert!(def.check(&option).is_ok());
294 }
295 #[test]
296 fn test_option_parsing() {
297 let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Float(None, None) };
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())),
301 Ok((NAOption{name:"option",value: NAValue::Float(42.0)}, 2)));
302 def.opt_type = NAOptionDefinitionType::Float(None, Some(40.0));
303 assert_eq!(def.parse("--option", Some(&"42".to_string())),
304 Err(OptionError::InvalidValue));
305 let def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Bool };
306 assert_eq!(def.parse("option", None),
307 Ok((NAOption{name: "option", value: NAValue::Bool(true) }, 1)));
308 assert_eq!(def.parse("nooption", None),
309 Ok((NAOption{name: "option", value: NAValue::Bool(false) }, 1)));
310 }
311 }