--- /dev/null
+use std::io::SeekFrom;
+use crate::io::byteio::ByteReader;
+
+#[derive(Debug,Clone,Copy,PartialEq)]
+pub enum DetectionScore {
+ No,
+ ExtensionMatches,
+ MagicMatches,
+}
+
+impl DetectionScore {
+ pub fn less(&self, other: DetectionScore) -> bool {
+ (*self as i32) < (other as i32)
+ }
+}
+
+#[allow(dead_code)]
+enum Arg {
+ Byte(u8),
+ U16BE(u16),
+ U16LE(u16),
+ U24BE(u32),
+ U24LE(u32),
+ U32BE(u32),
+ U32LE(u32),
+ U64BE(u64),
+ U64LE(u64),
+}
+
+impl Arg {
+ fn val(&self) -> u64 {
+ match *self {
+ Arg::Byte(b) => { b as u64 }
+ Arg::U16BE(v) => { v as u64 }
+ Arg::U16LE(v) => { v as u64 }
+ Arg::U24BE(v) => { v as u64 }
+ Arg::U24LE(v) => { v as u64 }
+ Arg::U32BE(v) => { v as u64 }
+ Arg::U32LE(v) => { v as u64 }
+ Arg::U64BE(v) => { v }
+ Arg::U64LE(v) => { v }
+ }
+ }
+ fn read_val(&self, src: &mut ByteReader) -> Option<u64> {
+ match *self {
+ Arg::Byte(_) => {
+ let res = src.peek_byte();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap() as u64)
+ }
+ Arg::U16BE(_) => {
+ let res = src.peek_u16be();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap() as u64)
+ }
+ Arg::U16LE(_) => {
+ let res = src.peek_u16le();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap() as u64)
+ }
+ Arg::U24BE(_) => {
+ let res = src.peek_u24be();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap() as u64)
+ }
+ Arg::U24LE(_) => {
+ let res = src.peek_u24le();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap() as u64)
+ }
+ Arg::U32BE(_) => {
+ let res = src.peek_u32be();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap() as u64)
+ }
+ Arg::U32LE(_) => {
+ let res = src.peek_u32le();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap() as u64)
+ }
+ Arg::U64BE(_) => {
+ let res = src.peek_u64be();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap())
+ }
+ Arg::U64LE(_) => {
+ let res = src.peek_u64le();
+ if let Err(_) = res { return None; }
+ Some(res.unwrap())
+ }
+ }
+ }
+ fn eq(&self, src: &mut ByteReader) -> bool {
+ let val = self.read_val(src);
+ if let None = val { false }
+ else { val.unwrap() == self.val() }
+ }
+ fn ge(&self, src: &mut ByteReader) -> bool {
+ let val = self.read_val(src);
+ if let None = val { false }
+ else { val.unwrap() >= self.val() }
+ }
+ fn gt(&self, src: &mut ByteReader) -> bool {
+ let val = self.read_val(src);
+ if let None = val { false }
+ else { val.unwrap() > self.val() }
+ }
+ fn le(&self, src: &mut ByteReader) -> bool {
+ let val = self.read_val(src);
+ if let None = val { false }
+ else { val.unwrap() <= self.val() }
+ }
+ fn lt(&self, src: &mut ByteReader) -> bool {
+ let val = self.read_val(src);
+ if let None = val { false }
+ else { val.unwrap() < self.val() }
+ }
+}
+
+#[allow(dead_code)]
+enum CC<'a> {
+ Or(&'a CC<'a>, &'a CC<'a>),
+ Eq(Arg),
+ Str(&'static [u8]),
+ In(Arg, Arg),
+ Lt(Arg),
+ Le(Arg),
+ Gt(Arg),
+ Ge(Arg),
+}
+
+impl<'a> CC<'a> {
+ fn eval(&self, src: &mut ByteReader) -> bool {
+ match *self {
+ CC::Or (ref a, ref b) => { a.eval(src) || b.eval(src) },
+ CC::Eq(ref arg) => { arg.eq(src) },
+ CC::In(ref a, ref b) => { a.le(src) && b.ge(src) },
+ CC::Lt(ref arg) => { arg.lt(src) },
+ CC::Le(ref arg) => { arg.le(src) },
+ CC::Gt(ref arg) => { arg.gt(src) },
+ CC::Ge(ref arg) => { arg.ge(src) },
+ CC::Str(str) => {
+ let mut val: Vec<u8> = Vec::with_capacity(str.len());
+ val.resize(str.len(), 0);
+ let res = src.peek_buf(val.as_mut_slice());
+ if let Err(_) = res { return false; }
+ val == str
+ }
+ }
+ }
+}
+
+struct CheckItem<'a> {
+ offs: u32,
+ cond: &'a CC<'a>,
+}
+
+#[allow(dead_code)]
+struct DetectConditions<'a> {
+ demux_name: &'static str,
+ extensions: &'static str,
+ conditions: &'a [CheckItem<'a>],
+}
+
+const DETECTORS: &[DetectConditions] = &[
+ DetectConditions {
+ demux_name: "avi",
+ extensions: ".avi",
+ conditions: &[CheckItem{offs: 0, cond: &CC::Or(&CC::Str(b"RIFF"), &CC::Str(b"ON2 ")) },
+ CheckItem{offs: 8, cond: &CC::Or(&CC::Or(&CC::Str(b"AVI LIST"),
+ &CC::Str(b"AVIXLIST")),
+ &CC::Str(b"ON2fLIST")) },
+ ]
+ },
+ DetectConditions {
+ demux_name: "gdv",
+ extensions: ".gdv",
+ conditions: &[CheckItem{offs: 0, cond: &CC::Eq(Arg::U32LE(0x29111994))}],
+ },
+ DetectConditions {
+ demux_name: "realaudio",
+ extensions: ".ra,.ram",
+ conditions: &[CheckItem{offs: 0, cond: &CC::Str(b".ra\xFD")}],
+ },
+ DetectConditions {
+ demux_name: "realmedia",
+ extensions: ".rm,.rmvb,.rma,.ra,.ram",
+ conditions: &[CheckItem{offs: 0, cond: &CC::Or(&CC::Str(b".RMF"), &CC::Str(b".RMP")) },
+ CheckItem{offs: 4, cond: &CC::Ge(Arg::U32BE(10))}],
+ },
+ DetectConditions {
+ demux_name: "real_ivr",
+ extensions: ".ivr",
+ conditions: &[CheckItem{offs: 0, cond: &CC::Or(&CC::Str(b".R1M"), &CC::Str(b".REC"))}],
+ },
+];
+
+pub fn detect_format(name: &str, src: &mut ByteReader) -> Option<(&'static str, DetectionScore)> {
+ let mut result = None;
+ let lname = name.to_lowercase();
+ for detector in DETECTORS {
+ let mut score = DetectionScore::No;
+ if name.len() > 0 {
+ for ext in detector.extensions.split(',') {
+ if lname.ends_with(ext) {
+ score = DetectionScore::ExtensionMatches;
+ break;
+ }
+ }
+ }
+ let mut passed = detector.conditions.len() > 0;
+ for ck in detector.conditions {
+ let ret = src.seek(SeekFrom::Start(ck.offs as u64));
+ if let Err(_) = ret {
+ passed = false;
+ break;
+ }
+ if !ck.cond.eval(src) {
+ passed = false;
+ break;
+ }
+ }
+ if passed {
+ score = DetectionScore::MagicMatches;
+ }
+ if score == DetectionScore::MagicMatches {
+ return Some((detector.demux_name, score));
+ }
+ if let None = result {
+ result = Some((detector.demux_name, score));
+ } else {
+ let (_, oldsc) = result.unwrap();
+ if oldsc.less(score) {
+ result = Some((detector.demux_name, score));
+ }
+ }
+ }
+ result
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::fs::File;
+ use crate::io::byteio::*;
+
+ #[test]
+ fn test_avi_detect() {
+ let name = "assets/laser05.avi";
+ let mut file = File::open(name).unwrap();
+ let mut fr = FileReader::new_read(&mut file);
+ let mut br = ByteReader::new(&mut fr);
+ let (name, score) = detect_format(name, &mut br).unwrap();
+ assert_eq!(name, "avi");
+ assert_eq!(score, DetectionScore::MagicMatches);
+ }
+
+ #[test]
+ fn test_gdv_detect() {
+ let name = "assets/intro1.gdv";
+ let mut file = File::open(name).unwrap();
+ let mut fr = FileReader::new_read(&mut file);
+ let mut br = ByteReader::new(&mut fr);
+ let (name, score) = detect_format(name, &mut br).unwrap();
+ assert_eq!(name, "gdv");
+ assert_eq!(score, DetectionScore::MagicMatches);
+ }
+}