add PNM image sequence as possible input source
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 13 Aug 2023 16:04:00 +0000 (18:04 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 13 Aug 2023 16:04:00 +0000 (18:04 +0200)
src/demux.rs
src/imgseq.rs [new file with mode: 0644]
src/main.rs

index 0297c2fadc25636c181da1eff8f7c1ac69ede595..61ddbaf08646d747fb052a6a61cbcd0e9f61cfe1 100644 (file)
@@ -6,6 +6,7 @@ use nihav_registry::detect;
 use nihav_core::io::byteio::ByteReader;
 use nihav_allstuff::*;
 use crate::null::*;
+use crate::imgseq::*;
 
 pub struct FullRegister {
     pub dmx_reg:    RegisteredDemuxers,
@@ -57,6 +58,7 @@ pub enum DemuxerObject<'a> {
     Normal(Demuxer<'a>),
     Raw(RawDemuxer<'a>, Vec<Option<Box<dyn NAPacketiser + Send>>>, bool),
     RawStream(RawStreamCtx<'a>),
+    ImageSequence(ImgSeqDemuxer),
 }
 
 impl<'a> DemuxerObject<'a> {
@@ -141,6 +143,9 @@ impl<'a> DemuxerObject<'a> {
             DemuxerObject::None
         }
     }
+    pub fn create_imgseq(isd: ImgSeqDemuxer) -> Self {
+        DemuxerObject::ImageSequence(isd)
+    }
     pub fn is_none(&self) -> bool {
         matches!(*self, DemuxerObject::None)
     }
@@ -160,6 +165,7 @@ impl<'a> DemuxerObject<'a> {
             DemuxerObject::Normal(ref dmx) => dmx.get_num_streams(),
             DemuxerObject::Raw(ref dmx, _, _) => dmx.get_num_streams(),
             DemuxerObject::RawStream(_) => 1,
+            DemuxerObject::ImageSequence(_) => 1,
         }
     }
     pub fn get_stream(&self, idx: usize) -> Option<NAStreamRef> {
@@ -167,6 +173,7 @@ impl<'a> DemuxerObject<'a> {
             DemuxerObject::Normal(ref dmx) => dmx.get_stream(idx),
             DemuxerObject::Raw(ref dmx, _, _) => dmx.get_stream(idx),
             DemuxerObject::RawStream(ref ctx) if idx == 0 => Some(ctx.stream.clone()),
+            DemuxerObject::ImageSequence(ref ctx) if idx == 0 => Some(ctx.stream.clone()),
             _ => None,
         }
     }
@@ -175,6 +182,7 @@ impl<'a> DemuxerObject<'a> {
             DemuxerObject::Normal(ref dmx) => dmx.get_streams(),
             DemuxerObject::Raw(ref dmx, _, _) => dmx.get_streams(),
             DemuxerObject::RawStream(ref ctx) => ctx.sm.iter(),
+            DemuxerObject::ImageSequence(ref ctx) => ctx.sm.iter(),
             _ => unreachable!(),
         }
     }
@@ -183,6 +191,7 @@ impl<'a> DemuxerObject<'a> {
             DemuxerObject::Normal(ref dmx) => dmx.get_stream_manager(),
             DemuxerObject::Raw(ref dmx, _, _) => dmx.get_stream_manager(),
             DemuxerObject::RawStream(ref ctx) => &ctx.sm,
+            DemuxerObject::ImageSequence(ref ctx) => &ctx.sm,
             _ => unreachable!(),
         }
     }
@@ -262,6 +271,7 @@ impl<'a> DemuxerObject<'a> {
                     };
                 }
             },
+            DemuxerObject::ImageSequence(ref mut ctx) => ctx.get_frame(),
             _ => unreachable!(),
         }
     }
@@ -269,6 +279,7 @@ impl<'a> DemuxerObject<'a> {
         match *self {
             DemuxerObject::Normal(ref mut dmx) => dmx.seek(seek_time),
             DemuxerObject::Raw(ref mut dmx, _, _) => dmx.seek(seek_time),
+            DemuxerObject::ImageSequence(ref mut ctx) => ctx.seek(seek_time),
             _ => Err(DemuxerError::NotImplemented),
         }
     }
@@ -279,6 +290,7 @@ impl<'a> NAOptionHandler for DemuxerObject<'a> {
         match *self {
             DemuxerObject::Normal(ref dmx) => dmx.get_supported_options(),
             DemuxerObject::Raw(ref dmx, _, _) => dmx.get_supported_options(),
+            DemuxerObject::ImageSequence(ref ctx) => ctx.get_supported_options(),
             _ => &[],
         }
     }
@@ -286,6 +298,7 @@ impl<'a> NAOptionHandler for DemuxerObject<'a> {
         match *self {
             DemuxerObject::Normal(ref mut dmx) => dmx.set_options(options),
             DemuxerObject::Raw(ref mut dmx, _, _) => dmx.set_options(options),
+            DemuxerObject::ImageSequence(ref mut ctx) => ctx.set_options(options),
             _ => {},
         }
     }
@@ -293,6 +306,7 @@ impl<'a> NAOptionHandler for DemuxerObject<'a> {
         match *self {
             DemuxerObject::Normal(ref dmx) => dmx.query_option_value(name),
             DemuxerObject::Raw(ref dmx, _, _) => dmx.query_option_value(name),
+            DemuxerObject::ImageSequence(ref ctx) => ctx.query_option_value(name),
             _ => None,
         }
     }
diff --git a/src/imgseq.rs b/src/imgseq.rs
new file mode 100644 (file)
index 0000000..dad9c81
--- /dev/null
@@ -0,0 +1,330 @@
+use nihav_core::frame::*;
+use nihav_core::demuxers::*;
+use std::fs::File;
+use std::io::BufReader;
+use std::io::Read;
+
+struct TemplateName {
+    prefix:         String,
+    pad_size:       usize,
+    suffix:         String,
+    single:         bool,
+}
+
+trait Deescape {
+    fn deescape(&mut self);
+}
+
+impl Deescape for String {
+    fn deescape(&mut self) {
+        while let Some(idx) = self.find("%%") {
+            self.remove(idx + 1);
+        }
+    }
+}
+
+impl TemplateName {
+    fn new(name: &str) -> Self {
+        let mut off = 0;
+        let mut tmpl_start = 0;
+        let mut tmpl_end = 0;
+        let mut pad_size = 0;
+        'parse_loop:
+        while let Some(idx) = name[off..].find('%') {
+            let idx = idx + off;
+            if idx + 1 == name.len() {
+                break;
+            }
+            if name[idx + 1..].starts_with('%') { // escape, skip it
+                off += 1;
+            }
+            if name[idx + 1..].starts_with('0') {
+                if let Some(end_idx) = name[idx + 2..].find('d') {
+                    if let Ok(val) = name[idx + 2..][..end_idx].parse::<usize>() {
+                        if val <= 32 {
+                            tmpl_start = idx;
+                            pad_size = val;
+                            tmpl_end = idx + 2 + end_idx + 1;
+                        }
+                    }
+                    break 'parse_loop;
+                }
+            }
+            if name[idx + 1..].starts_with('d') {
+                tmpl_start = idx;
+                tmpl_end = idx + 2;
+                break;
+            }
+            off += idx;
+        }
+
+        if tmpl_end == 0 {
+            let mut prefix = name.to_owned();
+            prefix.deescape();
+            Self {
+                prefix,
+                pad_size:   0,
+                suffix:     String::new(),
+                single:     true,
+            }
+        } else {
+            let mut prefix = name[..tmpl_start].to_string();
+            prefix.deescape();
+            let mut suffix = name[tmpl_end..].to_string();
+            suffix.deescape();
+            Self {
+                prefix, suffix, pad_size,
+                single: false,
+            }
+        }
+    }
+    fn format<T: Sized+ToString>(&self, id: T) -> String {
+        let mut number = id.to_string();
+        while number.len() < self.pad_size {
+            number.insert(0, '0');
+        }
+        let mut fname = String::with_capacity(self.prefix.len() + number.len() + self.suffix.len());
+        fname.push_str(&self.prefix);
+        fname.push_str(&number);
+        fname.push_str(&self.suffix);
+        fname
+    }
+}
+
+pub struct ImgSeqDemuxer {
+    pub stream:     NAStreamRef,
+    pub sm:         StreamManager,
+    cur_frame:      u64,
+    template:       TemplateName,
+    pgmyuv:         bool,
+}
+
+impl ImgSeqDemuxer {
+    fn new(stream: NAStreamRef, cur_frame: u64, template: TemplateName, pgmyuv: bool) -> Self {
+        let mut sm = StreamManager::new();
+        sm.add_stream_ref(stream.clone());
+        Self {
+            stream, sm, cur_frame, template, pgmyuv,
+        }
+    }
+    pub fn seek(&mut self, time: NATimePoint) -> DemuxerResult<()> {
+        self.cur_frame = match time {
+                NATimePoint::None => return Ok(()),
+                NATimePoint::Milliseconds(ms) => NATimeInfo::time_to_ts(ms, 1000, self.stream.tb_num, self.stream.tb_den),
+                NATimePoint::PTS(pts) => pts,
+            };
+        Ok(())
+    }
+    pub fn get_frame(&mut self) -> DemuxerResult<NAPacket> {
+        if self.cur_frame > 0 && self.template.single {
+            return Err(DemuxerError::EOF);
+        }
+        let fname = self.template.format(self.cur_frame);
+        if let Ok(file) = File::open(fname.as_str()) {
+            let mut file = BufReader::new(file);
+            let vinfo = read_pnm_header(&mut file, self.pgmyuv)?;
+            let pkt_size = if vinfo.format.model.is_yuv() && vinfo.format.components == 3 {
+                    vinfo.width * (vinfo.height * 3 / 2) * if vinfo.format.get_max_depth() > 8 { 2 } else { 1 }
+                } else {
+                    vinfo.width * vinfo.height * usize::from(vinfo.format.components)
+                };
+            let mut buf = vec![0; pkt_size];
+            file.read_exact(&mut buf).map_err(|_| DemuxerError::IOError)?;
+            let ts = NATimeInfo::new(Some(self.cur_frame), None, None, self.stream.tb_num, self.stream.tb_den);
+            let pkt = NAPacket::new(self.stream.clone(), ts, true, buf);
+            self.cur_frame += 1;
+            Ok(pkt)
+        } else {
+            Err(DemuxerError::EOF)
+        }
+    }
+}
+
+impl NAOptionHandler for ImgSeqDemuxer {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) {}
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub struct ImgSeqDemuxerCreator<'a> {
+    name:       &'a str,
+    start:      Option<u64>,
+    pgmyuv:     bool,
+    tb_num:     u32,
+    tb_den:     u32,
+}
+
+impl<'a> ImgSeqDemuxerCreator<'a> {
+    pub fn new(name: &'a str) -> Self {
+        Self {
+            name,
+            start:      None,
+            pgmyuv:     false,
+            tb_num:     1,
+            tb_den:     25,
+        }
+    }
+    pub fn open(&mut self) -> DemuxerResult<ImgSeqDemuxer> {
+        let template = TemplateName::new(self.name);
+
+        let fname = template.format(self.start.unwrap_or(0));
+
+        if let Ok(file) = File::open(fname.as_str()) {
+            let mut file = BufReader::new(file);
+            let vinfo = read_pnm_header(&mut file, self.pgmyuv)?;
+            let cinfo = NACodecInfo::new(if vinfo.format.model.is_yuv() { "rawvideo" } else { "rawvideo-ms" }, NACodecTypeInfo::Video(vinfo), None);
+            let stream = NAStream::new(StreamType::Video, 0, cinfo, self.tb_num, self.tb_den, 0).into_ref();
+            return Ok(ImgSeqDemuxer::new(stream, self.start.unwrap_or(0), template, self.pgmyuv));
+        }
+
+        // if start is not given, try also starting from one
+        if self.start.is_none() {
+            let new_start = 1;
+            let fname = template.format(new_start);
+            if let Ok(file) = File::open(fname.as_str()) {
+                let mut file = BufReader::new(file);
+                let vinfo = read_pnm_header(&mut file, self.pgmyuv)?;
+                let cinfo = NACodecInfo::new(if vinfo.format.model.is_yuv() { "rawvideo" } else { "rawvideo-ms" }, NACodecTypeInfo::Video(vinfo), None);
+                let stream = NAStream::new(StreamType::Video, 0, cinfo, self.tb_num, self.tb_den, 0).into_ref();
+                return Ok(ImgSeqDemuxer::new(stream, new_start, template, self.pgmyuv));
+            }
+        }
+
+        Err(DemuxerError::NoSuchInput)
+    }
+}
+
+const IMGSEQ_OPTIONS: &[NAOptionDefinition] = &[
+    NAOptionDefinition {
+        name: "start", description: "start frame number",
+        opt_type: NAOptionDefinitionType::Int(Some(0), None) },
+    NAOptionDefinition {
+        name: "pgmyuv", description: "Input is in PGMYUV format",
+        opt_type: NAOptionDefinitionType::Bool },
+    NAOptionDefinition {
+        name: "tb_num", description: "timebase numerator",
+        opt_type: NAOptionDefinitionType::Int(Some(1), Some(1000000)) },
+    NAOptionDefinition {
+        name: "tb_den", description: "timebase denominator",
+        opt_type: NAOptionDefinitionType::Int(Some(1), Some(1000000)) },
+];
+
+impl<'a> NAOptionHandler for ImgSeqDemuxerCreator<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { IMGSEQ_OPTIONS }
+    fn set_options(&mut self, options: &[NAOption]) {
+        for option in options.iter() {
+            for opt_def in IMGSEQ_OPTIONS.iter() {
+                if opt_def.check(option).is_ok() {
+                    match (option.name, &option.value) {
+                        ("start", NAValue::Int(intval)) => {
+                            self.start = Some(*intval as u64);
+                        },
+                        ("pgmyuv", NAValue::Bool(bval)) => {
+                            self.pgmyuv = *bval;
+                        },
+                        ("tb_num", NAValue::Int(intval)) => {
+                            self.tb_num = *intval as u32;
+                        },
+                        ("tb_den", NAValue::Int(intval)) => {
+                            self.tb_den = *intval as u32;
+                        },
+                        _ => {},
+                    }
+                }
+            }
+        }
+    }
+    fn query_option_value(&self, name: &str) -> Option<NAValue> {
+        match name {
+            "start" => Some(NAValue::Int(self.start.unwrap_or(0) as i64)),
+            "pgmyuv" => Some(NAValue::Bool(self.pgmyuv)),
+            "tb_num" => Some(NAValue::Int(self.tb_num as i64)),
+            "tb_den" => Some(NAValue::Int(self.tb_den as i64)),
+            _ => None,
+        }
+    }
+}
+
+fn read_pnm_header(file: &mut BufReader<File>, pgmyuv: bool) -> DemuxerResult<NAVideoInfo> {
+    let mut fr = FileReader::new_read(file);
+    let mut br = ByteReader::new(&mut fr);
+
+    let mut magic = [0; 2];
+    br.read_buf(&mut magic)?;
+    if magic[0] != b'P' { return Err(DemuxerError::InvalidData); }
+    match magic[1] {
+        b'4' | // PBM, PBM ASCII
+        b'1' => return Err(DemuxerError::NotImplemented),
+        b'5' => { // PGM
+        },
+        b'2' => return Err(DemuxerError::NotImplemented), // PGM ASCII
+        b'6' => { // PPM
+        },
+        b'3' => return Err(DemuxerError::NotImplemented), // PPM ASCII
+        _ => return Err(DemuxerError::InvalidData),
+    };
+    if br.read_byte()? != b'\n' { return Err(DemuxerError::InvalidData); }
+    let w = read_number(&mut br)?;
+    let h = read_number(&mut br)?;
+    let maxval = if matches!(magic[1], b'4' | b'1') { 1 } else { read_number(&mut br)? };
+    if maxval > 65535 || (maxval & (maxval + 1)) != 0 { return Err(DemuxerError::InvalidData); }
+    let bits = maxval.count_ones() as u8;
+
+    let mut vinfo = NAVideoInfo::new(w, h, false, RGB24_FORMAT);
+    match magic[1] {
+        b'5' | b'2' if !pgmyuv => {
+            vinfo.format = NAPixelFormaton {
+                    model: ColorModel::YUV(YUVSubmodel::YUVJ),
+                    components: 1,
+                    comp_info: [Some(NAPixelChromaton{h_ss: 0, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 0, next_elem: 1}), None, None, None, None],
+                    elem_size: 1,
+                    be: true,
+                    alpha: false,
+                    palette: false,
+                };
+            vinfo.bits = bits;
+        },
+        b'5' | b'2' => {
+            if ((w & 1) != 0) || ((h % 3) != 0) { return Err(DemuxerError::InvalidData); }
+            vinfo.format = YUV420_FORMAT;
+            vinfo.height = h * 2 / 3;
+            vinfo.bits = bits * 3 / 2;
+        },
+        b'6' | b'3' => {
+            vinfo.format = RGB24_FORMAT;
+            vinfo.bits = bits;
+        },
+        _ => unreachable!(),
+    };
+    if bits != 8 {
+        for chr in vinfo.format.comp_info.iter_mut().flatten() {
+            chr.depth = bits;
+            if bits > 8 {
+                chr.next_elem = 2;
+            }
+        }
+        if bits > 8 {
+            vinfo.format.elem_size <<= 1;
+        }
+    }
+    
+    Ok(vinfo)
+}
+
+fn read_number(br: &mut ByteReader) -> DemuxerResult<usize> {
+    let mut val = 0;
+    loop {
+        let c = br.read_byte()?;
+        match c {
+            b'0'..=b'9' => {
+                if val > 1048576 {
+                    return Err(DemuxerError::InvalidData);
+                }
+                val = val * 10 + usize::from(c - b'0');
+            },
+            b' ' | b'\n' => break,
+            _ => return Err(DemuxerError::InvalidData),
+        };
+    }
+    Ok(val)
+}
index e033dfd1040cd3d7b5eb965b351e1c8d926ce309..97be1e2ea36367b6b833a1b61f5134cc5a5619da 100644 (file)
@@ -23,6 +23,8 @@ use crate::demux::*;
 mod null;
 mod acvt;
 use crate::acvt::*;
+mod imgseq;
+use crate::imgseq::*;
 
 const SND_NO_FORMAT: NASoniton = NASoniton { bits: 0, be: false, packed: false, planar: false, float: false, signed: false };
 
@@ -1068,6 +1070,20 @@ fn main() {
         return;
     }
 
+    if let Some("imgseq") = transcoder.input_fmt.as_deref() {
+        let mut isdc = ImgSeqDemuxerCreator::new(transcoder.input_name.as_str());
+        parse_and_apply_options!(isdc, &transcoder.demux_opts, "input");
+        let isd = if let Ok(ctx) = isdc.open() {
+                ctx
+            } else {
+                println!("failed to create image sequence demuxer!");
+                return;
+            };
+        let mut dmx = DemuxerObject::create_imgseq(isd);
+        transcode(transcoder, full_reg, &mut dmx);
+        return;
+    }
+
     let res = File::open(transcoder.input_name.as_str());
     if res.is_err() {
         println!("error opening input");
@@ -1097,10 +1113,14 @@ fn main() {
         println!("cannot find demuxer for '{}'", transcoder.input_name.as_str());
         return;
     }
+    parse_and_apply_options!(dmx, &transcoder.demux_opts, "input");
+    transcode(transcoder, full_reg, &mut dmx);
+}
+
+fn transcode(mut transcoder: Transcoder, full_reg: FullRegister, dmx: &mut DemuxerObject) {
     let duration = dmx.get_duration();
     let duration_string = if duration != 0 { format_time(duration) } else { String::new() };
 
-    parse_and_apply_options!(dmx, &transcoder.demux_opts, "input");
     for i in 0..dmx.get_num_streams() {
         let s = dmx.get_stream(i).unwrap();
         let info = s.get_info();