core: support DeflateMode option setting
[nihav.git] / nihav-core / src / options.rs
index d3c9eb76b000fc7aa06eb41bd03a28c57e670f05..d3d8be889ccceae1ea06b33f27133af522e6d044 100644 (file)
@@ -6,6 +6,25 @@
 //! As a rule target for options should provide a list of supported options and ignore unknown options.
 
 use std::sync::Arc;
+use std::fmt;
+
+pub use crate::compr::deflate::{DEFLATE_MODE_DESCRIPTION, DEFLATE_OPTION_VALUES, DEFLATE_MODE_NONE, DEFLATE_MODE_FAST, DEFLATE_MODE_BETTER, DEFLATE_MODE_BEST};
+
+/// Common name for keyframe interval option.
+pub const KEYFRAME_OPTION: &str = "key_int";
+/// Common description for keyframe interval option.
+pub const KEYFRAME_OPTION_DESC: &str = "Keyframe interval (0 - automatic)";
+
+/// Common name for frame skipping mode.
+pub const FRAME_SKIP_OPTION: &str = "frame_skip";
+/// Common description for frame skipping mode.
+pub const FRAME_SKIP_OPTION_DESC: &str = "Frame skipping mode";
+/// Frame skipping option value for no skipped frames.
+pub const FRAME_SKIP_OPTION_VAL_NONE: &str = "none";
+/// Frame skipping option value for decoding only keyframes.
+pub const FRAME_SKIP_OPTION_VAL_KEYFRAME: &str = "keyframes";
+/// Frame skipping option value for decoding only intra frames.
+pub const FRAME_SKIP_OPTION_VAL_INTRA: &str = "intra";
 
 /// A list specifying option parsing and validating errors.
 #[derive(Clone,Copy,Debug,PartialEq)]
@@ -23,6 +42,23 @@ pub enum OptionError {
 /// A specialised `Result` type for option parsing/validation.
 pub type OptionResult<T> = Result<T, OptionError>;
 
+/// Option definition type.
+#[derive(Debug)]
+pub enum NAOptionDefinitionType {
+    /// Option may just be present.
+    None,
+    /// Option is a boolean value.
+    Bool,
+    /// Option is an integer with optional minimum and maximum value.
+    Int(Option<i64>, Option<i64>),
+    /// Option is a floating point number with optional minimum and maximum value.
+    Float(Option<f64>, Option<f64>),
+    /// Option is a string with an optional list of allowed values.
+    String(Option<&'static [&'static str]>),
+    /// Option is some binary data.
+    Data,
+}
+
 /// Option definition.
 #[derive(Debug)]
 pub struct NAOptionDefinition {
@@ -30,55 +66,33 @@ pub struct NAOptionDefinition {
     pub name:           &'static str,
     /// Option meaning.
     pub description:    &'static str,
-    /// Minimal value for the option (if applicable).
-    pub min_value:      Option<NAValue>,
-    /// Maximum value for the option (if applicable).
-    pub max_value:      Option<NAValue>,
-    /// Allowed string values (when value is a string).
-    pub valid_strings:  Option<Vec<String>>,
-    /// Default option value.
-    ///
-    /// This is used mainly to tell in which format options should be (e.g. bool or float).
-    pub default_value:  NAValue,
+    /// Option type.
+    pub opt_type:       NAOptionDefinitionType,
 }
 
 impl NAOptionDefinition {
     /// Tries to parse input string(s) as an option and returns new option and number of arguments used (1 or 2) on success.
-    pub fn parse(&self, name: &String, value: Option<&String>) -> OptionResult<(NAOption, usize)> {
+    pub fn parse(&self, name: &str, value: Option<&String>) -> OptionResult<(NAOption, usize)> {
         let no_name = "no".to_owned() + self.name;
         let opt_no_name = "--no".to_owned() + self.name;
-        if name == &no_name || name == &opt_no_name {
-            match self.default_value {
-                NAValue::Bool(_) => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)),
+        if name == no_name || name == opt_no_name {
+            match self.opt_type {
+                NAOptionDefinitionType::Bool => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)),
                 _ => return Err(OptionError::InvalidFormat),
             };
         }
         let opt_name = "--".to_owned() + self.name;
-        if self.name != name && &opt_name != name {
+        if self.name != name && opt_name != name {
             return Err(OptionError::WrongName);
         }
-        match self.default_value {
-            NAValue::None => Ok((NAOption { name: self.name, value: NAValue::None }, 1)),
-            NAValue::Bool(_) => Ok((NAOption { name: self.name, value: NAValue::Bool(true) }, 1)),
-            NAValue::Int(_) => {
-                if let Some(str) = value {
-                    let ret = str.parse::<i32>();
-                    if let Ok(val) = ret {
-                        let opt = NAOption { name: self.name, value: NAValue::Int(val) };
-                        self.check(&opt)?;
-                        Ok((opt, 2))
-                    } else {
-                        Err(OptionError::ParseError)
-                    }
-                } else {
-                    Err(OptionError::ParseError)
-                }
-            },
-            NAValue::Long(_) => {
+        match self.opt_type {
+            NAOptionDefinitionType::None => Ok((NAOption { name: self.name, value: NAValue::None }, 1)),
+            NAOptionDefinitionType::Bool => Ok((NAOption { name: self.name, value: NAValue::Bool(true) }, 1)),
+            NAOptionDefinitionType::Int(_, _) => {
                 if let Some(str) = value {
                     let ret = str.parse::<i64>();
                     if let Ok(val) = ret {
-                        let opt = NAOption { name: self.name, value: NAValue::Long(val) };
+                        let opt = NAOption { name: self.name, value: NAValue::Int(val) };
                         self.check(&opt)?;
                         Ok((opt, 2))
                     } else {
@@ -88,7 +102,7 @@ impl NAOptionDefinition {
                     Err(OptionError::ParseError)
                 }
             },
-            NAValue::Float(_) => {
+            NAOptionDefinitionType::Float(_, _) => {
                 if let Some(str) = value {
                     let ret = str.parse::<f64>();
                     if let Ok(val) = ret {
@@ -102,7 +116,7 @@ impl NAOptionDefinition {
                     Err(OptionError::ParseError)
                 }
             },
-            NAValue::String(_) => {
+            NAOptionDefinitionType::String(_) => {
                 if let Some(str) = value {
                     let opt = NAOption { name: self.name, value: NAValue::String(str.to_string()) };
                     self.check(&opt)?;
@@ -123,52 +137,53 @@ impl NAOptionDefinition {
             NAValue::None => Ok(()),
             NAValue::Bool(_) => Ok(()),
             NAValue::Int(intval) => {
-                if let Some(ref minval) = self.min_value {
-                    let (minres, _) = Self::compare(i64::from(intval), minval)?;
-                    if !minres {
-                        return Err(OptionError::InvalidValue);
-                    }
-                }
-                if let Some(ref maxval) = self.max_value {
-                    let (_, maxres) = Self::compare(i64::from(intval), maxval)?;
-                    if !maxres {
-                        return Err(OptionError::InvalidValue);
-                    }
-                }
-                Ok(())
-            },
-            NAValue::Long(intval) => {
-                if let Some(ref minval) = self.min_value {
-                    let (minres, _) = Self::compare(intval, minval)?;
-                    if !minres {
-                        return Err(OptionError::InvalidValue);
-                    }
-                }
-                if let Some(ref maxval) = self.max_value {
-                    let (_, maxres) = Self::compare(intval, maxval)?;
-                    if !maxres {
-                        return Err(OptionError::InvalidValue);
-                    }
-                }
+                match self.opt_type {
+                    NAOptionDefinitionType::Int(minval, maxval) => {
+                        if let Some(minval) = minval {
+                            if intval < minval { return Err(OptionError::InvalidValue); }
+                        }
+                        if let Some(maxval) = maxval {
+                            if intval > maxval { return Err(OptionError::InvalidValue); }
+                        }
+                    },
+                    NAOptionDefinitionType::Float(minval, maxval) => {
+                        let fval = intval as f64;
+                        if let Some(minval) = minval {
+                            if fval < minval { return Err(OptionError::InvalidValue); }
+                        }
+                        if let Some(maxval) = maxval {
+                            if fval > maxval { return Err(OptionError::InvalidValue); }
+                        }
+                    },
+                    _ => return Err(OptionError::InvalidFormat),
+                };
                 Ok(())
             },
             NAValue::Float(fval) => {
-                if let Some(ref minval) = self.min_value {
-                    let (minres, _) = Self::compare_f64(fval, minval)?;
-                    if !minres {
-                        return Err(OptionError::InvalidValue);
-                    }
-                }
-                if let Some(ref maxval) = self.max_value {
-                    let (_, maxres) = Self::compare_f64(fval, maxval)?;
-                    if !maxres {
-                        return Err(OptionError::InvalidValue);
-                    }
-                }
+                match self.opt_type {
+                    NAOptionDefinitionType::Int(minval, maxval) => {
+                        let intval = fval as i64;
+                        if let Some(minval) = minval {
+                            if intval < minval { return Err(OptionError::InvalidValue); }
+                        }
+                        if let Some(maxval) = maxval {
+                            if intval > maxval { return Err(OptionError::InvalidValue); }
+                        }
+                    },
+                    NAOptionDefinitionType::Float(minval, maxval) => {
+                        if let Some(minval) = minval {
+                            if fval < minval { return Err(OptionError::InvalidValue); }
+                        }
+                        if let Some(maxval) = maxval {
+                            if fval > maxval { return Err(OptionError::InvalidValue); }
+                        }
+                    },
+                    _ => return Err(OptionError::InvalidFormat),
+                };
                 Ok(())
             },
             NAValue::String(ref cur_str) => {
-                if let Some(ref strings) = self.valid_strings {
+                if let NAOptionDefinitionType::String(Some(ref strings)) = self.opt_type {
                     for str in strings.iter() {
                         if cur_str == str {
                             return Ok(());
@@ -182,32 +197,39 @@ impl NAOptionDefinition {
             NAValue::Data(_) => Ok(()),
         }
     }
-    fn compare(val: i64, ref_val: &NAValue) -> OptionResult<(bool, bool)> {
-        match ref_val {
-            NAValue::Int(ref_min) => {
-                Ok((val >= i64::from(*ref_min), val <= i64::from(*ref_min)))
-            },
-            NAValue::Long(ref_min) => {
-                Ok((val >= *ref_min, val <= *ref_min))
-            },
-            NAValue::Float(ref_min) => {
-                Ok(((val as f64) >= *ref_min, (val as f64) <= *ref_min))
-            },
-            _ => Err(OptionError::InvalidFormat),
-        }
-    }
-    fn compare_f64(val: f64, ref_val: &NAValue) -> OptionResult<(bool, bool)> {
-        match ref_val {
-            NAValue::Int(ref_min) => {
-                Ok((val >= f64::from(*ref_min), val <= f64::from(*ref_min)))
+}
+
+impl fmt::Display for NAOptionDefinition {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self.opt_type {
+            NAOptionDefinitionType::None => write!(f, "{}: {}", self.name, self.description),
+            NAOptionDefinitionType::Bool => write!(f, "[no]{}: {}", self.name, self.description),
+            NAOptionDefinitionType::String(ref str_list) => {
+                if let Some(ref opts) = str_list {
+                    write!(f, "{} {}: {}", self.name, opts.join("|"), self.description)
+                } else {
+                    write!(f, "{} <string>: {}", self.name, self.description)
+                }
             },
-            NAValue::Long(ref_min) => {
-                Ok((val >= (*ref_min as f64), val <= (*ref_min as f64)))
+            NAOptionDefinitionType::Int(minval, maxval) => {
+                let range = match (&minval, &maxval) {
+                        (Some(minval), None) => format!("{}-..", minval),
+                        (None, Some(maxval)) => format!("..-{}", maxval),
+                        (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval),
+                        _ => "<integer>".to_string(),
+                    };
+                write!(f, "{} {}: {}", self.name, range, self.description)
             },
-            NAValue::Float(ref_min) => {
-                Ok((val >= *ref_min, val <= *ref_min))
+            NAOptionDefinitionType::Float(minval, maxval) => {
+                let range = match (&minval, &maxval) {
+                        (Some(minval), None) => format!("{}-..", minval),
+                        (None, Some(maxval)) => format!("..-{}", maxval),
+                        (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval),
+                        _ => "<float>".to_string(),
+                    };
+                write!(f, "{} {}: {}", self.name, range, self.description)
             },
-            _ => Err(OptionError::InvalidFormat),
+            NAOptionDefinitionType::Data => write!(f, "{} <binary data>: {}", self.name, self.description),
         }
     }
 }
@@ -229,9 +251,7 @@ pub enum NAValue {
     /// Boolean value.
     Bool(bool),
     /// Integer value.
-    Int(i32),
-    /// Long integer value.
-    Long(i64),
+    Int(i64),
     /// Floating point value.
     Float(f64),
     /// String value.
@@ -256,37 +276,36 @@ mod test {
     #[test]
     fn test_option_validation() {
         let option = NAOption {name: "option", value: NAValue::Int(42) };
-        let mut def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::Float(0.0) };
+        let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Int(None, None) };
         assert!(def.check(&option).is_ok());
-        def.max_value = Some(NAValue::Long(30));
+        def.opt_type = NAOptionDefinitionType::Int(None, Some(30));
         assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
-        def.max_value = None;
-        def.min_value = Some(NAValue::Int(40));
+        def.opt_type = NAOptionDefinitionType::Int(Some(40), None);
         assert!(def.check(&option).is_ok());
         def.name = "option2";
         assert_eq!(def.check(&option), Err(OptionError::WrongName));
         let option = NAOption {name: "option", value: NAValue::String("test".to_string()) };
-        let mut def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::String("".to_string()) };
+        let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::String(None) };
         assert!(def.check(&option).is_ok());
-        def.valid_strings = Some(vec!["a string".to_string(), "test string".to_string()]);
+        def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test string"]));
         assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
-        def.valid_strings = Some(vec!["a string".to_string(), "test".to_string()]);
+        def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test"]));
         assert!(def.check(&option).is_ok());
     }
     #[test]
     fn test_option_parsing() {
-        let mut def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::Float(0.0) };
-        assert_eq!(def.parse(&"--option".to_string(), None), Err(OptionError::ParseError));
-        assert_eq!(def.parse(&"--nooption".to_string(), None), Err(OptionError::InvalidFormat));
-        assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())),
+        let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Float(None, None) };
+        assert_eq!(def.parse("--option", None), Err(OptionError::ParseError));
+        assert_eq!(def.parse("--nooption", None), Err(OptionError::InvalidFormat));
+        assert_eq!(def.parse("--option", Some(&"42".to_string())),
                    Ok((NAOption{name:"option",value: NAValue::Float(42.0)}, 2)));
-        def.max_value = Some(NAValue::Float(40.0));
-        assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())),
+        def.opt_type = NAOptionDefinitionType::Float(None, Some(40.0));
+        assert_eq!(def.parse("--option", Some(&"42".to_string())),
                    Err(OptionError::InvalidValue));
-        let def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::Bool(true) };
-        assert_eq!(def.parse(&"option".to_string(), None),
+        let def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Bool };
+        assert_eq!(def.parse("option", None),
                    Ok((NAOption{name: "option", value: NAValue::Bool(true) }, 1)));
-        assert_eq!(def.parse(&"nooption".to_string(), None),
+        assert_eq!(def.parse("nooption", None),
                    Ok((NAOption{name: "option", value: NAValue::Bool(false) }, 1)));
     }
 }