X-Git-Url: https://git.nihav.org/?a=blobdiff_plain;f=nihav-core%2Fsrc%2Fdetect.rs;fp=nihav-core%2Fsrc%2Fdetect.rs;h=0b606419b0c91f86b495482e56d0332e39d4de25;hb=5641dccfbf2a70d589cf094a0d4ed5a10f919f00;hp=0000000000000000000000000000000000000000;hpb=b74ff9fac35d41737d71d97227fad233aa4a4b49;p=nihav.git diff --git a/nihav-core/src/detect.rs b/nihav-core/src/detect.rs new file mode 100644 index 0000000..0b60641 --- /dev/null +++ b/nihav-core/src/detect.rs @@ -0,0 +1,268 @@ +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 { + 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 = 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); + } +}