--- /dev/null
+//! Common interface and functionality for demuxing data.
+//!
+//! Currently there are three types of demuxing possible:
+//! * ordinary demuxer that returns packets from one or more embedded streams;
+//! * raw streams demuxer (e.g. MPEG-2 TS) that returns chunks of raw data and requires a packetiser to form them into proper packets;
+//! * raw stream without any additional headers (e.g. MP3) that also requires packetising.
+//!
+//! This module provides functionality to automatically detect input format and select appropriate
+//! implementation that will still behave like an ordinary `Demuxer` object that returns full packets.
+
+use std::io::SeekFrom;
+use nihav_core::codecs::*;
+use nihav_core::demuxers::*;
+use nihav_core::muxers::RegisteredMuxers;
+use nihav_registry::detect;
+use nihav_core::io::byteio::ByteIO;
+use nihav_core::sbbox::*;
+#[cfg(feature="imgseq_dec")]
+use crate::imgseqdec::ImgSeqDemuxer;
+
+/// A helper structure to pass references to all registered (de)muxers, encoders, decoders and such.
+///
+/// The most convenient way to populate it is to invoke `nihav_register_all_{demuxers,encoders,ets}` from `nihav_allstuff` crate on corresponding structure members.
+#[derive(Default)]
+pub struct FullRegister {
+ /// Registered demuxers
+ pub dmx_reg: RegisteredDemuxers,
+ /// Registered raw stream demuxers
+ pub rdmx_reg: RegisteredRawDemuxers,
+ /// Registered packetisers
+ pub pkt_reg: RegisteredPacketisers,
+ /// Registered decoders
+ pub dec_reg: RegisteredDecoders,
+ /// Registered muxers
+ pub mux_reg: RegisteredMuxers,
+ /// Registered encoders
+ pub enc_reg: RegisteredEncoders,
+}
+
+impl FullRegister {
+ /// Creates a new empty instance of `FullRegister`.
+ pub fn new() -> Self { Self::default() }
+}
+
+/// Auxiliary structure to deal with single input raw stream and packetising it. Not for the direct use.
+pub struct RawStreamCtx<'a> {
+ stream: NAStreamRef,
+ sm: StreamManager,
+ pkt: Box<dyn NAPacketiser + Send>,
+ br: &'a mut dyn ByteIO,
+ pts: u64,
+ seek: SeekIndex,
+}
+
+impl<'a> RawStreamCtx<'a> {
+ fn new(stream: NAStreamRef, packetiser: Box<dyn NAPacketiser + Send>, br: &'a mut dyn ByteIO) -> Self {
+ let mut sm = StreamManager::new();
+ sm.add_stream_ref(stream.clone());
+ let mut seek = SeekIndex::new();
+ seek.add_stream(0);
+ Self { stream, pkt: packetiser, br, pts: 0, sm, seek }
+ }
+ fn account_for_packet(&mut self, packet: &mut NAPacket) {
+ let pos = self.br.tell() - (self.pkt.bytes_left() as u64);
+ if packet.get_pts().is_none() && packet.get_duration().is_some() {
+ packet.ts.pts = Some(self.pts);
+ }
+ if packet.is_keyframe() {
+ let pts = packet.get_pts().unwrap_or(self.pts);
+ let time = NATimeInfo::rescale_ts(pts, self.stream.tb_num, self.stream.tb_den, 1, 1000);
+ let in_range = if let Some(last) = self.seek.seek_info[0].entries.last() {
+ last.pts >= pts
+ } else {
+ false
+ };
+ if !in_range {
+ self.seek.add_entry(0, SeekEntry { time, pts, pos });
+ }
+ }
+ self.pts += packet.get_duration().unwrap_or(0);
+ }
+ fn get_frame(&mut self) -> DemuxerResult<NAPacket> {
+ let mut buf = [0; 1048576];
+ loop {
+ match self.pkt.get_packet(self.stream.clone()) {
+ Ok(Some(mut packet)) => {
+ self.account_for_packet(&mut packet);
+ return Ok(packet);
+ },
+ Ok(None) => {},
+ Err(DecoderError::ShortData) => {},
+ _ => return Err(DemuxerError::InvalidData),
+ };
+ match self.br.read_buf_some(&mut buf) {
+ Ok(size) => {
+ self.pkt.add_data(&buf[..size]);
+ },
+ Err(_) => {
+ match self.pkt.get_packet(self.stream.clone()) {
+ Ok(Some(mut packet)) => {
+ self.account_for_packet(&mut packet);
+ return Ok(packet);
+ },
+ Ok(None) | Err(DecoderError::ShortData) => return Err(DemuxerError::EOF),
+ _ => return Err(DemuxerError::InvalidData),
+ };
+ },
+ };
+ }
+ }
+}
+
+/// Auxiliary structure to maintain the state of raw streams demuxer. Not for the direct use.
+pub struct RawDemuxerState<'a> {
+ dmx: SBBox<ReaderBox, RawDemuxer<'a>>,
+ pkts: Vec<Option<Box<dyn NAPacketiser + Send>>>,
+ eof: bool,
+}
+
+/// Alias for input source
+pub type ReaderBox = Box<dyn ByteIO>;
+
+/// Wrapper that provides common interface a la `Demuxer` for different types of input.
+pub enum DemuxerObject<'a> {
+ /// Nothing created yet
+ None,
+ /// Ordinary demuxer
+ Normal(SBBox<ReaderBox, Demuxer<'a>>),
+ /// Raw stream demuxer with individual stream contexts
+ Raw(RawDemuxerState<'a>),
+ /// Elementary stream
+ RawStream(SBBox<ReaderBox, RawStreamCtx<'a>>),
+ #[cfg(feature="imgseq_dec")]
+ /// Image sequence
+ ImageSequence(ImgSeqDemuxer),
+}
+
+impl<'a> DemuxerObject<'a> {
+ fn new_demuxer(mut br: ReaderBox, reg: &FullRegister, name: &str, dmx_name: &str, opts: &[NAOption], verbose: bool, forced: bool) -> Result<DemuxerObject<'a>, ReaderBox> {
+ if let Some(dmx_fact) = reg.dmx_reg.find_demuxer(dmx_name) {
+ if verbose {
+ println!("{} demuxer {} on {}", if forced { "forcing" } else { "trying" }, dmx_name, name);
+ }
+ br.seek(SeekFrom::Start(0)).unwrap();
+ SelfBorrow::try_new(br, |br_| {
+ unsafe {
+ create_demuxer_with_options(dmx_fact, (*br_).as_mut(), opts).ok()
+ }
+ }).map(DemuxerObject::Normal)
+ } else {
+ Err(br)
+ }
+ }
+ fn new_raw_demuxer(mut br: ReaderBox, reg: &FullRegister, name: &str, dmx_name: &str, opts: &[NAOption], verbose: bool, forced: bool) -> Result<DemuxerObject<'a>, ReaderBox> {
+ if let Some(rdmx_fact) = reg.rdmx_reg.find_demuxer(dmx_name) {
+ if verbose {
+ println!("{} raw demuxer {} on {}", if forced { "forcing" } else { "trying" }, dmx_name, name);
+ }
+ br.seek(SeekFrom::Start(0)).unwrap();
+ let dmx = SelfBorrow::try_new(br, |br_| {
+ unsafe {
+ create_raw_demuxer_with_options(rdmx_fact, (*br_).as_mut(), opts).ok()
+ }
+ })?;
+ let mut pkts = Vec::new();
+ for stream in dmx.get_object().get_streams() {
+ if let Some(pcreate) = reg.pkt_reg.find_packetiser(stream.get_info().get_name()) {
+ let mut packetiser = (pcreate)();
+ packetiser.attach_stream(stream);
+ pkts.push(Some(packetiser));
+ } else {
+ pkts.push(None);
+ }
+ }
+ Ok(DemuxerObject::Raw(RawDemuxerState{ dmx, pkts, eof: false }))
+ } else {
+ Err(br)
+ }
+ }
+
+ /// Attempts to create a new instance of `DemuxerObject`.
+ ///
+ /// Input parameters:
+ /// * `br` --- input I/O context
+ /// * `reg` --- register with at least some of the demuxers or raw stream demuxers with packetisers being registered
+ /// * `name` --- input file name (used for format detection)
+ /// * `force_dmx` --- optionally provided demuxer name (set to `None` to have autodetection instead)
+ /// * `is_raw` --- skips attempting to create a demuxer and treats input as an elementary stream
+ /// * `opts` --- demuxer options
+ /// * `verbose` -- enables printing messages e.g. for debugging purposes
+ pub fn create(mut br: ReaderBox, reg: &FullRegister, name: &str, force_dmx: Option<&str>, is_raw: bool, opts: &[NAOption], verbose: bool) -> DemuxerObject<'a> {
+ if !is_raw {
+ if let Some(dmx_name) = force_dmx {
+ match Self::new_demuxer(br, reg, name, dmx_name, opts, verbose, true) {
+ Ok(dmx) => return dmx,
+ Err(nbr) => br = nbr,
+ };
+ if let Ok(dmx) = Self::new_raw_demuxer(br, reg, name, dmx_name, opts, verbose, true) {
+ return dmx;
+ } else {
+ return DemuxerObject::None;
+ }
+ }
+ let res = detect::detect_format(name, &mut *br);
+ let (dmx_name, _) = res.unwrap_or(("", detect::DetectionScore::No));
+ if !dmx_name.is_empty() {
+ match Self::new_demuxer(br, reg, name, dmx_name, opts, verbose, false) {
+ Ok(dmx) => return dmx,
+ Err(nbr) => br = nbr,
+ }
+ }
+ if !dmx_name.is_empty() {
+ match Self::new_raw_demuxer(br, reg, name, dmx_name, opts, verbose, false) {
+ Ok(dmx) => return dmx,
+ Err(nbr) => br = nbr,
+ }
+ }
+ for rdmx in reg.rdmx_reg.iter() {
+ if rdmx.check_format(&mut *br) {
+ if verbose {
+ println!("detected {} as {}", name, rdmx.get_name());
+ }
+ match Self::new_raw_demuxer(br, reg, name, rdmx.get_name(), opts, false, false) {
+ Ok(dmx) => return dmx,
+ Err(nbr) => br = nbr,
+ }
+ }
+ }
+ }
+ br.seek(SeekFrom::Start(0)).unwrap();
+ let mut buf = vec![0; 1048576];
+ let size = br.read_buf_some(&mut buf).unwrap();
+ br.seek(SeekFrom::Start(0)).unwrap();
+ let mut pname = "";
+
+ for pinfo in reg.pkt_reg.iter() {
+ let mut packetiser = (pinfo.get_packetiser)();
+ packetiser.add_data(&buf[..size]);
+ if packetiser.parse_stream(0).is_ok() {
+ pname = pinfo.name;
+ break;
+ }
+ }
+ if !pname.is_empty() {
+ if verbose {
+ println!("found raw stream of type {} for {}", pname, name);
+ }
+ let pcreate = reg.pkt_reg.find_packetiser(pname).unwrap();
+ let rctx = SelfBorrow::new(br, |br_| {
+ unsafe {
+ let mut packetiser = (pcreate)();
+ packetiser.add_data(&buf[..size]);
+ let stream = packetiser.parse_stream(0).unwrap();
+ packetiser.reset();
+ RawStreamCtx::new(stream, packetiser, (*br_).as_mut())
+ }
+ });
+ DemuxerObject::RawStream(rctx)
+ } else {
+ DemuxerObject::None
+ }
+ }
+ /// Wraps image sequence demuxer.
+ #[cfg(feature="imgseq_dec")]
+ pub fn create_imgseq(isd: ImgSeqDemuxer) -> Self {
+ DemuxerObject::ImageSequence(isd)
+ }
+ /// Checks if there is no actual demuxer created.
+ pub fn is_none(&self) -> bool {
+ matches!(*self, DemuxerObject::None)
+ }
+ /// Returns total file duration (or 0 if unknown).
+ pub fn get_duration(&self) -> u64 {
+ match *self {
+ DemuxerObject::Normal(ref dmx) => dmx.get_object().get_duration(),
+ DemuxerObject::Raw(ref dmxs) => dmxs.dmx.get_object().get_duration(),
+ DemuxerObject::RawStream(ref ctx) => {
+ let stream = &ctx.get_object().stream;
+ NATimeInfo::rescale_ts(stream.duration, stream.tb_num, stream.tb_den, 1, 1000)
+ },
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(_) => 0,
+ _ => 0,
+ }
+ }
+ /// Returns number of present streams.
+ pub fn get_num_streams(&self) -> usize {
+ match *self {
+ DemuxerObject::None => 0,
+ DemuxerObject::Normal(ref dmx) => dmx.get_object().get_num_streams(),
+ DemuxerObject::Raw(ref dmxs) => dmxs.dmx.get_object().get_num_streams(),
+ DemuxerObject::RawStream(_) => 1,
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(_) => 1,
+ }
+ }
+ /// Returns stream with the requested index.
+ pub fn get_stream(&self, idx: usize) -> Option<NAStreamRef> {
+ match *self {
+ DemuxerObject::Normal(ref dmx) => dmx.get_object().get_stream(idx),
+ DemuxerObject::Raw(ref dmxs) => dmxs.dmx.get_object().get_stream(idx),
+ DemuxerObject::RawStream(ref ctx) if idx == 0 => Some(ctx.get_object().stream.clone()),
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(ref ctx) if idx == 0 => Some(ctx.stream.clone()),
+ _ => None,
+ }
+ }
+ /// Returns stream manager associated with the demuxer.
+ pub fn get_stream_manager(&self) -> &StreamManager {
+ match *self {
+ DemuxerObject::Normal(ref dmx) => dmx.get_object().get_stream_manager(),
+ DemuxerObject::Raw(ref dmxs) => dmxs.dmx.get_object().get_stream_manager(),
+ DemuxerObject::RawStream(ref ctx) => &ctx.get_object().sm,
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(ref ctx) => &ctx.sm,
+ _ => unreachable!(),
+ }
+ }
+ /// Demuxes a packet.
+ pub fn get_frame(&mut self) -> DemuxerResult<NAPacket> {
+ match *self {
+ DemuxerObject::Normal(ref mut dmx) => dmx.get_object_mut().get_frame(),
+ DemuxerObject::Raw(ref mut dmxs) => {
+ loop {
+ let mut has_some = false;
+ for (stream, p) in dmxs.dmx.get_object().get_streams().zip(dmxs.pkts.iter_mut()) {
+ if let Some(ref mut pkts) = p {
+ match pkts.get_packet(stream.clone()) {
+ Ok(Some(pkt)) => return Ok(pkt),
+ Ok(None) | Err(DecoderError::ShortData) => {
+ if dmxs.eof {
+ *p = None;
+ }
+ },
+ Err(err) => {
+ println!("packetisation error {:?}", err);
+ return Err(DemuxerError::InvalidData);
+ }
+ };
+ has_some |= p.is_some();
+ }
+ }
+ if !has_some {
+ return Err(DemuxerError::EOF);
+ }
+ if let Ok(data) = dmxs.dmx.get_object_mut().get_data() {
+ let id = data.get_stream().get_id();
+ for (i, stream) in dmxs.dmx.get_object().get_streams().enumerate() {
+ if stream.get_id() == id {
+ if let Some(ref mut pkts) = dmxs.pkts[i] {
+ pkts.add_data(&data.get_buffer());
+ }
+ break;
+ }
+ }
+ } else {
+ dmxs.eof = true;
+ }
+ }
+ },
+ DemuxerObject::RawStream(ref mut ctx) => ctx.get_object_mut().get_frame(),
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(ref mut ctx) => ctx.get_frame(),
+ _ => unreachable!(),
+ }
+ }
+ /// Seeks to the specified time.
+ pub fn seek(&mut self, seek_time: NATimePoint) -> DemuxerResult<()> {
+ match *self {
+ DemuxerObject::Normal(ref mut dmx) => dmx.get_object_mut().seek(seek_time),
+ DemuxerObject::Raw(ref mut dmxs) => dmxs.dmx.get_object_mut().seek(seek_time),
+ DemuxerObject::RawStream(ref mut ctxobj) => {
+ let ctx = ctxobj.get_object_mut();
+ if seek_time == NATimePoint::None {
+ return Err(DemuxerError::SeekError);
+ }
+ if let Some(last) = ctx.seek.seek_info[0].entries.last() {
+ let in_index = match seek_time {
+ NATimePoint::None => unreachable!(),
+ NATimePoint::PTS(pts) => last.pts >= pts,
+ NATimePoint::Milliseconds(ms) => last.time >= ms,
+ };
+ if in_index {
+ if let Some(result) = ctx.seek.find_pos(seek_time) {
+ ctx.br.seek(SeekFrom::Start(result.pos))?;
+ ctx.pts = result.pts;
+ ctx.pkt.reset();
+ return Ok(());
+ }
+ }
+ }
+ if let Some(last) = ctx.seek.seek_info[0].entries.last() {
+ ctx.br.seek(SeekFrom::Start(last.pos))?;
+ ctx.pts = last.pts;
+ ctx.pkt.reset();
+ }
+ let mut key_pts = 0;
+ while let Ok(pkt) = ctx.get_frame() {
+ if !pkt.ts.less_than(seek_time) && !pkt.ts.equal(seek_time) {
+ break;
+ }
+ if pkt.is_keyframe() {
+ key_pts = pkt.get_pts().unwrap_or(0);
+ }
+ }
+ let result = ctx.seek.find_pos(NATimePoint::PTS(key_pts)).unwrap();
+ ctx.br.seek(SeekFrom::Start(result.pos))?;
+ ctx.pts = result.pts;
+ ctx.pkt.reset();
+ Ok(())
+ },
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(ref mut ctx) => ctx.seek(seek_time),
+ _ => Err(DemuxerError::NotImplemented),
+ }
+ }
+}
+
+
+impl<'a> NAOptionHandler for DemuxerObject<'a> {
+ fn get_supported_options(&self) -> &[NAOptionDefinition] {
+ match *self {
+ DemuxerObject::Normal(ref dmx) => dmx.get_object().get_supported_options(),
+ DemuxerObject::Raw(ref dmxs) => dmxs.dmx.get_object().get_supported_options(),
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(ref ctx) => ctx.get_supported_options(),
+ _ => &[],
+ }
+ }
+ fn set_options(&mut self, options: &[NAOption]) {
+ match *self {
+ DemuxerObject::Normal(ref mut dmx) => dmx.get_object_mut().set_options(options),
+ DemuxerObject::Raw(ref mut dmxs) => dmxs.dmx.get_object_mut().set_options(options),
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(ref mut ctx) => ctx.set_options(options),
+ _ => {},
+ }
+ }
+ fn query_option_value(&self, name: &str) -> Option<NAValue> {
+ match *self {
+ DemuxerObject::Normal(ref dmx) => dmx.get_object().query_option_value(name),
+ DemuxerObject::Raw(ref dmxs) => dmxs.dmx.get_object().query_option_value(name),
+ #[cfg(feature="imgseq_dec")]
+ DemuxerObject::ImageSequence(ref ctx) => ctx.query_option_value(name),
+ _ => None,
+ }
+ }
+}
+
+/// Checks input for various metadata tags.
+///
+/// Music files often have various metadata (e.g. title, artist, genre etc) associated with them but not always being a part of the file/stream format but rather a generic extension prepended or appended to the elementary stream.
+/// MP3 with ID3 tags is the most common example of such files.
+///
+/// This functions allows detecting several of such extensions reporting whether it expects stream to be raw, start position and end position (if necessary).
+/// For example, MP3 with first 4200 bytes being an ID3 tag will result in `(true, 4200, None)` value while FLAC file with APE tags at the end will return something like `(false, 0, Some(424242))`.
+pub fn detect_tags(br: &mut dyn ByteIO, verbose: bool) -> (bool, u64, Option<u64>) {
+ let mut is_raw = false;
+ let mut start = 0;
+ let mut end = None;
+
+ // check for ID3v{2-4}
+ let mut buf = [0; 5];
+ loop {
+ if br.peek_buf(&mut buf).is_err() {
+ break;
+ }
+ if &buf[0..3] == b"ID3" && buf[3] > 0 && buf[3] < 5 && buf[4] == 0 { //ID3 tag found, must be a raw stream
+ br.read_skip(6).unwrap();
+ let mut size = 0;
+ for _ in 0..4 {
+ let b = br.read_byte().unwrap();
+ if (b & 0x80) != 0 {
+ if verbose {
+ println!("Invalid ID3 size");
+ }
+ break;
+ }
+ size = (size << 7) | u64::from(b);
+ }
+ start += size + 10;
+ br.read_skip(size as usize).unwrap();
+ while let Ok(0) = br.read_byte() {
+ start += 1;
+ }
+ br.seek(SeekFrom::Start(start)).unwrap();
+ is_raw = true;
+ } else {
+ break;
+ }
+ }
+ // check for ID3v1
+ br.seek(SeekFrom::End(-128)).unwrap();
+ let off = br.tell();
+ br.peek_buf(&mut buf[..3]).unwrap();
+ if &buf[0..3] == b"TAG" {
+ end = Some(off);
+ // check for Lyrics v2
+ let mut sig = [0; 9];
+ br.seek(SeekFrom::End(-128 - 9)).unwrap();
+ br.peek_buf(&mut sig).unwrap();
+ if &sig == b"LYRICS200" {
+ br.seek(SeekFrom::Current(-6)).unwrap();
+ let mut sizestr = [0; 6];
+ br.peek_buf(&mut sizestr).unwrap();
+ if let Ok(sstr) = std::str::from_utf8(&sizestr) {
+ if let Ok(size) = sstr.parse::<u64>() {
+ end = Some(br.tell() - size);
+ }
+ }
+ }
+ }
+ // check for APETAG
+ let mut buf = [0; 8];
+ if let Some(off) = end {
+ br.seek(SeekFrom::Start(off - 32)).unwrap();
+ } else {
+ br.seek(SeekFrom::End(-32)).unwrap();
+ }
+ let off = br.tell();
+ br.read_buf(&mut buf).unwrap();
+ if &buf == b"APETAGEX" {
+ let ver = br.read_u32le().unwrap();
+ let size = u64::from(br.read_u32le().unwrap());
+ let _items = br.read_u32le().unwrap();
+ let flags = br.read_u32le().unwrap();
+ if ver == 1000 || (flags & 0x80000000) == 0 {
+ end = Some(off - size + 32);
+ } else {
+ end = Some(off - size);
+ }
+ }
+ // check for MusicMatch tag
+ let ret = if let Some(endpos) = end {
+ br.seek(SeekFrom::Start(endpos - 0x30))
+ } else {
+ br.seek(SeekFrom::End(-0x30))
+ };
+ if ret.is_ok() && br.tell() > (0x2000 - 0x30) {
+ let mut buf = [0; 19];
+ br.peek_buf(&mut buf).unwrap();
+ if &buf == b"Brava Software Inc." {
+ br.seek(SeekFrom::Current(-20)).unwrap();
+ let mut mm_start = u64::from(br.read_u32le().unwrap());
+ if mm_start > 4 && mm_start + 0x2000 <= br.tell() {
+ let diff = (br.tell() - mm_start) & 3;
+ if diff != 0 {
+ mm_start -= 4 - diff;
+ }
+ end = Some(mm_start);
+ }
+ }
+ }
+
+ (is_raw, start, end)
+}
--- /dev/null
+//! Image sequence demuxer
+//!
+//! This module offers a functionality of representing an image sequence with an object having `Demuxer`-like interface.
+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
+ }
+}
+
+/// Image sequence demuxer
+///
+/// Use [`ImgSeqDemuxerCreator`] to create an instance of it.
+///
+/// [`ImgSeqDemuxerCreator`]: ./struct.ImgSeqDemuxerCreator.html
+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,
+ }
+ }
+ /// Seeks to the requested time if possible.
+ pub fn seek(&mut self, time: NATimePoint) -> DemuxerResult<()> {
+ self.cur_frame = match time {
+ NATimePoint::None => return Ok(()),
+ NATimePoint::Milliseconds(ms) => NATimeInfo::rescale_ts(ms, 1, 1000, self.stream.tb_num, self.stream.tb_den),
+ NATimePoint::PTS(pts) => pts,
+ };
+ Ok(())
+ }
+ /// Demuxes a packet.
+ 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 }
+}
+
+/// Helper object that creates image sequence demuxer
+///
+/// By default it will try to open images using the provided simplified `printf`-like template e.g. `file%d.ppm` will be expanded into `file0.ppm`, `file1.ppm`, `file2.ppm` ... and `img%03d.pnm` will be expanded into `img000.pnm`, `img001.pnm` etc.
+/// Please note that passing single image names like `picture.pgm` is fine, also if `img000.ppm` is not found but `img001.ppm` is then decoding will start from it. You can set start image in the sequence with `start` option.
+///
+/// Other useful options are:
+/// * `tb_num`/`tb_den` pair allows to set custom timebase for the sequence
+/// * `pgmyuv` -- tells the demuxer that input PGM images are not greyscale images but rather YUV 4:2:0 data with chroma planes next to each other and below luma plane data.
+pub struct ImgSeqDemuxerCreator<'a> {
+ name: &'a str,
+ start: Option<u64>,
+ pgmyuv: bool,
+ tb_num: u32,
+ tb_den: u32,
+}
+
+impl<'a> ImgSeqDemuxerCreator<'a> {
+ /// Creates a new instance of `ImgSeqDemuxerCreator`, allowing to set options before actual demuxer is created.
+ pub fn new(name: &'a str) -> Self {
+ Self {
+ name,
+ start: None,
+ pgmyuv: false,
+ tb_num: 1,
+ tb_den: 25,
+ }
+ }
+ /// Tries to create new image sequence demuxer.
+ ///
+ /// Note: for setting stream properties the first image in the sequence is checked.
+ 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("rawvideo", 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("rawvideo", 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 br = FileReader::new_read(file);
+
+ 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 dyn ByteIO) -> 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)
+}