]> git.nihav.org Git - nihav.git/commitdiff
mov: split code into smaller components
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 2 Apr 2026 16:18:03 +0000 (18:18 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 2 Apr 2026 16:18:03 +0000 (18:18 +0200)
nihav-commonfmt/src/demuxers/mov.rs [deleted file]
nihav-commonfmt/src/demuxers/mov/mod.rs [new file with mode: 0644]
nihav-commonfmt/src/demuxers/mov/pktread.rs [new file with mode: 0644]
nihav-commonfmt/src/demuxers/mov/track.rs [new file with mode: 0644]

diff --git a/nihav-commonfmt/src/demuxers/mov.rs b/nihav-commonfmt/src/demuxers/mov.rs
deleted file mode 100644 (file)
index eb16b45..0000000
+++ /dev/null
@@ -1,2324 +0,0 @@
-use nihav_core::demuxers::*;
-use nihav_registry::register::*;
-use nihav_core::compr::deflate::*;
-use nihav_codec_support::codecs::qt_pal::*;
-
-macro_rules! mktag {
-    ($a:expr, $b:expr, $c:expr, $d:expr) => ({
-        (($a as u32) << 24) | (($b as u32) << 16) | (($c as u32) << 8) | ($d as u32)
-    });
-    ($arr:expr) => ({
-        (($arr[0] as u32) << 24) | (($arr[1] as u32) << 16) | (($arr[2] as u32) << 8) | ($arr[3] as u32)
-    });
-}
-
-const QT_RGB555_FORMAT: NAPixelFormaton = NAPixelFormaton::make_rgb16_fmt(5, 5, 5, true, true);
-
-pub const ARGB_FORMAT: NAPixelFormaton = NAPixelFormaton {
-        model: ColorModel::RGB(RGBSubmodel::RGB), components: 4,
-        comp_info: [
-            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 1, next_elem: 4 }),
-            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 2, next_elem: 4 }),
-            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 3, next_elem: 4 }),
-            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 0, next_elem: 4 }),
-            None ],
-        elem_size: 4, be: false, alpha: true, palette: false };
-
-#[derive(Clone,Copy,Debug,PartialEq)]
-enum DemuxMode {
-    Normal,
-    MacBin,
-    ResFork,
-}
-
-trait Skip64 {
-    fn skip64(&mut self, size: u64) -> ByteIOResult<()>;
-}
-
-impl<T: ?Sized + ByteIO> Skip64 for T {
-    fn skip64(&mut self, size: u64) -> ByteIOResult<()> {
-        if (size as usize as u64) != size {
-            self.seek(SeekFrom::Current(size as i64))?;
-        } else {
-            self.read_skip(size as usize)?;
-        }
-        Ok(())
-    }
-}
-
-fn read_chunk_header(br: &mut dyn ByteIO) -> DemuxerResult<(u32, u64)> {
-    let size            = br.read_u32be()?;
-    let ctype           = br.read_u32be()?;
-    if size == 0 {
-        Ok((ctype, br.left() as u64))
-    } else if size == 1 {
-        let size64      = br.read_u64be()?;
-        validate!(size64 >= 16);
-        Ok((ctype, size64 - 16))
-    } else {
-        validate!(size >= 8);
-        Ok((ctype, (size as u64) - 8))
-    }
-}
-
-fn read_palette(br: &mut dyn ByteIO, size: u64, pal: &mut [u8; 1024]) -> DemuxerResult<u64> {
-    let _seed           = br.read_u32be()?;
-    let flags           = br.read_u16be()?;
-    let palsize         = (br.read_u16be()? as usize) + 1;
-    validate!(palsize <= 256);
-    validate!(flags == 0 || flags == 0x4000 || flags == 0x8000);
-    validate!((palsize as u64) * 8 + 8 <= size);
-    for i in 0..palsize {
-        let a           = br.read_u16be()?;
-        let r           = br.read_u16be()?;
-        let g           = br.read_u16be()?;
-        let b           = br.read_u16be()?;
-        pal[i * 4]     = (r >> 8) as u8;
-        pal[i * 4 + 1] = (g >> 8) as u8;
-        pal[i * 4 + 2] = (b >> 8) as u8;
-        if flags == 0x8000 {
-            pal[i * 4 + 3] = (a >> 8) as u8;
-        }
-    }
-    if flags == 0x4000 {
-        br.read_skip(8)?;
-    }
-    Ok(size)
-}
-
-struct RootChunkHandler {
-    ctype:  u32,
-    parse:  fn(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64>,
-}
-
-struct TrackChunkHandler {
-    ctype:  u32,
-    parse:  fn(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64>,
-}
-
-const IGNORED_CHUNKS: &[u32] = &[
-    mktag!(b"free"), mktag!(b"skip"), mktag!(b"udta"), mktag!(b"wide")
-];
-
-const ROOT_CHUNK_HANDLERS: &[RootChunkHandler] = &[
-    RootChunkHandler { ctype: mktag!(b"ftyp"), parse: read_ftyp },
-    RootChunkHandler { ctype: mktag!(b"mdat"), parse: read_mdat },
-    RootChunkHandler { ctype: mktag!(b"moov"), parse: read_moov },
-    RootChunkHandler { ctype: mktag!(b"moof"), parse: read_moof },
-    RootChunkHandler { ctype: mktag!(b"sidx"), parse: read_sidx },
-];
-
-fn print_cname(ctype: u32, size: u64, off: u64, depth: u8) {
-    for _ in 0..depth { print!("    "); }
-    let tag = [(ctype >> 24) as u8, (ctype >> 16) as u8, (ctype >> 8) as u8, ctype as u8];
-    let mut printable = true;
-    for ch in tag.iter() {
-        if !(0x20..=0x7F).contains(ch) {
-            printable = false;
-            break;
-        }
-    }
-    if printable {
-        print!(" '{}{}{}{}'", tag[0] as char, tag[1] as char, tag[2] as char, tag[3] as char);
-    } else {
-        print!(" {:08X}", ctype);
-    }
-    println!(" size {} @ {:X}", size, off);
-}
-
-macro_rules! read_chunk_list {
-    (root; $name: expr, $fname: ident, $handlers: ident) => {
-        fn $fname(&mut self, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<()> {
-            self.depth += 1;
-            validate!(self.depth < 32);
-            let list_end = self.src.tell() + size;
-            while self.src.tell() < list_end {
-                let ret = read_chunk_header(self.src);
-                if ret.is_err() { break; }
-                let (ctype, size) = ret.unwrap();
-                if self.print_chunks {
-                    print_cname(ctype, size, self.src.tell(), self.depth as u8);
-                }
-                if self.src.tell() + size > list_end {
-                    break;
-                }
-                if IGNORED_CHUNKS.contains(&ctype) {
-                    self.src.skip64(size)?;
-                    continue;
-                }
-                let handler = $handlers.iter().find(|x| x.ctype == ctype);
-                let read_size;
-                if let Some(ref handler) = handler {
-                    read_size = (handler.parse)(self, strmgr, size)?;
-                } else {
-                    println!("skipping unknown chunk {:08X} size {}", ctype, size);
-                    read_size = 0;
-                }
-                validate!(read_size <= size);
-                self.src.skip64(size - read_size)?;
-            }
-            self.depth -= 1;
-            validate!(self.src.tell() == list_end);
-            Ok(())
-        }
-    };
-    (track; $name: expr, $fname: ident, $handlers: ident) => {
-        fn $fname(&mut self, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<()> {
-            self.depth += 1;
-            validate!(self.depth < 32);
-            let list_end = br.tell() + size;
-            while br.tell() < list_end {
-                let ret = read_chunk_header(br);
-                if ret.is_err() { break; }
-                let (ctype, size) = ret.unwrap();
-                if self.print_chunks {
-                    print_cname(ctype, size, br.tell(), self.depth + 1);
-                }
-                if br.tell() + size > list_end {
-                    break;
-                }
-                if IGNORED_CHUNKS.contains(&ctype) {
-                    br.skip64(size)?;
-                    continue;
-                }
-                let handler = $handlers.iter().find(|x| x.ctype == ctype);
-                let read_size;
-                if let Some(ref handler) = handler {
-                    read_size = (handler.parse)(self, br, size)?;
-                } else {
-                    read_size = 0;
-                }
-                validate!(read_size <= size);
-                br.skip64(size - read_size)?;
-            }
-            self.depth -= 1;
-            validate!(br.tell() == list_end);
-            Ok(())
-        }
-    }
-}
-
-fn skip_chunk(_track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    br.skip64(size)?;
-    Ok(size)
-}
-
-fn read_ftyp(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    dmx.src.skip64(size)?;
-    Ok(size)
-}
-
-fn read_mdat(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    dmx.mdat_pos  = dmx.src.tell();
-    dmx.mdat_size = size;
-    dmx.src.skip64(size)?;
-    Ok(size)
-}
-
-fn read_sidx(_dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, _size: u64) -> DemuxerResult<u64> {
-    Ok(0)
-}
-
-fn read_moov(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    dmx.read_moov(strmgr, size)?;
-    Ok(size)
-}
-
-const MOOV_CHUNK_HANDLERS: &[RootChunkHandler] = &[
-    RootChunkHandler { ctype: mktag!(b"mvhd"), parse: read_mvhd },
-    RootChunkHandler { ctype: mktag!(b"cmov"), parse: read_cmov },
-    RootChunkHandler { ctype: mktag!(b"ctab"), parse: read_ctab },
-    RootChunkHandler { ctype: mktag!(b"trak"), parse: read_trak },
-    RootChunkHandler { ctype: mktag!(b"meta"), parse: read_meta },
-    RootChunkHandler { ctype: mktag!(b"mvex"), parse: read_mvex },
-    RootChunkHandler { ctype: mktag!(b"iods"), parse: skip_chunk_mov },
-];
-
-fn read_mvhd(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_MVHD_SIZE: u64 = 100;
-    let br = &mut dmx.src;
-    validate!(size >= KNOWN_MVHD_SIZE);
-    let version             = br.read_byte()?;
-    validate!(version == 0 || version == 0xFF);
-    dmx.ver_m1 = version == 0xFF;
-    let _flags              = br.read_u24be()?;
-    let _ctime              = br.read_u32be()?;
-    let _mtime              = br.read_u32be()?;
-    let tscale              = br.read_u32be()?;
-    let duration            = br.read_u32be()?;
-    let _pref_rate          = br.read_u32be()?;
-    let _pref_volume        = br.read_u16be()?;
-                              br.read_skip(10)?;
-                              br.read_skip(36)?; // matrix
-    let _preview_time       = br.read_u32be()?;
-    let _preview_duration   = br.read_u32be()?;
-    let _poster_time        = br.read_u32be()?;
-    let _sel_time           = br.read_u32be()?;
-    let _sel_duration       = br.read_u32be()?;
-    let _cur_time           = br.read_u32be()?;
-    let _next_track_id      = br.read_u32be()?;
-    dmx.duration = duration;
-    dmx.tb_den = tscale;
-
-    Ok(KNOWN_MVHD_SIZE)
-}
-
-fn read_cmov(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    let br = &mut dmx.src;
-    validate!(size > 24);
-    let dcom_size           = br.read_u32be()?;
-    let dcom_tag            = br.read_tag()?;
-    let compr_type          = br.read_tag()?;
-    validate!(&dcom_tag == b"dcom" && dcom_size == 12);
-    if &compr_type != b"zlib" {
-        return Err(DemuxerError::NotImplemented);
-    }
-    let cmvd_size           = u64::from(br.read_u32be()?);
-    let cmvd_tag            = br.read_tag()?;
-    validate!(&cmvd_tag == b"cmvd" && cmvd_size > 14 && cmvd_size == size - 12);
-    let comp_size = (cmvd_size - 12) as usize;
-    let uncomp_size         = br.read_u32be()? as usize;
-    validate!(uncomp_size > 8);
-    let mut sbuf = vec![0; comp_size];
-    let mut dbuf = vec![0; uncomp_size];
-                              br.read_buf(sbuf.as_mut_slice())?;
-    let ret = Inflate::uncompress(sbuf.as_slice(), dbuf.as_mut_slice());
-    if ret.is_err() {
-        return Err(DemuxerError::InvalidData);
-    }
-    let len = ret.unwrap();
-    validate!(len == uncomp_size);
-    let mut br = MemoryReader::new_read(dbuf.as_slice());
-    let (ctype, csize) = read_chunk_header(&mut br)?;
-    validate!(ctype == mktag!(b"moov"));
-    let mut ddmx = MOVDemuxer::new(&mut br);
-    ddmx.print_chunks = dmx.print_chunks;
-    ddmx.read_moov(strmgr, csize)?;
-    std::mem::swap(&mut dmx.tracks, &mut ddmx.tracks);
-    dmx.duration = ddmx.duration;
-    dmx.tb_den = ddmx.tb_den;
-    std::mem::swap(&mut dmx.pal, &mut ddmx.pal);
-
-    Ok(size)
-}
-
-fn read_ctab(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    let mut pal = [0; 1024];
-    let size = read_palette(dmx.src, size, &mut pal)?;
-    dmx.pal = Some(Arc::new(pal));
-    Ok(size)
-}
-
-fn read_meta(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    dmx.src.skip64(size)?;
-    Ok(size)
-}
-
-fn read_mvex(_dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, _size: u64) -> DemuxerResult<u64> {
-    Ok(0)
-}
-
-fn skip_chunk_mov(_dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, _size: u64) -> DemuxerResult<u64> {
-    Ok(0)
-}
-
-fn read_trak(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    let mut track = Track::new(dmx.cur_track as u32, dmx.tb_den);
-    track.print_chunks = dmx.print_chunks;
-    track.ver_m1 = dmx.ver_m1;
-    track.read_trak(dmx.src, size)?;
-    validate!(track.tkhd_found);
-    if !track.stsd_found {
-        track.stream = Some(NAStream::new(StreamType::Data, track.track_no, DUMMY_CODEC_INFO, track.tb_num, track.tb_den, 0));
-    }
-    // invent keyframes for video stream if none were reported
-    if !track.stss_found && track.stream_type == StreamType::Video {
-        track.keyframes.reserve(track.time_to_sample.len());
-        if !track.time_to_sample.is_empty() {
-            let mut abs_csamp = 1u64;
-            for &(count, _scount) in track.time_to_sample.iter() {
-                let count = u64::from(count);
-                track.keyframes.push(abs_csamp as u32);
-                abs_csamp += count;
-            }
-        } else if let Some(ref stream) = track.stream {
-            if let Some(desc) = get_codec_description(stream.get_info().get_name()) {
-                if (desc.caps & CODEC_CAP_INTRAONLY) != 0 && !track.chunk_sizes.is_empty() {
-                    for samp in 0..track.chunk_sizes.len() {
-                        track.keyframes.push((samp + 1) as u32);
-                    }
-                }
-            }
-        }
-    }
-    validate!(strmgr.get_stream_by_id(track.track_id).is_none());
-    dmx.cur_track += 1;
-    dmx.tracks.push(track);
-    Ok(size)
-}
-
-fn read_moof(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    dmx.moof_off = dmx.src.tell() - 8;
-    dmx.read_moof(strmgr, size)?;
-    Ok(size)
-}
-
-const MOOF_CHUNK_HANDLERS: &[RootChunkHandler] = &[
-    RootChunkHandler { ctype: mktag!(b"mfhd"), parse: read_mfhd },
-    RootChunkHandler { ctype: mktag!(b"traf"), parse: read_traf },
-    RootChunkHandler { ctype: mktag!(b"meta"), parse: read_meta },
-];
-
-fn read_mfhd(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_MFHD_SIZE: u64 = 8;
-    validate!(size >= KNOWN_MFHD_SIZE);
-    let version             = dmx.src.read_byte()?;
-    validate!(version == 0);
-    let flags               = dmx.src.read_u24be()?;
-    validate!(flags == 0);
-    let _seq_no             = dmx.src.read_u32be()?;
-
-    Ok(KNOWN_MFHD_SIZE)
-}
-
-fn read_traf(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
-    let mut buf = [0u8; 16];
-                              dmx.src.peek_buf(&mut buf)?;
-    validate!(&buf[4..8] == b"tfhd");
-    let track_id = read_u32be(&buf[12..16])?;
-    let mut track = None;
-    for trk in dmx.tracks.iter_mut() {
-        if trk.track_id == track_id {
-            track = Some(trk);
-            break;
-        }
-    }
-    if let Some(track) = track {
-        track.moof_off = dmx.moof_off;
-        track.read_traf(dmx.src, size)?;
-        Ok(size)
-    } else {
-        Ok(0)
-    }
-}
-
-const TRAK_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
-    TrackChunkHandler { ctype: mktag!(b"clip"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"matt"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"edts"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"tref"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"load"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"imap"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"tkhd"), parse: read_tkhd },
-    TrackChunkHandler { ctype: mktag!(b"mdia"), parse: read_mdia },
-];
-
-fn read_tkhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_TKHD_SIZE: u64 = 84;
-    validate!(size >= KNOWN_TKHD_SIZE);
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let _ctime              = br.read_u32be()?;
-    let _mtime              = br.read_u32be()?;
-    let track_id            = br.read_u32be()?;
-                              br.read_skip(4)?;
-    let duration            = br.read_u32be()?;
-                              br.read_skip(8)?;
-    let _layer              = br.read_u16be()?;
-    let _alt_group          = br.read_u16be()?;
-    let _volume             = br.read_u16be()?;
-                              br.read_skip(2)?;
-                              br.read_skip(36)?; // matrix
-    let width               = br.read_u32be()? as usize;
-    let height              = br.read_u32be()? as usize;
-    track.width  = width  >> 16;
-    track.height = height >> 16;
-    track.track_id = track_id;
-    track.duration = duration;
-
-    track.tkhd_found = true;
-    Ok(KNOWN_TKHD_SIZE)
-}
-
-fn read_mdia(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    track.stream_type = StreamType::None;
-    track.read_mdia(br, size)?;
-    Ok(size)
-}
-
-const MDIA_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
-    TrackChunkHandler { ctype: mktag!(b"mdhd"), parse: read_mdhd },
-    TrackChunkHandler { ctype: mktag!(b"hdlr"), parse: read_hdlr },
-    TrackChunkHandler { ctype: mktag!(b"minf"), parse: read_minf },
-];
-
-fn read_mdhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_MDHD_SIZE: u64 = 24;
-    validate!(size >= KNOWN_MDHD_SIZE);
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let flags               = br.read_u24be()?;
-    validate!(flags == 0);
-    let _ctime              = br.read_u32be()?;
-    let _mtime              = br.read_u32be()?;
-    track.tb_den            = br.read_u32be()?;
-    validate!(track.tb_den != 0);
-    track.duration          = br.read_u32be()?;
-    let _language           = br.read_u16be()?;
-    let _quality            = br.read_u16be()?;
-
-    Ok(KNOWN_MDHD_SIZE)
-}
-
-fn read_hdlr(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_HDLR_SIZE: u64 = 24;
-    validate!(size >= KNOWN_HDLR_SIZE);
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let flags               = br.read_u24be()?;
-    validate!(flags == 0);
-    let comp_type           = br.read_u32be()?;
-    let comp_subtype        = br.read_u32be()?;
-    let _comp_manufacturer  = br.read_u32be()?;
-    let _comp_flags         = br.read_u32be()?;
-    let _comp_flags_mask    = br.read_u32be()?;
-
-    if comp_type == mktag!(b"mhlr") || comp_type == 0 {
-        if comp_subtype == mktag!(b"vide") {
-            track.stream_type = StreamType::Video;
-        } else if comp_subtype == mktag!(b"soun") {
-            track.stream_type = StreamType::Audio;
-        } else {
-            track.stream_type = StreamType::Data;
-        }
-    } else if comp_type == mktag!(b"dhlr") {
-        track.stream_type = StreamType::Data;
-    } else {
-        println!("Unknown stream type");
-        track.stream_type = StreamType::Data;
-    }
-
-    Ok(KNOWN_HDLR_SIZE)
-}
-
-fn read_minf(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    track.read_minf(br, size)?;
-    Ok(size)
-}
-
-const MINF_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
-    TrackChunkHandler { ctype: mktag!(b"hdlr"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"dinf"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"vmhd"), parse: read_vmhd },
-    TrackChunkHandler { ctype: mktag!(b"smhd"), parse: read_smhd },
-    TrackChunkHandler { ctype: mktag!(b"gmhd"), parse: read_gmhd },
-    TrackChunkHandler { ctype: mktag!(b"gmin"), parse: read_gmin },
-    TrackChunkHandler { ctype: mktag!(b"stbl"), parse: read_stbl },
-];
-
-fn read_vmhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_VMHD_SIZE: u64 = 12;
-    validate!(track.stream_type == StreamType::Video);
-    validate!(size >= KNOWN_VMHD_SIZE);
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-                              br.read_skip(2)?; // graphics mode
-                              br.read_skip(6)?; // opcolor
-    Ok(KNOWN_VMHD_SIZE)
-}
-
-fn read_smhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_SMHD_SIZE: u64 = 8;
-    validate!(track.stream_type == StreamType::Audio);
-    validate!(size >= KNOWN_SMHD_SIZE);
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-                              br.read_skip(2)?; // balance
-                              br.read_skip(2)?;
-    Ok(KNOWN_SMHD_SIZE)
-}
-
-fn read_gmhd(track: &mut Track, _br: &mut dyn ByteIO, _size: u64) -> DemuxerResult<u64> {
-    validate!(track.stream_type == StreamType::Data);
-    Ok(0)
-}
-
-fn read_gmin(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    validate!(track.stream_type == StreamType::Data);
-    const KNOWN_GMIN_SIZE: u64 = 16;
-    validate!(size >= KNOWN_GMIN_SIZE);
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-                              br.read_skip(2)?; // graphics mode
-                              br.read_skip(6)?; // opcolor
-                              br.read_skip(2)?; // balance
-                              br.read_skip(2)?;
-    Ok(KNOWN_GMIN_SIZE)
-}
-
-fn read_stbl(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    track.read_stbl(br, size)?;
-    Ok(size)
-}
-
-const STBL_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
-    TrackChunkHandler { ctype: mktag!(b"stsd"), parse: read_stsd },
-    TrackChunkHandler { ctype: mktag!(b"stts"), parse: read_stts },
-    TrackChunkHandler { ctype: mktag!(b"stgs"), parse: read_stgs },
-    TrackChunkHandler { ctype: mktag!(b"stss"), parse: read_stss },
-    TrackChunkHandler { ctype: mktag!(b"stsc"), parse: read_stsc },
-    TrackChunkHandler { ctype: mktag!(b"stsz"), parse: read_stsz },
-    TrackChunkHandler { ctype: mktag!(b"stco"), parse: read_stco },
-    TrackChunkHandler { ctype: mktag!(b"co64"), parse: read_co64 },
-    TrackChunkHandler { ctype: mktag!(b"stsh"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"ctts"), parse: read_ctts },
-];
-
-fn parse_audio_edata(br: &mut dyn ByteIO, start_pos: u64, size: u64) -> DemuxerResult<Option<Vec<u8>>> {
-    let read_part = br.tell() - start_pos;
-    if read_part + 8 < size {
-        let mut buf = [0; 8];
-                              br.peek_buf(&mut buf)?;
-        if &buf[4..8] != b"wave" {
-            let mut buf = vec![0; (size - read_part) as usize];
-                              br.read_buf(&mut buf)?;
-            return Ok(Some(buf));
-        }
-
-        let csize           = br.read_u32be()? as u64;
-        let ctag            = br.read_u32be()?;
-        validate!(read_part + csize <= size);
-        validate!(ctag == mktag!(b"wave"));
-        if csize == 8 {
-            return Ok(None);
-        }
-        let mut buf = [0; 8];
-                              br.peek_buf(&mut buf)?;
-        if &buf[4..8] == b"frma" {
-                              br.read_skip(12)?;
-            if csize > 20 {
-                let mut buf = vec![0; (csize - 20) as usize];
-                              br.read_buf(&mut buf)?;
-                Ok(Some(buf))
-            } else {
-                Ok(None)
-            }
-        } else if csize > 8 {
-            let mut buf = vec![0; (csize as usize) - 8];
-                              br.read_buf(&mut buf)?;
-            Ok(Some(buf))
-        } else {
-            Ok(None)
-        }
-    } else {
-        Ok(None)
-    }
-}
-
-#[allow(clippy::neg_cmp_op_on_partial_ord)]
-fn read_stsd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    const KNOWN_STSD_SIZE: u64 = 24;
-    validate!(size >= KNOWN_STSD_SIZE);
-    let start_pos = br.tell();
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let entries             = br.read_u32be()?;
-    validate!(entries > 0);
-    let esize               = u64::from(br.read_u32be()?);
-    validate!(esize + 8 <= size);
-    let mut fcc = [0u8; 4];
-                              br.read_buf(&mut fcc)?;
-                              br.read_skip(6)?;
-    let _data_ref           = br.read_u16be()?;
-
-    track.fcc = fcc;
-
-    let codec_info;
-    match track.stream_type {
-        StreamType::Video => {
-            let _ver            = br.read_u16be()?;
-            let _revision       = br.read_u16le()?;
-            let _vendor         = br.read_u32be()?;
-            let _temp_quality   = br.read_u32be()?;
-            let _spat_quality   = br.read_u32be()?;
-            let width           = br.read_u16be()? as usize;
-            let height          = br.read_u16be()? as usize;
-            let _hor_res        = br.read_u32be()?;
-            let _vert_res       = br.read_u32be()?;
-            let data_size       = br.read_u32be()?;
-            if !track.ver_m1 {
-                validate!(data_size == 0);
-            }
-            let _frame_count    = br.read_u16be()? as usize;
-            let _cname_len      = br.read_byte()? as usize;
-                                  br.read_skip(31)?; // actual compressor name
-            let depth           = br.read_u16be()?;
-            let ctable_id       = br.read_u16be()?;
-            let grayscale = depth > 0x20 || depth == 1;
-            let depth = if grayscale { depth & 0x1F } else { depth };
-            if ctable_id == 0 {
-                let max_pal_size = start_pos + size - br.tell();
-                if depth <= 8 {
-                    let mut pal = [0; 1024];
-                    read_palette(br, max_pal_size, &mut pal)?;
-                    track.pal = Some(Arc::new(pal));
-                } else {
-                                  br.read_skip(max_pal_size as usize)?;
-                }
-            } else if (depth <= 8) && !grayscale {
-                match depth & 0x1F {
-                    2 => {
-                        let mut pal = [0; 1024];
-                        pal[..4 * 4].copy_from_slice(&MOV_DEFAULT_PAL_2BIT);
-                        track.pal = Some(Arc::new(pal));
-                    },
-                    4 => {
-                        let mut pal = [0; 1024];
-                        pal[..16 * 4].copy_from_slice(&MOV_DEFAULT_PAL_4BIT);
-                        track.pal = Some(Arc::new(pal));
-                    },
-                    8 => {
-                        track.pal = Some(Arc::new(MOV_DEFAULT_PAL_8BIT));
-                    },
-                    _ => {},
-                };
-            } else if grayscale && ctable_id != 0xFFFF {
-                let mut pal = [0; 1024];
-                let cdepth = depth & 0x1F;
-                let size = 1 << cdepth;
-                for i in 0..size {
-                    let mut clr = ((size - 1 - i) as u8) << (8 - cdepth);
-                    let mut off = 8 - cdepth;
-                    while off >= cdepth {
-                        clr |= clr >> (8 - off);
-                        off -= cdepth;
-                    }
-                    if off > 0 {
-                        clr |= clr >> (8 - off);
-                    }
-                    pal[i * 4]     = clr;
-                    pal[i * 4 + 1] = clr;
-                    pal[i * 4 + 2] = clr;
-                }
-                track.pal = Some(Arc::new(pal));
-            }
-// todo other atoms, put as extradata
-            let cname = if let Some(name) = find_codec_from_mov_video_fourcc(&fcc) {
-                    name
-                } else if let Some(name) = find_codec_from_avi_fourcc(&fcc) {
-                    name
-                } else {
-                    "unknown"
-                };
-            let format = if cname == "rawvideo" {
-                    if &fcc == b"j420" {
-                        validate!(depth == 12);
-                        YUV420_FORMAT
-                    } else {
-                        match depth {
-                            1..=8 | 33..=40 => PAL8_FORMAT,
-                            15 | 16 => QT_RGB555_FORMAT,
-                            24 => RGB24_FORMAT,
-                            32 => ARGB_FORMAT,
-                            _ => {
-                                println!("unknown depth {depth}");
-                                return Err(DemuxerError::NotImplemented);
-                            }
-                        }
-                    }
-                } else if depth > 8 && depth <= 32 {
-                    RGB24_FORMAT
-                } else {
-                    PAL8_FORMAT
-                };
-            let mut vhdr = NAVideoInfo::new(width, height, false, format);
-            vhdr.bits = depth as u8;
-            if track.ver_m1 { // skip the rest for the beta version of QT
-                br.seek(SeekFrom::Start(start_pos + size))?;
-            }
-            //skip various common atoms
-            while br.tell() - start_pos + 8 < size {
-                let mut buf = [0u8; 8];
-                br.peek_buf(&mut buf)?;
-                let tsize = read_u32be(&buf).unwrap() as usize;
-                let tag = &buf[4..8];
-                validate!(tsize >= 8);
-                match tag {
-                    b"pasp" | b"clap" | b"gama" => {
-                        br.read_skip(tsize)?;
-                    },
-                    _ => break,
-                };
-            }
-            let edata = if br.tell() - start_pos + 4 < size {
-                    let edata_size  = br.read_u32be()? as usize;
-                    validate!(edata_size >= 4);
-                    let mut buf = vec![0; edata_size - 4];
-                                  br.read_buf(buf.as_mut_slice())?;
-                    Some(buf)
-                } else {
-                    None
-                };
-            codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Video(vhdr), edata);
-        },
-        StreamType::Audio if track.ver_m1 => {
-                                  br.read_skip(8)?;
-            let nchannels       = br.read_u16be()?;
-            validate!(nchannels == 1 || nchannels == 2);
-            let sample_size     = br.read_u16be()?;
-            validate!(sample_size == 8 || sample_size == 16);
-                                  br.read_u32be()?;
-            let sample_rate     = br.read_u32be()? >> 16;
-            let cname = if fcc == [0; 4] { "pcm" } else { "unknown" };
-            let mut soniton = NASoniton::new(sample_size as u8, SONITON_FLAG_SIGNED | SONITON_FLAG_BE);
-            if sample_size == 8 {
-                soniton.signed = false;
-            }
-            let ahdr = NAAudioInfo::new(sample_rate, nchannels as u8, soniton, 1);
-            let edata = parse_audio_edata(br, start_pos, size)?;
-            codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Audio(ahdr), edata);
-            track.raw_audio = cname == "pcm";
-            track.bsize     = (sample_size / 8) as usize;
-            track.channels  = nchannels as usize;
-            track.bits      = sample_size as usize;
-            if track.tb_den <= 1 {
-                track.tb_den = sample_rate;
-            }
-        },
-        StreamType::Audio => {
-            let sver            = br.read_u16be()?;
-            let _revision       = br.read_u16le()?;
-            let _vendor         = br.read_u32be()?;
-            let mut nchannels   = br.read_u16be()?;
-            if sver != 2 {
-                validate!(nchannels <= 64);
-            }
-            let sample_size     = br.read_u16be()?;
-            validate!(sample_size <= 128);
-            let _compr_id       = br.read_u16be()?;
-            let packet_size     = br.read_u16be()? as usize;
-            validate!(packet_size == 0);
-            let mut sample_rate = br.read_u32be()? >> 16;
-            if sver != 2 {
-                validate!(sample_rate > 0);
-            }
-            let cname = if let Some(name) = find_codec_from_mov_audio_fourcc(&fcc) {
-                    name
-                } else if let (true, Some(name)) = ((fcc[0] == b'm' && fcc[1] == b's'),  find_codec_from_wav_twocc(u16::from(fcc[2]) * 256 + u16::from(fcc[3]))) {
-                    name
-                } else {
-                    "unknown"
-                };
-            let mut soniton = NASoniton::new(sample_size as u8, SONITON_FLAG_SIGNED | SONITON_FLAG_BE);
-            if &fcc == b"raw " && sample_size == 8 {
-                soniton.signed = false;
-            }
-            if &fcc == b"sowt" {
-                soniton.be = false;
-            }
-            let mut block_align = 1;
-            match sver {
-                1 => {
-                    let samples_per_packet  = br.read_u32be()?;
-                    let _bytes_per_packet   = br.read_u32be()?;
-                    let bytes_per_frame     = br.read_u32be()?;
-                    let _bytes_per_sample   = br.read_u32be()?;
-                    track.bsize = bytes_per_frame as usize;
-                    track.frame_samples = samples_per_packet as usize;
-                    track.tb_num = samples_per_packet.max(1);
-                    block_align = bytes_per_frame as usize;
-                },
-                2 => {
-                                              br.read_u32be()?; // some size
-                    let srate               = br.read_f64be()?;
-                    validate!(srate > 1.0);
-                    sample_rate = srate as u32;
-                    let channels            = br.read_u32be()?;
-                    validate!(channels > 0 && channels < 255);
-                    nchannels = channels as u16;
-                                              br.read_u32be()?; // always 0x7F000000
-                    let _bits_per_csample   = br.read_u32be()?;
-                    let _codec_flags        = br.read_u32be()?;
-                    let bytes_per_frame     = br.read_u32be()?;
-                    let samples_per_packet  = br.read_u32be()?;
-                    track.bsize = bytes_per_frame as usize;
-                    track.frame_samples = samples_per_packet as usize;
-                    track.tb_num = samples_per_packet;
-                },
-                _ => {
-                    track.bsize = (sample_size / 8) as usize;
-                },
-            };
-            if track.tb_den <= 1 {
-                track.tb_den = sample_rate;
-            }
-            track.raw_audio = matches!(&fcc,
-                    b"NONE" | b"raw " | b"twos" | b"sowt" |
-                    b"in24" | b"in32" | b"fl32" | b"fl64" |
-                    b"ima4" | b"ms\x00\x02" | b"ms\x00\x11" |
-                    b"alaw" | b"ulaw" |
-                    b"MAC3" | b"MAC6");
-            let ahdr = NAAudioInfo::new(sample_rate, nchannels as u8, soniton, block_align);
-            let edata = parse_audio_edata(br, start_pos, size)?;
-            codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Audio(ahdr), edata);
-            track.channels  = nchannels as usize;
-            track.bits      = sample_size as usize;
-        },
-        StreamType::None => {
-            return Err(DemuxerError::InvalidData);
-        },
-        _ => {
-//todo put it all into extradata
-            let edata = None;
-            codec_info = NACodecInfo::new("unknown", NACodecTypeInfo::None, edata);
-        },
-    };
-    let read_size = br.tell() - start_pos;
-    validate!(read_size <= size);
-    track.stream = Some(NAStream::new(track.stream_type, track.track_no, codec_info, track.tb_num, track.tb_den, u64::from(track.duration)));
-    track.stsd_found = true;
-    Ok(read_size)
-}
-
-fn read_stts(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    validate!(size >= 8);
-    let start_pos = br.tell();
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let entries             = br.read_u32be()? as usize;
-    validate!(entries as u64 <= (size - 8) / 8);
-    if entries == 0 {
-    } else if entries == 1 {
-        let _count          = br.read_u32be()?;
-        let tb_num          = br.read_u32be()?;
-        validate!(tb_num != 0);
-        track.rescale(tb_num);
-    } else {
-        track.time_to_sample.clear();
-        track.time_to_sample.reserve(entries);
-        for _ in 0..entries {
-            let count       = br.read_u32be()?;
-            let mult        = br.read_u32be()?;
-            track.time_to_sample.push((count, mult));
-        }
-    }
-    let read_size = br.tell() - start_pos;
-    validate!(read_size <= size);
-    Ok(read_size)
-}
-
-fn read_stgs(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    validate!(size >= 8);
-    validate!(track.ver_m1);
-    let start_pos = br.tell();
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let entries             = br.read_u32be()? as usize;
-    validate!(entries as u64 <= (size - 8) / 8);
-    if entries == 0 {
-    } else if entries == 1 {
-        let _count          = br.read_u32be()?;
-        let tb_num          = br.read_u32be()?;
-        validate!(tb_num != 0);
-        track.rescale(tb_num);
-    } else {
-        track.time_to_sample.clear();
-        track.time_to_sample.reserve(entries);
-        for _ in 0..entries {
-            let count       = br.read_u32be()?;
-            let mult        = br.read_u32be()?;
-            track.time_to_sample.push((count, mult));
-        }
-    }
-    let read_size = br.tell() - start_pos;
-    validate!(read_size <= size);
-    Ok(read_size)
-}
-
-fn read_stss(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let entries             = br.read_u32be()? as usize;
-    validate!(entries < ((u32::MAX >> 2) - 8) as usize);
-    validate!((entries * 4 + 8) as u64 == size);
-    track.keyframes = Vec::with_capacity(entries);
-    let mut last_sample_no = 0;
-    for _ in 0..entries {
-        let sample_no       = br.read_u32be()?;
-        validate!(sample_no > last_sample_no);
-        track.keyframes.push(sample_no);
-        last_sample_no = sample_no;
-    }
-    track.stss_found = true;
-    Ok(size)
-}
-
-fn read_stsc(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let entries             = br.read_u32be()? as usize;
-    if track.ver_m1 {
-        if entries != 1 || size != 24 {
-            return Err(DemuxerError::NotImplemented);
-        }
-                              br.read_u32be()?;
-        let sample_no       = br.read_u32be()?;
-        validate!(sample_no == 1);
-        let nsamples        = br.read_u32be()?;
-                              br.read_u32be()?; // maybe sample descriptor
-        track.sample_map.push((sample_no, nsamples));
-        return Ok(size);
-    }
-    validate!(entries < ((u32::MAX / 12) - 8) as usize);
-    validate!((entries * 12 + 8) as u64 == size);
-    track.sample_map = Vec::with_capacity(entries);
-    let mut last_sample_no = 0;
-    for _i in 0..entries {
-        let sample_no       = br.read_u32be()?;
-        validate!(sample_no > last_sample_no);
-        let nsamples        = br.read_u32be()?;
-        let _sample_desc    = br.read_u32be()?;
-        track.sample_map.push((sample_no, nsamples));
-        last_sample_no = sample_no;
-    }
-    Ok(size)
-}
-
-fn read_stsz(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let sample_size         = br.read_u32be()?;
-    if sample_size != 0 {
-        track.sample_size = sample_size;
-        if track.sample_size != 1 || track.bsize == 0 {
-            track.bsize = sample_size as usize;
-        }
-        Ok(8)
-    } else {
-        let entries             = br.read_u32be()? as usize;
-        validate!((entries * 4 + 12) as u64 == size);
-        track.chunk_sizes = vec![0; entries];
-                                  br.read_u32be_arr(&mut track.chunk_sizes)?;
-        Ok(size)
-    }
-}
-
-fn read_stco(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let entries             = br.read_u32be()? as usize;
-    validate!((entries * 4 + 8) as u64 == size);
-    if entries < 1024 {
-        track.chunk_offsets = Vec::with_capacity(entries);
-        for _i in 0..entries {
-            let sample_offset   = br.read_u32be()?;
-            track.chunk_offsets.push(u64::from(sample_offset));
-        }
-    } else {
-        // reading whole array at once and converting it later is much faster
-        track.chunk_offsets = vec![0; entries];
-        let mut tmp = vec![0u32; entries];
-        unsafe {
-            let data = tmp.as_mut_ptr();
-            let ptr = std::slice::from_raw_parts_mut(data as *mut u8, entries * 4);
-            br.read_buf(ptr)?;
-        }
-        for (dst, &src) in track.chunk_offsets.iter_mut().zip(tmp.iter()) {
-            *dst = u64::from(u32::from_be(src));
-        }
-    }
-    Ok(size)
-}
-
-fn read_co64(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    let version             = br.read_byte()?;
-    validate!(version == 0);
-    let _flags              = br.read_u24be()?;
-    let entries             = br.read_u32be()? as usize;
-    validate!(entries > 0 && (entries * 8 + 8) as u64 == size);
-    track.chunk_offsets = vec![0; entries];
-                              br.read_u64be_arr(&mut track.chunk_offsets)?;
-    Ok(size)
-}
-
-fn read_ctts(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    validate!(size >= 8);
-    let version             = br.read_byte()?;
-    let _flags              = br.read_u24be()?;
-    if version > 1 {
-        return Err(DemuxerError::NotImplemented);
-    }
-    let entries             = br.read_u32be()? as usize;
-    track.ctts_version = version;
-    track.ctts_map.resize(entries);
-    match version {
-        0 | 1 => {
-            validate!(size == (entries as u64) * 8 + 8);
-            if entries < 1024 {
-                for _ in 0..entries {
-                    let samp_count  = br.read_u32be()?;
-                    let samp_offset = br.read_u32be()?;
-                    track.ctts_map.add(samp_count, samp_offset / track.tb_div);
-                }
-            } else {
-                // reading whole array at once and converting it later is much faster
-                track.ctts_map.array.resize(entries, RLEPair(0, 0));
-                unsafe {
-                    let data = track.ctts_map.array.as_mut_ptr();
-                    let ptr = std::slice::from_raw_parts_mut(data as *mut u8, entries * 8);
-                    br.read_buf(ptr)?;
-                }
-                for RLEPair(count, offset) in track.ctts_map.array.iter_mut() {
-                    *count = u32::from_be(*count);
-                    *offset = u32::from_be(*offset) / track.tb_div;
-                }
-            }
-        },
-        _ => unreachable!(),
-    };
-    track.ctts_map.reset();
-
-    Ok(size)
-}
-
-const TRAF_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
-    TrackChunkHandler { ctype: mktag!(b"tfhd"), parse: read_tfhd },
-    TrackChunkHandler { ctype: mktag!(b"trun"), parse: read_trun },
-    TrackChunkHandler { ctype: mktag!(b"sbgp"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"sgpd"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"subs"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"saiz"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"saio"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"tfdt"), parse: skip_chunk },
-    TrackChunkHandler { ctype: mktag!(b"meta"), parse: skip_chunk },
-];
-
-fn read_tfhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    validate!(size >= 8);
-    let start = br.tell();
-    let _version            = br.read_byte()?;
-    let flags               = br.read_u24be()?;
-    let _track_id           = br.read_u32be()?;
-    if (flags & 0x000001) != 0 {
-        let base_offset     = br.read_u64be()?;
-        track.moof_off = base_offset;
-    }
-    if (flags & 0x000002) != 0 {
-        let _sample_description_index = br.read_u32be()?;
-    }
-    if (flags & 0x000008) != 0 {
-        let default_sample_duration = br.read_u32be()?;
-        if track.tb_div == 1 {
-            track.rescale(default_sample_duration);
-        }
-    }
-    if (flags & 0x000010) != 0 {
-        let _default_sample_size = br.read_u32be()?;
-    }
-    if (flags & 0x000020) != 0 {
-        let _default_sample_flags = br.read_u32be()?;
-    }
-    /*if (flags & 0x010000) != 0 { // duration is empty
-    }*/
-    /*if (flags & 0x020000) != 0 { // base offset is moof start
-    }*/
-    Ok(br.tell() - start)
-}
-
-fn read_trun(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
-    validate!(size >= 8);
-    let version             = br.read_byte()?;
-    let flags               = br.read_u24be()?;
-    let data_off_present        = (flags & 0x000001) != 0;
-    let first_sample_flags      = (flags & 0x000004) != 0;
-    let sample_duration_present = (flags & 0x000100) != 0;
-    let sample_size_present     = (flags & 0x000200) != 0;
-    let sample_flags_present    = (flags & 0x000400) != 0;
-    let sample_ct_off_present   = (flags & 0x000800) != 0;
-
-    let sample_count            = br.read_u32be()? as usize;
-
-    let mut hdr_size = 8;
-    let mut arr_size = 0;
-    if data_off_present {
-        hdr_size += 4;
-    }
-    if first_sample_flags {
-        hdr_size += 4;
-    }
-    if sample_duration_present {
-        arr_size += 4;
-    }
-    if sample_size_present {
-        arr_size += 4;
-    }
-    if sample_flags_present {
-        arr_size += 4;
-    }
-    if sample_ct_off_present {
-        arr_size += 4;
-    }
-    validate!(size == hdr_size + arr_size * (sample_count as u64));
-
-    let mut data_off = if data_off_present {
-            let off             = br.read_u32be()? as i32;
-            let new_off = (track.moof_off as i64) + i64::from(off);
-            validate!(new_off > 0);
-            new_off as u64
-        } else {
-            track.moof_off
-        };
-    if first_sample_flags {
-        let _flags              = br.read_u32be()?;
-    }
-
-    if sample_size_present {
-        track.chunk_sizes.reserve(sample_count);
-        track.chunk_offsets.reserve(sample_count);
-    }
-
-    if sample_ct_off_present {
-        if track.ctts_version != version {
-            track.ctts_version = version;
-        }
-        track.ctts_map.reserve(sample_count);
-    }
-
-    if track.chunk_offsets.len() < (u32::MAX as usize) {
-        track.keyframes.push((track.chunk_offsets.len() + 1) as u32);
-    }
-    for _ in 0..sample_count {
-        if sample_duration_present {
-            let _duration       = br.read_u32be()?;
-        }
-        if sample_size_present {
-            let ssize           = br.read_u32be()?;
-            track.chunk_sizes.push(ssize);
-            track.chunk_offsets.push(data_off);
-            data_off += u64::from(ssize);
-        }
-        if sample_flags_present {
-            let _flags          = br.read_u32be()?;
-        }
-        if sample_ct_off_present {
-            let samp_offset     = br.read_u32be()?;
-            if version == 0 {
-                track.ctts_map.add(1, samp_offset / track.tb_div);
-            } else {
-                track.ctts_map.add(1, ((samp_offset as i32) / (track.tb_div as i32)) as u32);
-            }
-        }
-    }
-    track.ctts_map.reset();
-
-    Ok(size)
-}
-
-struct MOVDemuxer<'a> {
-    src:            &'a mut dyn ByteIO,
-    depth:          usize,
-    mdat_pos:       u64,
-    mdat_size:      u64,
-    tracks:         Vec<Track>,
-    cur_track:      usize,
-    tb_den:         u32,
-    duration:       u32,
-    pal:            Option<Arc<[u8; 1024]>>,
-    ver_m1:         bool,
-
-    moof_off:       u64,
-
-    print_chunks:   bool,
-
-    demux_mode:     DemuxMode,
-
-    force_seek:     bool,
-}
-
-struct Track {
-    ver_m1:         bool,
-    track_id:       u32,
-    track_str_id:   usize,
-    track_no:       u32,
-    tb_num:         u32,
-    tb_den:         u32,
-    tb_div:         u32,
-    raw_audio:      bool,
-    raw_apos:       u64,
-    duration:       u32,
-    depth:          u8,
-    tkhd_found:     bool,
-    stsd_found:     bool,
-    stss_found:     bool,
-    stream_type:    StreamType,
-    width:          usize,
-    height:         usize,
-    channels:       usize,
-    bits:           usize,
-    bsize:          usize,
-    fcc:            [u8; 4],
-    keyframes:      Vec<u32>,
-    chunk_sizes:    Vec<u32>,
-    chunk_offsets:  Vec<u64>,
-    time_to_sample: Vec<(u32, u32)>,
-    sample_map:     Vec<(u32, u32)>,
-    sample_size:    u32,
-    frame_samples:  usize,
-    ctts_map:       RLESearcher<u32>,
-    ctts_version:   u8,
-    stream:         Option<NAStream>,
-    cur_chunk:      usize,
-    cur_sample:     usize,
-    cur_ts:         Option<u64>,
-    samples_left:   usize,
-    last_offset:    u64,
-    pal:            Option<Arc<[u8; 1024]>>,
-    timesearch:     TimeSearcher,
-
-    moof_off:       u64,
-
-    print_chunks:   bool,
-}
-
-#[derive(Default)]
-struct TimeSearcher {
-    idx:        usize,
-    base:       u64,
-    sbase:      u32,
-    cur_len:    u32,
-    cur_mul:    u32,
-}
-
-impl TimeSearcher {
-    fn new() -> Self { Self::default() }
-    fn reset(&mut self) {
-        *self = Self::default();
-    }
-    fn map_time(&mut self, sample: u32, tts: &[(u32, u32)]) -> u64 {
-        if tts.is_empty() {
-            u64::from(sample)
-        } else if sample >= self.sbase {
-            let mut sample = sample - self.sbase;
-            if self.idx == 0 {
-                let (cur_len, cur_mul) = tts[0];
-                self.cur_len = cur_len;
-                self.cur_mul = cur_mul;
-                self.idx += 1;
-            }
-            while self.idx < tts.len() && sample > self.cur_len {
-                sample -= self.cur_len;
-                self.sbase += self.cur_len;
-                self.base += u64::from(self.cur_len) * u64::from(self.cur_mul);
-                self.cur_len = tts[self.idx].0;
-                self.cur_mul = tts[self.idx].1;
-                self.idx += 1;
-            }
-            self.base + u64::from(sample) * u64::from(self.cur_mul)
-        } else {
-            self.reset();
-            self.map_time(sample, tts)
-        }
-    }
-}
-
-#[repr(C)]
-#[derive(Clone,Copy,Default)]
-struct RLEPair<T>(u32, T);
-
-#[derive(Default)]
-struct RLESearcher<T> {
-    array:      Vec<RLEPair<T>>,
-    idx:        usize,
-    start:      u64,
-    next:       u64,
-}
-
-impl<T:Default+Copy> RLESearcher<T> {
-    fn new() -> Self { Self::default() }
-    fn resize(&mut self, size: usize) {
-        self.array.clear();
-        self.array.reserve(size);
-    }
-    fn reserve(&mut self, size: usize) {
-        self.array.reserve(size);
-    }
-    fn add(&mut self, len: u32, val: T) {
-        self.array.push(RLEPair(len, val));
-    }
-    fn reset(&mut self) {
-        self.start = 0;
-        if !self.array.is_empty() {
-            self.idx = 0;
-            self.next = u64::from(self.array[0].0);
-        } else {
-            self.idx = self.array.len();
-            self.next = 0;
-        }
-    }
-    fn map(&mut self, sample: u64) -> Option<T> {
-        if sample < self.start {
-            self.reset();
-        }
-        if self.idx < self.array.len() {
-            if sample < self.next {
-                Some(self.array[self.idx].1)
-            } else {
-                while (self.idx < self.array.len()) && (sample >= self.next) {
-                    self.start = self.next;
-                    self.idx += 1;
-                    if self.idx < self.array.len() {
-                        self.next += u64::from(self.array[self.idx].0);
-                    }
-                }
-                if self.idx < self.array.len() {
-                    Some(self.array[self.idx].1)
-                } else {
-                    None
-                }
-            }
-        } else {
-            None
-        }
-    }
-}
-
-impl Track {
-    fn new(track_no: u32, tb_den: u32) -> Self {
-        Self {
-            tkhd_found:     false,
-            stsd_found:     false,
-            stss_found:     false,
-            track_id:       0,
-            track_str_id:   0,
-            track_no,
-            tb_num: 1,
-            tb_den,
-            tb_div:         1,
-            raw_audio:      false,
-            raw_apos:       0,
-            duration:       0,
-            stream_type:    StreamType::None,
-            width:          0,
-            height:         0,
-            channels:       0,
-            bits:           0,
-            bsize:          0,
-            fcc:            [0; 4],
-            keyframes:      Vec::new(),
-            chunk_sizes:    Vec::new(),
-            chunk_offsets:  Vec::new(),
-            time_to_sample: Vec::new(),
-            sample_map:     Vec::new(),
-            sample_size:    0,
-            frame_samples:  0,
-            ctts_map:       RLESearcher::new(),
-            ctts_version:   0,
-            stream:         None,
-            depth:          0,
-            cur_chunk:      0,
-            cur_sample:     0,
-            cur_ts:         None,
-            samples_left:   0,
-            last_offset:    0,
-            pal:            None,
-            timesearch:     TimeSearcher::new(),
-            ver_m1:         false,
-
-            moof_off:       0,
-
-            print_chunks:   false,
-        }
-    }
-    read_chunk_list!(track; "trak", read_trak, TRAK_CHUNK_HANDLERS);
-    read_chunk_list!(track; "mdia", read_mdia, MDIA_CHUNK_HANDLERS);
-    read_chunk_list!(track; "minf", read_minf, MINF_CHUNK_HANDLERS);
-    read_chunk_list!(track; "stbl", read_stbl, STBL_CHUNK_HANDLERS);
-    read_chunk_list!(track; "traf", read_traf, TRAF_CHUNK_HANDLERS);
-    fn rescale(&mut self, tb_num: u32) {
-        self.tb_div = tb_num;
-        if let Some(ref mut stream) = self.stream {
-            let tb_den = stream.tb_den;
-            let (tb_num, tb_den) = reduce_timebase(tb_num * stream.tb_num, tb_den);
-            stream.duration /= u64::from(self.tb_div);
-            stream.tb_num = tb_num;
-            stream.tb_den = tb_den;
-            self.tb_num = tb_num;
-            self.tb_den = tb_den;
-            self.duration /= self.tb_div;
-        }
-    }
-    fn fill_seek_index(&self, seek_index: &mut SeekIndex) {
-        if !self.keyframes.is_empty() {
-            seek_index.mode = SeekIndexMode::Present;
-        }
-        let mut tsearch = TimeSearcher::new();
-        for kf_time in self.keyframes.iter() {
-            let pts = tsearch.map_time(*kf_time - 1, &self.time_to_sample);
-            let time = NATimeInfo::rescale_ts(pts, self.tb_num, self.tb_den, 1, 1000);
-            seek_index.add_entry(self.track_no, SeekEntry { time, pts: u64::from(*kf_time - 1), pos: 0 });
-        }
-    }
-    fn calculate_chunk_size(&self, nsamp: usize) -> usize {
-        if nsamp == 0 {
-            self.bsize
-        } else {
-            match &self.fcc {
-                b"NONE" | b"raw " | b"twos" | b"sowt" | &[0, 0, 0, 0] => {
-                    (nsamp * self.bits * self.channels + 7) >> 3
-                },
-                b"ima4" => {
-                    let nblocks = (nsamp + 63) >> 6;
-                    nblocks * 34 * self.channels
-                },
-                b"MAC3" => {
-                    (nsamp + 5) / 6 * 2 * self.channels
-                },
-                b"MAC6" => {
-                    (nsamp + 5) / 6 * self.channels
-                },
-                b"in24" => nsamp * 3 * self.channels,
-                b"in32" | b"fl32" => nsamp * 4 * self.channels,
-                b"fl64" => nsamp * 8 * self.channels,
-                b"ulaw" | b"alaw" => nsamp,
-                b"ms\x00\x02" => { //MS ADPCM
-                    ((nsamp - 1) / 2 + 7) * self.channels
-                },
-                b"ms\x00\x11" => { //IMA ADPCM
-                    (nsamp / 2 + 4) * self.channels
-                },
-                _ => self.bsize,
-            }
-        }
-    }
-    fn reset(&mut self) {
-        self.cur_chunk = self.chunk_offsets.len();
-        self.samples_left = 0;
-        self.cur_sample = self.chunk_sizes.len();
-    }
-    fn get_next_chunk(&mut self) -> Option<(NATimeInfo, u64, usize, bool)> {
-        let dts_val = self.timesearch.map_time(self.cur_sample as u32, &self.time_to_sample);
-        let pts = if let Some(dts_corr) = self.ctts_map.map(self.cur_sample as u64) {
-                let pts = match self.ctts_version {
-                        0 => dts_val.wrapping_add(u64::from(dts_corr)),
-                        1 => dts_val.wrapping_add(i64::from(dts_corr as i32) as u64),
-                        _ => unimplemented!(),
-                    };
-                if (pts as i64) < 0 {
-                    None
-                } else {
-                    Some(pts)
-                }
-            } else {
-                Some(dts_val)
-            };
-        let mut ts = NATimeInfo::new(pts, Some(dts_val), None, self.tb_num, self.tb_den);
-        if self.chunk_offsets.len() == self.chunk_sizes.len() { // simple one-to-one mapping
-            if self.cur_sample >= self.chunk_sizes.len() {
-                return None;
-            }
-            let offset = self.chunk_offsets[self.cur_sample];
-            let size   = self.chunk_sizes[self.cur_sample] as usize;
-            self.cur_sample += 1;
-            let is_kf = self.keyframes.contains(&(self.cur_sample as u32));
-            Some((ts, offset, size, is_kf))
-        } else {
-            let chunk_start = self.samples_left == 0;
-            if self.samples_left == 0 {
-                if self.cur_chunk >= self.chunk_offsets.len() {
-                    return None;
-                }
-                for (idx, samples) in self.sample_map.iter() {
-                    if *idx as usize <= self.cur_chunk + 1 {
-                        self.samples_left = *samples as usize;
-                    } else {
-                        break;
-                    }
-                }
-                self.last_offset = self.chunk_offsets[self.cur_chunk];
-                self.cur_chunk += 1;
-            }
-            let offset = self.last_offset;
-            let size = self.get_size(self.cur_sample);
-            self.last_offset += size as u64;
-            let is_kf = chunk_start && (self.stream_type == StreamType::Audio || self.keyframes.contains(&(self.cur_chunk as u32)));
-            if self.stream_type == StreamType::Video {
-                self.samples_left -= 1;
-            } else if self.frame_samples != 0 && self.bsize != 0 {
-                let nblocks = size / self.bsize;
-                if self.raw_audio {
-                    ts.pts = Some(self.raw_apos);
-                    ts.duration = Some(nblocks as u64);
-                    self.raw_apos += nblocks as u64;
-                }
-                if nblocks > 0 {
-                    let consumed = (nblocks * self.frame_samples).min(self.samples_left);
-                    self.samples_left -= consumed;
-                } else {
-                    self.samples_left = 0;
-                }
-            } else if !self.raw_audio {
-                self.samples_left -= 1;
-            } else {
-                const BLOCK_SAMPLES: usize = 1024 * 6; // should be multiple of 64 and 6 to fit both IMA ADPCM and MACE 6:1 blocks
-                self.last_offset -= size as u64;
-                let samples = self.samples_left.min(BLOCK_SAMPLES);
-                let cur_size = self.calculate_chunk_size(samples);
-                self.cur_sample   += samples;
-                self.samples_left -= samples;
-                self.last_offset += cur_size as u64;
-                return Some((ts, offset, cur_size, true));
-            }
-            self.cur_sample += 1;
-            Some((ts, offset, size, is_kf))
-        }
-    }
-    fn get_size(&self, sample_no: usize) -> usize {
-        if !self.chunk_sizes.is_empty() {
-            self.chunk_sizes[sample_no] as usize
-        } else if self.stream_type != StreamType::Audio && self.sample_map.len() <= 1 && self.bsize > 0 {
-            self.bsize
-        } else if !self.sample_map.is_empty() && self.sample_size == 0 {
-            let mut nsamp = 0;
-            for (idx, samples) in self.sample_map.iter() {
-                if *idx as usize <= self.cur_chunk {
-                    nsamp = *samples;
-                } else {
-                    break;
-                }
-            }
-            self.calculate_chunk_size(nsamp as usize)
-        } else {
-            self.bsize
-        }
-    }
-    fn seek(&mut self, pts: u64, tpoint: NATimePoint, forced: bool) -> DemuxerResult<u64> {
-        let tgt_pts = match tpoint {
-            NATimePoint::Milliseconds(ms) => NATimeInfo::rescale_ts(ms, 1, 1000, self.tb_num, self.tb_den),
-            NATimePoint::PTS(ts) => ts,
-            _ => 0,
-        };
-        self.cur_sample = pts as usize;
-        self.samples_left = 0;
-        self.cur_ts = None;
-        if forced && pts.abs_diff(tgt_pts) > 5 * u64::from(self.tb_den) {
-            self.cur_sample = tgt_pts as usize;
-        }
-        if self.stream_type == StreamType::Audio {
-            if let NATimePoint::Milliseconds(ms) = tpoint {
-                let exp_pts = NATimeInfo::rescale_ts(ms, 1, 1000, self.tb_num, self.tb_den);
-                if self.raw_audio {
-                    if self.frame_samples != 0 {
-                        self.raw_apos = exp_pts / (self.frame_samples as u64);
-                        let mut apos = 0;
-                        self.cur_sample = 0;
-                        self.cur_chunk = 0;
-                        let mut cmap = self.sample_map.iter();
-                        let mut cur_samps = 0;
-                        let (mut next_idx, mut next_samples) = cmap.next().unwrap();
-                        loop {
-                            if self.cur_chunk + 1 == next_idx as usize {
-                                self.samples_left = cur_samps;
-                                cur_samps = next_samples as usize;
-                                if let Some((new_idx, new_samples)) = cmap.next() {
-                                    next_idx = *new_idx;
-                                    next_samples = *new_samples;
-                                }
-                            }
-                            self.raw_apos = apos;
-                            apos += (cur_samps / self.frame_samples) as u64;
-                            if apos > exp_pts && (cur_samps == self.frame_samples || apos > exp_pts + 1) {
-                                if self.cur_chunk >= self.chunk_offsets.len() {
-                                    return Err(DemuxerError::SeekError);
-                                }
-                                self.last_offset = self.chunk_offsets[self.cur_chunk];
-                                break;
-                            }
-                            self.cur_chunk += 1;
-                        }
-                        self.samples_left = cur_samps;
-                        self.cur_chunk += 1;
-                    } else {
-                        self.raw_apos = exp_pts;
-                        self.cur_sample = exp_pts as usize;
-                        let mut csamp = 0;
-                        self.cur_chunk = 0;
-                        let mut cmap = self.sample_map.iter();
-                        let mut cur_samps = 0;
-                        let (mut next_idx, mut next_samples) = cmap.next().unwrap();
-                        loop {
-                            if self.cur_chunk + 1 == next_idx as usize {
-                                self.samples_left = cur_samps;
-                                cur_samps = next_samples as usize;
-                                if let Some((new_idx, new_samples)) = cmap.next() {
-                                    next_idx = *new_idx;
-                                    next_samples = *new_samples;
-                                }
-                            }
-                            csamp += cur_samps;
-                            if csamp > self.cur_sample {
-                                if self.cur_chunk >= self.chunk_offsets.len() {
-                                    return Err(DemuxerError::SeekError);
-                                }
-                                self.last_offset = self.chunk_offsets[self.cur_chunk];
-                                break;
-                            }
-                            self.cur_chunk += 1;
-                        }
-                        self.samples_left = csamp - self.cur_sample;
-                        self.cur_chunk += 1;
-                    }
-                } else if self.chunk_offsets.len() == self.chunk_sizes.len() && self.duration != 0 {
-                    let new_sample = (self.chunk_sizes.len() as u64 * exp_pts / u64::from(self.duration)) as usize;
-                    self.cur_sample = new_sample;
-                    self.cur_chunk = self.cur_sample;
-                } else {
-                    if !self.time_to_sample.is_empty() {
-                        let mut remaining = exp_pts;
-                        let mut abs_csamp = 0;
-                        for &(count, scount) in self.time_to_sample.iter() {
-                            let count = u64::from(count);
-                            let scount = u64::from(scount);
-                            let nblk = remaining / scount;
-                            if nblk < count {
-                                abs_csamp += nblk;
-                                break;
-                            }
-                            remaining -= count * scount;
-                            abs_csamp += count;
-                        }
-                        self.cur_sample = abs_csamp as usize;
-                    } else {
-                        self.cur_sample = exp_pts as usize;
-                    }
-                    let tgt_sample = self.cur_sample;
-                    let mut csamp = 0;
-                    self.cur_chunk = 0;
-                    let mut cmap = self.sample_map.iter();
-                    let mut cur_samps = 0;
-                    let (mut next_idx, mut next_samples) = cmap.next().unwrap();
-                    loop {
-                        if self.cur_chunk + 1 == next_idx as usize {
-                            self.samples_left = cur_samps;
-                            cur_samps = next_samples as usize;
-                            if let Some((new_idx, new_samples)) = cmap.next() {
-                                next_idx = *new_idx;
-                                next_samples = *new_samples;
-                            }
-                        }
-                        csamp += cur_samps;
-                        if csamp > self.cur_sample {
-                            if self.cur_chunk >= self.chunk_offsets.len() {
-                                self.cur_sample = csamp - cur_samps;
-                                self.samples_left = 0;
-                                self.cur_sample = csamp;
-                                return Err(DemuxerError::SeekError);
-                            }
-                            self.last_offset = self.chunk_offsets[self.cur_chunk];
-                            break;
-                        }
-                        self.cur_chunk += 1;
-                    }
-                    self.cur_sample = csamp - cur_samps;
-                    self.samples_left = cur_samps;
-                    self.last_offset = self.chunk_offsets[self.cur_chunk];
-                    self.cur_chunk += 1;
-
-                    // try to refine sample position
-                    if self.chunk_sizes.len() > self.chunk_offsets.len() {
-                        for i in self.cur_sample..tgt_sample {
-                            self.cur_sample   += 1;
-                            self.samples_left -= 1;
-                            self.last_offset  += u64::from(self.chunk_sizes[i]);
-                        }
-                    }
-                }
-            } else {
-                self.cur_chunk = self.cur_sample;
-            }
-        } else if self.chunk_offsets.len() != self.chunk_sizes.len() && !self.sample_map.is_empty() {
-            let mut csamp = 0;
-            self.cur_chunk = 0;
-            let mut cmap = self.sample_map.iter();
-            let mut cur_samps = 0;
-            let (mut next_idx, mut next_samples) = cmap.next().unwrap();
-            loop {
-                if self.cur_chunk + 1 == next_idx as usize {
-                    self.samples_left = cur_samps;
-                    cur_samps = next_samples as usize;
-                    if let Some((new_idx, new_samples)) = cmap.next() {
-                        next_idx = *new_idx;
-                        next_samples = *new_samples;
-                    }
-                }
-                csamp += cur_samps;
-                if csamp >= self.cur_sample {
-                    if self.cur_chunk >= self.chunk_offsets.len() {
-                        if self.stream_type == StreamType::Data {
-                            self.cur_chunk = self.chunk_offsets.len();
-                            self.samples_left = 0;
-                            return Ok(NATimeInfo::rescale_ts(tgt_pts, self.tb_num, self.tb_den, 1, 1000));
-                        }
-                        return Err(DemuxerError::SeekError);
-                    }
-                    self.last_offset = self.chunk_offsets[self.cur_chunk];
-                    break;
-                }
-                self.cur_chunk += 1;
-            }
-            csamp -= cur_samps;
-            for sample_no in csamp..self.cur_sample {
-                self.last_offset += self.get_size(sample_no) as u64;
-            }
-            self.samples_left = csamp + cur_samps - self.cur_sample;
-            self.cur_chunk += 1;
-        }
-        let cur_pts = self.timesearch.map_time(self.cur_sample as u32, &self.time_to_sample);
-        let cur_time = NATimeInfo::rescale_ts(cur_pts, self.tb_num, self.tb_den, 1, 1000);
-        Ok(cur_time)
-    }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn process_packet(src: &mut dyn ByteIO, strmgr: &StreamManager, track: &mut Track, ts: NATimeInfo, offset: u64, size: usize, first: bool, is_kf: bool) -> DemuxerResult<NAPacket> {
-    if let Some(cpts) = ts.get_pts() {
-        let cts = NATimeInfo::rescale_ts(cpts, ts.tb_num, ts.tb_den, 1, 1000);
-        track.cur_ts = Some(cts);
-    } else {
-        track.cur_ts = None;
-    }
-    let stream = strmgr.get_stream(track.track_str_id);
-    if stream.is_none() { return Err(DemuxerError::InvalidData); }
-    let stream = stream.unwrap();
-    src.seek(SeekFrom::Start(offset))?;
-    let mut pkt = src.read_packet(stream, ts, first | is_kf, size)?;
-    if let Some(ref pal) = track.pal {
-        let side_data = NASideData::Palette(first, pal.clone());
-        pkt.add_side_data(side_data);
-    }
-    Ok(pkt)
-}
-
-impl<'a> DemuxCore<'a> for MOVDemuxer<'a> {
-    fn open(&mut self, strmgr: &mut StreamManager, seek_index: &mut SeekIndex) -> DemuxerResult<()> {
-        let mut data_start = 0;
-        match self.demux_mode {
-            DemuxMode::Normal => self.read_root(strmgr)?,
-            DemuxMode::MacBin => {
-                let ver                     = self.src.read_byte()?;
-                validate!(ver == 0);
-                                              self.src.read_skip(64)?;
-                let tag                     = self.src.read_tag()?;
-                validate!(&tag == b"MooV");
-                                              self.src.read_skip(14)?;
-                let data_length             = self.src.read_u32be()?;
-                validate!(data_length > 8);
-                let rsrc_length             = self.src.read_u32be()?;
-                validate!(rsrc_length > 0);
-                                              self.src.read_skip(31)?;
-                let ver                     = self.src.read_byte()?;
-                validate!(ver == 0x81);
-                let ver                     = self.src.read_byte()?;
-                validate!(ver == 0x81);
-                //xxx: maybe check header CRC
-
-                let rsrc_start = 0x80 + ((data_length + 0x7F) & !0x7F);
-                                              self.src.seek(SeekFrom::Start(rsrc_start.into()))?;
-                let rsrc_off                = self.src.read_u32be()?;
-                let rsrc_map_off            = self.src.read_u32be()?;
-                let rsrc_size               = self.src.read_u32be()?;
-                let _rsrc_map_size          = self.src.read_u32be()?;
-                validate!(rsrc_off >= 0x10);
-                validate!(rsrc_map_off >= rsrc_off + rsrc_size);
-                                              self.src.seek(SeekFrom::Current(i64::from(rsrc_off - 16)))?;
-                // I'm too lazy to parse resource map, so let's just iterate over resources for movie header
-                let end_pos = u64::from(rsrc_start + rsrc_off + rsrc_size);
-                let mut peek_buf = [0u8; 8];
-                while self.src.tell() < end_pos {
-                    let cur_size            = self.src.read_u32be()?;
-                    validate!(self.src.tell() + u64::from(cur_size) <= end_pos);
-                    if cur_size > 8 {
-                        let rsize           = self.src.peek_u32be()?;
-                        if rsize == cur_size {
-                                              self.src.peek_buf(&mut peek_buf)?;
-                            if &peek_buf[4..] == b"moov" {
-                                              self.src.read_skip(8)?;
-                                              self.src.peek_buf(&mut peek_buf)?;
-                                if peek_buf == [0; 8] {
-                                    println!("This looks like an alpha version of the format, try warhol demuxer instead");
-                                    return Err(DemuxerError::InvalidData);
-                                }
-                                self.read_moov(strmgr, u64::from(rsize - 8))?;
-                                self.mdat_pos = 8;
-                                break;
-                            }
-                        }
-                    }
-                                              self.src.read_skip(cur_size as usize)?;
-                }
-                // if moov/mdat is not found, it may be in data part
-                if self.tracks.is_empty() || self.mdat_pos == 0 {
-                                              self.src.seek(SeekFrom::Start(0x80))?;
-                    let end_pos = u64::from(data_length) + 0x80;
-                    while self.src.tell() < end_pos && (self.mdat_pos == 0 || self.tracks.is_empty()) {
-                        let size            = self.src.read_u32be()?;
-                        let tag             = self.src.read_tag()?;
-                        validate!(size >= 8);
-                        match &tag {
-                            b"moov" => {
-                                              self.src.peek_buf(&mut peek_buf)?;
-                                if peek_buf == [0; 8] {
-                                    println!("This looks like an alpha version of the format, try warhol demuxer instead");
-                                    return Err(DemuxerError::InvalidData);
-                                }
-                                self.read_moov(strmgr, u64::from(size - 8))?;
-                            },
-                            b"mdat" => {
-                                self.mdat_pos = self.src.tell() - 0x80;
-                                              self.src.read_skip(size as usize - 8)?;
-                            },
-                            _ => {
-                                              self.src.read_skip(size as usize - 8)?;
-                            },
-                        }
-                    }
-                }
-            },
-            DemuxMode::ResFork => {
-                let res_data_offset         = self.src.read_u32be()?;
-                let res_map_offset          = self.src.read_u32be()?;
-                let res_map_data_len        = self.src.read_u32be()?;
-                let res_map_length          = self.src.read_u32be()?;
-                validate!(res_data_offset == 0x100 && res_map_length > 16);
-                                              self.src.seek(SeekFrom::Start(u64::from(res_map_offset)))?;
-                let res_data_offset2        = self.src.read_u32be()?;
-                let res_map_offset2         = self.src.read_u32be()?;
-                let res_map_data_len2       = self.src.read_u32be()?;
-                let res_map_length2         = self.src.read_u32be()?;
-                validate!(res_data_offset == res_data_offset2
-                    && res_map_offset == res_map_offset2
-                    && res_map_data_len == res_map_data_len2
-                    && res_map_length == res_map_length2);
-                                              self.src.read_skip(res_map_length as usize - 16)?;
-                data_start = self.src.tell();
-                self.read_root(strmgr)?;
-            },
-        };
-        validate!(self.mdat_pos > 0);
-        validate!(!self.tracks.is_empty());
-        for track in self.tracks.iter_mut() {
-            let mut strm = None;
-            std::mem::swap(&mut track.stream, &mut strm);
-            if let Some(stream) = strm {
-                let str_id = strmgr.add_stream(stream).unwrap();
-                track.track_str_id = str_id;
-            }
-        }
-        match self.demux_mode {
-            DemuxMode::MacBin => {
-                // patch data offsets
-                for track in self.tracks.iter_mut() {
-                    for offset in track.chunk_offsets.iter_mut() {
-                        *offset += 0x80;
-                    }
-                }
-            },
-            DemuxMode::ResFork => {
-                // patch data offsets
-                for track in self.tracks.iter_mut() {
-                    for offset in track.chunk_offsets.iter_mut() {
-                        *offset += data_start;
-                    }
-                }
-            },
-            _ => {},
-        }
-        for track in self.tracks.iter() {
-            track.fill_seek_index(seek_index);
-        }
-        self.src.seek(SeekFrom::Start(self.mdat_pos))?;
-        self.cur_track = 0;
-        Ok(())
-    }
-
-    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
-        if self.tracks.is_empty() {
-            return Err(DemuxerError::EOF);
-        }
-        let mut has_all_time = true;
-        let mut min_ts = u64::MAX;
-        for (trk_no, trk) in self.tracks.iter().enumerate() {
-            if strmgr.is_ignored(trk_no) {
-                continue;
-            }
-            if let Some(ts) = trk.cur_ts {
-                min_ts = min_ts.min(ts);
-            } else {
-                has_all_time = false;
-                break;
-            }
-        }
-        if has_all_time {
-            for (trk_no, track) in self.tracks.iter_mut().enumerate() {
-                if let Some(ts) = track.cur_ts {
-                    if ts == min_ts {
-                        let first = track.cur_sample == 0;
-                        if let Some((cts, offset, size, is_kf)) = track.get_next_chunk() {
-                            self.cur_track = trk_no + 1;
-                            return process_packet(self.src, strmgr, track, cts, offset, size, first, is_kf);
-                        }
-                    }
-                }
-            }
-        }
-
-        for _ in 0..self.tracks.len() {
-            if self.cur_track >= self.tracks.len() {
-                self.cur_track = 0;
-            }
-            let track = &mut self.tracks[self.cur_track];
-            self.cur_track += 1;
-            if strmgr.is_ignored(self.cur_track - 1) {
-                continue;
-            }
-            let first = track.cur_sample == 0;
-            if let Some((cts, offset, size, is_kf)) = track.get_next_chunk() {
-                return process_packet(self.src, strmgr, track, cts, offset, size, first, is_kf);
-            }
-        }
-        Err(DemuxerError::EOF)
-    }
-
-    fn seek(&mut self, time: NATimePoint, seek_index: &SeekIndex) -> DemuxerResult<()> {
-        let ret = seek_index.find_pos(time);
-        if ret.is_none() {
-            if let NATimePoint::Milliseconds(_) = time {
-                let mut aonly = true;
-                for track in self.tracks.iter() {
-                    if track.stream_type != StreamType::Audio {
-                        aonly = false;
-                        break;
-                    }
-                }
-                if aonly {
-                    for track in self.tracks.iter_mut() {
-                        track.seek(0, time, false)?;
-                    }
-                    return Ok(());
-                }
-            }
-            if self.force_seek {
-                let mut ret = Ok(());
-                for track in self.tracks.iter_mut() {
-                    if let Err(err) = track.seek(0, time, true) {
-                        ret = Err(err);
-                        break;
-                    }
-                }
-                if ret.is_err() {
-                    for track in self.tracks.iter_mut() {
-                        track.reset();
-                    }
-                }
-                return ret;
-            }
-            return Err(DemuxerError::SeekError);
-        }
-        let seek_info = ret.unwrap();
-        let tbn = self.tracks[seek_info.str_id as usize].tb_num;
-        let tbd = self.tracks[seek_info.str_id as usize].tb_den;
-        let mut vpts = None;
-        let mut apts = None;
-        for track in self.tracks.iter_mut() {
-            let cur_pts = if track.track_id == seek_info.str_id {
-                    seek_info.pts
-                } else {
-                    seek_info.pts * u64::from(tbn) * u64::from(track.tb_den) / (u64::from(tbd) * u64::from(track.tb_num))
-                };
-            let actual_time = track.seek(cur_pts, time, false)?;
-            match track.stream_type {
-                StreamType::Video => vpts = Some(actual_time),
-                StreamType::Audio => apts = Some(actual_time),
-                _ => {},
-            };
-        }
-        /* For audio+video stream case when the post-seek actual times differ
-           by more than half a second try to seek audio to a closer position
-           to video.
-        */
-        if let (Some(vtime), Some(atime)) = (vpts, apts) {
-            let acount = self.tracks.iter().filter(|trk| trk.stream_type == StreamType::Audio).count();
-            let vcount = self.tracks.iter().filter(|trk| trk.stream_type == StreamType::Video).count();
-            if (vcount == 1 && acount == 1) && (vtime.max(atime) - vtime.min(atime) > 500) && (atime != 0) {
-                for track in self.tracks.iter_mut() {
-                    if track.stream_type == StreamType::Audio {
-                        let new_pts = NATimeInfo::rescale_ts(vtime, 1, 1000, track.tb_num, track.tb_den);
-                        track.seek(new_pts, NATimePoint::Milliseconds(vtime), false)?;
-                    }
-                }
-            }
-        }
-
-        Ok(())
-    }
-    fn get_duration(&self) -> u64 {
-        if self.tb_den != 0 {
-            u64::from(self.duration) * 1000 / u64::from(self.tb_den)
-        } else {
-            0
-        }
-    }
-}
-
-const PRINT_CHUNKS: &str = "print_chunks";
-
-const DEMUXER_OPTIONS: &[NAOptionDefinition] = &[
-    NAOptionDefinition {
-        name:           PRINT_CHUNKS,
-        description:    "Print parsed file structure",
-        opt_type:       NAOptionDefinitionType::Bool },
-    NAOptionDefinition {
-        name:           FORCE_SEEK_OPTION,
-        description:    FORCE_SEEK_OPTION_DESC,
-        opt_type:       NAOptionDefinitionType::Bool },
-];
-
-impl<'a> NAOptionHandler for MOVDemuxer<'a> {
-    fn get_supported_options(&self) -> &[NAOptionDefinition] { DEMUXER_OPTIONS }
-    #[allow(clippy::single_match)]
-    fn set_options(&mut self, options: &[NAOption]) {
-        for option in options.iter() {
-            for opt_def in DEMUXER_OPTIONS.iter() {
-                if opt_def.check(option).is_ok() {
-                    match (option.name, &option.value) {
-                        (PRINT_CHUNKS, NAValue::Bool(val)) => {
-                            self.print_chunks = *val;
-                        },
-                        (FORCE_SEEK_OPTION, NAValue::Bool(val)) => {
-                            self.force_seek = *val;
-                        },
-                        _ => {},
-                    }
-                }
-            }
-        }
-    }
-    fn query_option_value(&self, name: &str) -> Option<NAValue> {
-        match name {
-            PRINT_CHUNKS    => Some(NAValue::Bool(self.print_chunks)),
-            FORCE_SEEK_OPTION => Some(NAValue::Bool(self.force_seek)),
-            _ => None,
-        }
-    }
-}
-
-impl<'a> MOVDemuxer<'a> {
-    fn new(io: &'a mut dyn ByteIO) -> Self {
-        Self::new_common(io, DemuxMode::Normal)
-    }
-    fn new_macbinary(io: &'a mut dyn ByteIO) -> Self {
-        Self::new_common(io, DemuxMode::MacBin)
-    }
-    fn new_resfork(io: &'a mut dyn ByteIO) -> Self {
-        Self::new_common(io, DemuxMode::ResFork)
-    }
-    fn new_common(io: &'a mut dyn ByteIO, demux_mode: DemuxMode,) -> Self {
-        MOVDemuxer {
-            src:            io,
-            depth:          0,
-            mdat_pos:       0,
-            mdat_size:      0,
-            tracks:         Vec::with_capacity(2),
-            cur_track:      0,
-            tb_den:         0,
-            duration:       0,
-            pal:            None,
-            ver_m1:         false,
-
-            moof_off:       0,
-
-            print_chunks:   false,
-
-            demux_mode,
-            force_seek:     false,
-        }
-    }
-    fn read_root(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> {
-        self.depth = 0;
-        while self.src.left() != 0 {
-            let ret = read_chunk_header(self.src);
-            if ret.is_err() { break; }
-            let (ctype, size) = ret.unwrap();
-            if self.print_chunks {
-                print_cname(ctype, size, self.src.tell(), 0);
-            }
-            if IGNORED_CHUNKS.contains(&ctype) {
-                self.src.skip64(size)?;
-                continue;
-            }
-            let handler = ROOT_CHUNK_HANDLERS.iter().find(|x| x.ctype == ctype);
-            let read_size;
-            if let Some(handler) = handler {
-                read_size = (handler.parse)(self, strmgr, size)?;
-            } else {
-                println!("skipping unknown chunk {:08X} size {}", ctype, size);
-                read_size = 0;
-            }
-            validate!(read_size <= size);
-            self.src.skip64(size - read_size)?;
-        }
-//todo check if all needed chunks are found
-        Ok(())
-    }
-    read_chunk_list!(root; "moov", read_moov, MOOV_CHUNK_HANDLERS);
-    read_chunk_list!(root; "moof", read_moof, MOOF_CHUNK_HANDLERS);
-}
-
-pub struct MOVDemuxerCreator { }
-
-impl DemuxerCreator for MOVDemuxerCreator {
-    fn new_demuxer<'a>(&self, br: &'a mut dyn ByteIO) -> Box<dyn DemuxCore<'a> + 'a> {
-        Box::new(MOVDemuxer::new(br))
-    }
-    fn get_name(&self) -> &'static str { "mov" }
-}
-
-pub struct MacBinaryMOVDemuxerCreator { }
-
-impl DemuxerCreator for MacBinaryMOVDemuxerCreator {
-    fn new_demuxer<'a>(&self, br: &'a mut dyn ByteIO) -> Box<dyn DemuxCore<'a> + 'a> {
-        Box::new(MOVDemuxer::new_macbinary(br))
-    }
-    fn get_name(&self) -> &'static str { "mov-macbin" }
-}
-
-pub struct MacResForkMOVDemuxerCreator { }
-
-impl DemuxerCreator for MacResForkMOVDemuxerCreator {
-    fn new_demuxer<'a>(&self, br: &'a mut dyn ByteIO) -> Box<dyn DemuxCore<'a> + 'a> {
-        Box::new(MOVDemuxer::new_resfork(br))
-    }
-    fn get_name(&self) -> &'static str { "mov-resfork" }
-}
-
-#[cfg(test)]
-mod test {
-    use super::*;
-    use std::fs::File;
-
-    #[test]
-    fn test_mov_demux() {
-        // sample: https://samples.mplayerhq.hu/V-codecs/IV32/cubes.mov
-        let mut file = File::open("assets/Indeo/cubes.mov").unwrap();
-        let mut br = FileReader::new_read(&mut file);
-        let mut dmx = MOVDemuxer::new(&mut br);
-        let mut sm = StreamManager::new();
-        let mut si = SeekIndex::new();
-        dmx.open(&mut sm, &mut si).unwrap();
-
-        loop {
-            let pktres = dmx.get_frame(&mut sm);
-            if let Err(e) = pktres {
-                if e == DemuxerError::EOF { break; }
-                panic!("error");
-            }
-            let pkt = pktres.unwrap();
-            println!("Got {}", pkt);
-        }
-    }
-
-    #[test]
-    fn test_dash_demux() {
-        // sample: a stream downloaded with youtube-dl
-        let mut file = File::open("assets/ITU/dash.m4a").unwrap();
-        let mut br = FileReader::new_read(&mut file);
-        let mut dmx = MOVDemuxer::new(&mut br);
-        let mut sm = StreamManager::new();
-        let mut si = SeekIndex::new();
-        dmx.open(&mut sm, &mut si).unwrap();
-
-        loop {
-            let pktres = dmx.get_frame(&mut sm);
-            if let Err(e) = pktres {
-                if e == DemuxerError::EOF { break; }
-                panic!("error");
-            }
-            let pkt = pktres.unwrap();
-            println!("Got {}", pkt);
-        }
-    }
-
-    #[test]
-    fn test_macbinary_demux() {
-        // sample from King's Quest VI Macintosh edition
-        let mut file = File::open("assets/QT/Halfdome.bin").unwrap();
-        let mut br = FileReader::new_read(&mut file);
-        let mut dmx = MOVDemuxer::new_macbinary(&mut br);
-        let mut sm = StreamManager::new();
-        let mut si = SeekIndex::new();
-        dmx.open(&mut sm, &mut si).unwrap();
-
-        loop {
-            let pktres = dmx.get_frame(&mut sm);
-            if let Err(e) = pktres {
-                if e == DemuxerError::EOF { break; }
-                panic!("error");
-            }
-            let pkt = pktres.unwrap();
-            println!("Got {}", pkt);
-        }
-    }
-
-
-    #[test]
-    fn test_beta_qt() {
-        // sample from Apple Reference & Presentation Library 8
-        let mut file = File::open("assets/QT/Smallwhale2").unwrap();
-        let mut br = FileReader::new_read(&mut file);
-        let mut dmx = MOVDemuxer::new_macbinary(&mut br);
-        let mut sm = StreamManager::new();
-        let mut si = SeekIndex::new();
-        dmx.open(&mut sm, &mut si).unwrap();
-
-        loop {
-            let pktres = dmx.get_frame(&mut sm);
-            if let Err(e) = pktres {
-                if e == DemuxerError::EOF { break; }
-                panic!("error");
-            }
-            let pkt = pktres.unwrap();
-            println!("Got {}", pkt);
-        }
-    }
-
-    #[test]
-    fn test_resfork_demux() {
-        // sample from The Wonders of Electricity: An Adventure in Safety
-        let mut file = File::open("assets/QT/car.mov").unwrap();
-        let mut br = FileReader::new_read(&mut file);
-        let mut dmx = MOVDemuxer::new_resfork(&mut br);
-        let mut sm = StreamManager::new();
-        let mut si = SeekIndex::new();
-        dmx.open(&mut sm, &mut si).unwrap();
-
-        loop {
-            let pktres = dmx.get_frame(&mut sm);
-            if let Err(e) = pktres {
-                if e == DemuxerError::EOF { break; }
-                panic!("error");
-            }
-            let pkt = pktres.unwrap();
-            println!("Got {}", pkt);
-        }
-    }
-}
diff --git a/nihav-commonfmt/src/demuxers/mov/mod.rs b/nihav-commonfmt/src/demuxers/mov/mod.rs
new file mode 100644 (file)
index 0000000..ad95837
--- /dev/null
@@ -0,0 +1,910 @@
+use nihav_core::demuxers::*;
+use nihav_registry::register::*;
+use nihav_core::compr::deflate::*;
+
+macro_rules! mktag {
+    ($a:expr, $b:expr, $c:expr, $d:expr) => ({
+        (($a as u32) << 24) | (($b as u32) << 16) | (($c as u32) << 8) | ($d as u32)
+    });
+    ($arr:expr) => ({
+        (($arr[0] as u32) << 24) | (($arr[1] as u32) << 16) | (($arr[2] as u32) << 8) | ($arr[3] as u32)
+    });
+}
+
+#[derive(Clone,Copy,Debug,PartialEq)]
+enum DemuxMode {
+    Normal,
+    MacBin,
+    ResFork,
+}
+
+trait Skip64 {
+    fn skip64(&mut self, size: u64) -> ByteIOResult<()>;
+}
+
+impl<T: ?Sized + ByteIO> Skip64 for T {
+    fn skip64(&mut self, size: u64) -> ByteIOResult<()> {
+        if (size as usize as u64) != size {
+            self.seek(SeekFrom::Current(size as i64))?;
+        } else {
+            self.read_skip(size as usize)?;
+        }
+        Ok(())
+    }
+}
+
+fn read_chunk_header(br: &mut dyn ByteIO) -> DemuxerResult<(u32, u64)> {
+    let size            = br.read_u32be()?;
+    let ctype           = br.read_u32be()?;
+    if size == 0 {
+        Ok((ctype, br.left() as u64))
+    } else if size == 1 {
+        let size64      = br.read_u64be()?;
+        validate!(size64 >= 16);
+        Ok((ctype, size64 - 16))
+    } else {
+        validate!(size >= 8);
+        Ok((ctype, (size as u64) - 8))
+    }
+}
+
+fn read_palette(br: &mut dyn ByteIO, size: u64, pal: &mut [u8; 1024]) -> DemuxerResult<u64> {
+    let _seed           = br.read_u32be()?;
+    let flags           = br.read_u16be()?;
+    let palsize         = (br.read_u16be()? as usize) + 1;
+    validate!(palsize <= 256);
+    validate!(flags == 0 || flags == 0x4000 || flags == 0x8000);
+    validate!((palsize as u64) * 8 + 8 <= size);
+    for i in 0..palsize {
+        let a           = br.read_u16be()?;
+        let r           = br.read_u16be()?;
+        let g           = br.read_u16be()?;
+        let b           = br.read_u16be()?;
+        pal[i * 4]     = (r >> 8) as u8;
+        pal[i * 4 + 1] = (g >> 8) as u8;
+        pal[i * 4 + 2] = (b >> 8) as u8;
+        if flags == 0x8000 {
+            pal[i * 4 + 3] = (a >> 8) as u8;
+        }
+    }
+    if flags == 0x4000 {
+        br.read_skip(8)?;
+    }
+    Ok(size)
+}
+
+struct RootChunkHandler {
+    ctype:  u32,
+    parse:  fn(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64>,
+}
+
+const IGNORED_CHUNKS: &[u32] = &[
+    mktag!(b"free"), mktag!(b"skip"), mktag!(b"udta"), mktag!(b"wide")
+];
+
+const ROOT_CHUNK_HANDLERS: &[RootChunkHandler] = &[
+    RootChunkHandler { ctype: mktag!(b"ftyp"), parse: read_ftyp },
+    RootChunkHandler { ctype: mktag!(b"mdat"), parse: read_mdat },
+    RootChunkHandler { ctype: mktag!(b"moov"), parse: read_moov },
+    RootChunkHandler { ctype: mktag!(b"moof"), parse: read_moof },
+    RootChunkHandler { ctype: mktag!(b"sidx"), parse: read_sidx },
+];
+
+fn print_cname(ctype: u32, size: u64, off: u64, depth: u8) {
+    for _ in 0..depth { print!("    "); }
+    let tag = [(ctype >> 24) as u8, (ctype >> 16) as u8, (ctype >> 8) as u8, ctype as u8];
+    let mut printable = true;
+    for ch in tag.iter() {
+        if !(0x20..=0x7F).contains(ch) {
+            printable = false;
+            break;
+        }
+    }
+    if printable {
+        print!(" '{}{}{}{}'", tag[0] as char, tag[1] as char, tag[2] as char, tag[3] as char);
+    } else {
+        print!(" {:08X}", ctype);
+    }
+    println!(" size {} @ {:X}", size, off);
+}
+
+macro_rules! read_chunk_list {
+    (root; $name: expr, $fname: ident, $handlers: ident) => {
+        fn $fname(&mut self, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<()> {
+            self.depth += 1;
+            validate!(self.depth < 32);
+            let list_end = self.src.tell() + size;
+            while self.src.tell() < list_end {
+                let ret = read_chunk_header(self.src);
+                if ret.is_err() { break; }
+                let (ctype, size) = ret.unwrap();
+                if self.print_chunks {
+                    print_cname(ctype, size, self.src.tell(), self.depth as u8);
+                }
+                if self.src.tell() + size > list_end {
+                    break;
+                }
+                if IGNORED_CHUNKS.contains(&ctype) {
+                    self.src.skip64(size)?;
+                    continue;
+                }
+                let handler = $handlers.iter().find(|x| x.ctype == ctype);
+                let read_size;
+                if let Some(ref handler) = handler {
+                    read_size = (handler.parse)(self, strmgr, size)?;
+                } else {
+                    println!("skipping unknown chunk {:08X} size {}", ctype, size);
+                    read_size = 0;
+                }
+                validate!(read_size <= size);
+                self.src.skip64(size - read_size)?;
+            }
+            self.depth -= 1;
+            validate!(self.src.tell() == list_end);
+            Ok(())
+        }
+    };
+    (track; $name: expr, $fname: ident, $handlers: ident) => {
+        fn $fname(&mut self, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<()> {
+            self.depth += 1;
+            validate!(self.depth < 32);
+            let list_end = br.tell() + size;
+            while br.tell() < list_end {
+                let ret = read_chunk_header(br);
+                if ret.is_err() { break; }
+                let (ctype, size) = ret.unwrap();
+                if self.print_chunks {
+                    print_cname(ctype, size, br.tell(), self.depth + 1);
+                }
+                if br.tell() + size > list_end {
+                    break;
+                }
+                if IGNORED_CHUNKS.contains(&ctype) {
+                    br.skip64(size)?;
+                    continue;
+                }
+                let handler = $handlers.iter().find(|x| x.ctype == ctype);
+                let read_size;
+                if let Some(ref handler) = handler {
+                    read_size = (handler.parse)(self, br, size)?;
+                } else {
+                    read_size = 0;
+                }
+                validate!(read_size <= size);
+                br.skip64(size - read_size)?;
+            }
+            self.depth -= 1;
+            validate!(br.tell() == list_end);
+            Ok(())
+        }
+    }
+}
+
+mod track;
+use track::*;
+mod pktread;
+
+fn read_ftyp(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    dmx.src.skip64(size)?;
+    Ok(size)
+}
+
+fn read_mdat(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    dmx.mdat_pos  = dmx.src.tell();
+    dmx.mdat_size = size;
+    dmx.src.skip64(size)?;
+    Ok(size)
+}
+
+fn read_sidx(_dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, _size: u64) -> DemuxerResult<u64> {
+    Ok(0)
+}
+
+fn read_moov(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    dmx.read_moov(strmgr, size)?;
+    Ok(size)
+}
+
+const MOOV_CHUNK_HANDLERS: &[RootChunkHandler] = &[
+    RootChunkHandler { ctype: mktag!(b"mvhd"), parse: read_mvhd },
+    RootChunkHandler { ctype: mktag!(b"cmov"), parse: read_cmov },
+    RootChunkHandler { ctype: mktag!(b"ctab"), parse: read_ctab },
+    RootChunkHandler { ctype: mktag!(b"trak"), parse: read_trak },
+    RootChunkHandler { ctype: mktag!(b"meta"), parse: read_meta },
+    RootChunkHandler { ctype: mktag!(b"mvex"), parse: read_mvex },
+    RootChunkHandler { ctype: mktag!(b"iods"), parse: skip_chunk_mov },
+];
+
+fn read_mvhd(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_MVHD_SIZE: u64 = 100;
+    let br = &mut dmx.src;
+    validate!(size >= KNOWN_MVHD_SIZE);
+    let version             = br.read_byte()?;
+    validate!(version == 0 || version == 0xFF);
+    dmx.ver_m1 = version == 0xFF;
+    let _flags              = br.read_u24be()?;
+    let _ctime              = br.read_u32be()?;
+    let _mtime              = br.read_u32be()?;
+    let tscale              = br.read_u32be()?;
+    let duration            = br.read_u32be()?;
+    let _pref_rate          = br.read_u32be()?;
+    let _pref_volume        = br.read_u16be()?;
+                              br.read_skip(10)?;
+                              br.read_skip(36)?; // matrix
+    let _preview_time       = br.read_u32be()?;
+    let _preview_duration   = br.read_u32be()?;
+    let _poster_time        = br.read_u32be()?;
+    let _sel_time           = br.read_u32be()?;
+    let _sel_duration       = br.read_u32be()?;
+    let _cur_time           = br.read_u32be()?;
+    let _next_track_id      = br.read_u32be()?;
+    dmx.duration = duration;
+    dmx.tb_den = tscale;
+
+    Ok(KNOWN_MVHD_SIZE)
+}
+
+fn read_cmov(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    let br = &mut dmx.src;
+    validate!(size > 24);
+    let dcom_size           = br.read_u32be()?;
+    let dcom_tag            = br.read_tag()?;
+    let compr_type          = br.read_tag()?;
+    validate!(&dcom_tag == b"dcom" && dcom_size == 12);
+    if &compr_type != b"zlib" {
+        return Err(DemuxerError::NotImplemented);
+    }
+    let cmvd_size           = u64::from(br.read_u32be()?);
+    let cmvd_tag            = br.read_tag()?;
+    validate!(&cmvd_tag == b"cmvd" && cmvd_size > 14 && cmvd_size == size - 12);
+    let comp_size = (cmvd_size - 12) as usize;
+    let uncomp_size         = br.read_u32be()? as usize;
+    validate!(uncomp_size > 8);
+    let mut sbuf = vec![0; comp_size];
+    let mut dbuf = vec![0; uncomp_size];
+                              br.read_buf(sbuf.as_mut_slice())?;
+    let ret = Inflate::uncompress(sbuf.as_slice(), dbuf.as_mut_slice());
+    if ret.is_err() {
+        return Err(DemuxerError::InvalidData);
+    }
+    let len = ret.unwrap();
+    validate!(len == uncomp_size);
+    let mut br = MemoryReader::new_read(dbuf.as_slice());
+    let (ctype, csize) = read_chunk_header(&mut br)?;
+    validate!(ctype == mktag!(b"moov"));
+    let mut ddmx = MOVDemuxer::new(&mut br);
+    ddmx.print_chunks = dmx.print_chunks;
+    ddmx.read_moov(strmgr, csize)?;
+    std::mem::swap(&mut dmx.tracks, &mut ddmx.tracks);
+    dmx.duration = ddmx.duration;
+    dmx.tb_den = ddmx.tb_den;
+    std::mem::swap(&mut dmx.pal, &mut ddmx.pal);
+
+    Ok(size)
+}
+
+fn read_ctab(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    let mut pal = [0; 1024];
+    let size = read_palette(dmx.src, size, &mut pal)?;
+    dmx.pal = Some(Arc::new(pal));
+    Ok(size)
+}
+
+fn read_meta(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    dmx.src.skip64(size)?;
+    Ok(size)
+}
+
+fn read_mvex(_dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, _size: u64) -> DemuxerResult<u64> {
+    Ok(0)
+}
+
+fn skip_chunk_mov(_dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, _size: u64) -> DemuxerResult<u64> {
+    Ok(0)
+}
+
+fn read_trak(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    let mut track = Track::new(dmx.cur_track as u32, dmx.tb_den);
+    track.print_chunks = dmx.print_chunks;
+    track.ver_m1 = dmx.ver_m1;
+    track.call_read_trak(dmx.src, size)?;
+    validate!(track.tkhd_found);
+    if !track.stsd_found {
+        track.stream = Some(NAStream::new(StreamType::Data, track.track_no, DUMMY_CODEC_INFO, track.tb_num, track.tb_den, 0));
+    }
+    // invent keyframes for video stream if none were reported
+    if !track.stss_found && track.stream_type == StreamType::Video {
+        let mut intraonly = false;
+        if let Some(ref stream) = track.stream {
+            if let Some(desc) = get_codec_description(stream.get_info().get_name()) {
+                intraonly = (desc.caps & CODEC_CAP_INTRAONLY) != 0;
+            }
+        }
+
+        track.pkt_demux.invent_keyframes(intraonly);
+    }
+    validate!(strmgr.get_stream_by_id(track.track_id).is_none());
+    dmx.cur_track += 1;
+    dmx.tracks.push(track);
+    Ok(size)
+}
+
+fn read_moof(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    dmx.moof_off = dmx.src.tell() - 8;
+    dmx.read_moof(strmgr, size)?;
+    Ok(size)
+}
+
+const MOOF_CHUNK_HANDLERS: &[RootChunkHandler] = &[
+    RootChunkHandler { ctype: mktag!(b"mfhd"), parse: read_mfhd },
+    RootChunkHandler { ctype: mktag!(b"traf"), parse: read_traf },
+    RootChunkHandler { ctype: mktag!(b"meta"), parse: read_meta },
+];
+
+fn read_mfhd(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_MFHD_SIZE: u64 = 8;
+    validate!(size >= KNOWN_MFHD_SIZE);
+    let version             = dmx.src.read_byte()?;
+    validate!(version == 0);
+    let flags               = dmx.src.read_u24be()?;
+    validate!(flags == 0);
+    let _seq_no             = dmx.src.read_u32be()?;
+
+    Ok(KNOWN_MFHD_SIZE)
+}
+
+fn read_traf(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult<u64> {
+    let mut buf = [0u8; 16];
+                              dmx.src.peek_buf(&mut buf)?;
+    validate!(&buf[4..8] == b"tfhd");
+    let track_id = read_u32be(&buf[12..16])?;
+    let mut track = None;
+    for trk in dmx.tracks.iter_mut() {
+        if trk.track_id == track_id {
+            track = Some(trk);
+            break;
+        }
+    }
+    if let Some(track) = track {
+        track.moof_off = dmx.moof_off;
+        track.call_read_traf(dmx.src, size)?;
+        Ok(size)
+    } else {
+        Ok(0)
+    }
+}
+
+struct MOVDemuxer<'a> {
+    src:            &'a mut dyn ByteIO,
+    depth:          usize,
+    mdat_pos:       u64,
+    mdat_size:      u64,
+    tracks:         Vec<Track>,
+    cur_track:      usize,
+    tb_den:         u32,
+    duration:       u32,
+    pal:            Option<Arc<[u8; 1024]>>,
+    ver_m1:         bool,
+
+    moof_off:       u64,
+
+    print_chunks:   bool,
+
+    demux_mode:     DemuxMode,
+
+    force_seek:     bool,
+}
+
+impl<'a> DemuxCore<'a> for MOVDemuxer<'a> {
+    fn open(&mut self, strmgr: &mut StreamManager, seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let mut data_start = 0;
+        match self.demux_mode {
+            DemuxMode::Normal => self.read_root(strmgr)?,
+            DemuxMode::MacBin => {
+                let ver                     = self.src.read_byte()?;
+                validate!(ver == 0);
+                                              self.src.read_skip(64)?;
+                let tag                     = self.src.read_tag()?;
+                validate!(&tag == b"MooV");
+                                              self.src.read_skip(14)?;
+                let data_length             = self.src.read_u32be()?;
+                validate!(data_length > 8);
+                let rsrc_length             = self.src.read_u32be()?;
+                validate!(rsrc_length > 0);
+                                              self.src.read_skip(31)?;
+                let ver                     = self.src.read_byte()?;
+                validate!(ver == 0x81);
+                let ver                     = self.src.read_byte()?;
+                validate!(ver == 0x81);
+                //xxx: maybe check header CRC
+
+                let rsrc_start = 0x80 + ((data_length + 0x7F) & !0x7F);
+                                              self.src.seek(SeekFrom::Start(rsrc_start.into()))?;
+                let rsrc_off                = self.src.read_u32be()?;
+                let rsrc_map_off            = self.src.read_u32be()?;
+                let rsrc_size               = self.src.read_u32be()?;
+                let _rsrc_map_size          = self.src.read_u32be()?;
+                validate!(rsrc_off >= 0x10);
+                validate!(rsrc_map_off >= rsrc_off + rsrc_size);
+                                              self.src.seek(SeekFrom::Current(i64::from(rsrc_off - 16)))?;
+                // I'm too lazy to parse resource map, so let's just iterate over resources for movie header
+                let end_pos = u64::from(rsrc_start + rsrc_off + rsrc_size);
+                let mut peek_buf = [0u8; 8];
+                while self.src.tell() < end_pos {
+                    let cur_size            = self.src.read_u32be()?;
+                    validate!(self.src.tell() + u64::from(cur_size) <= end_pos);
+                    if cur_size > 8 {
+                        let rsize           = self.src.peek_u32be()?;
+                        if rsize == cur_size {
+                                              self.src.peek_buf(&mut peek_buf)?;
+                            if &peek_buf[4..] == b"moov" {
+                                              self.src.read_skip(8)?;
+                                              self.src.peek_buf(&mut peek_buf)?;
+                                if peek_buf == [0; 8] {
+                                    println!("This looks like an alpha version of the format, try warhol demuxer instead");
+                                    return Err(DemuxerError::InvalidData);
+                                }
+                                self.read_moov(strmgr, u64::from(rsize - 8))?;
+                                self.mdat_pos = 8;
+                                break;
+                            }
+                        }
+                    }
+                                              self.src.read_skip(cur_size as usize)?;
+                }
+                // if moov/mdat is not found, it may be in data part
+                if self.tracks.is_empty() || self.mdat_pos == 0 {
+                                              self.src.seek(SeekFrom::Start(0x80))?;
+                    let end_pos = u64::from(data_length) + 0x80;
+                    while self.src.tell() < end_pos && (self.mdat_pos == 0 || self.tracks.is_empty()) {
+                        let size            = self.src.read_u32be()?;
+                        let tag             = self.src.read_tag()?;
+                        validate!(size >= 8);
+                        match &tag {
+                            b"moov" => {
+                                              self.src.peek_buf(&mut peek_buf)?;
+                                if peek_buf == [0; 8] {
+                                    println!("This looks like an alpha version of the format, try warhol demuxer instead");
+                                    return Err(DemuxerError::InvalidData);
+                                }
+                                self.read_moov(strmgr, u64::from(size - 8))?;
+                            },
+                            b"mdat" => {
+                                self.mdat_pos = self.src.tell() - 0x80;
+                                              self.src.read_skip(size as usize - 8)?;
+                            },
+                            _ => {
+                                              self.src.read_skip(size as usize - 8)?;
+                            },
+                        }
+                    }
+                }
+            },
+            DemuxMode::ResFork => {
+                let res_data_offset         = self.src.read_u32be()?;
+                let res_map_offset          = self.src.read_u32be()?;
+                let res_map_data_len        = self.src.read_u32be()?;
+                let res_map_length          = self.src.read_u32be()?;
+                validate!(res_data_offset == 0x100 && res_map_length > 16);
+                                              self.src.seek(SeekFrom::Start(u64::from(res_map_offset)))?;
+                let res_data_offset2        = self.src.read_u32be()?;
+                let res_map_offset2         = self.src.read_u32be()?;
+                let res_map_data_len2       = self.src.read_u32be()?;
+                let res_map_length2         = self.src.read_u32be()?;
+                validate!(res_data_offset == res_data_offset2
+                    && res_map_offset == res_map_offset2
+                    && res_map_data_len == res_map_data_len2
+                    && res_map_length == res_map_length2);
+                                              self.src.read_skip(res_map_length as usize - 16)?;
+                data_start = self.src.tell();
+                self.read_root(strmgr)?;
+            },
+        };
+        validate!(self.mdat_pos > 0);
+        validate!(!self.tracks.is_empty());
+        for track in self.tracks.iter_mut() {
+            let mut strm = None;
+            std::mem::swap(&mut track.stream, &mut strm);
+            if let Some(stream) = strm {
+                let str_id = strmgr.add_stream(stream).unwrap();
+                track.track_str_id = str_id;
+            }
+        }
+        match self.demux_mode {
+            DemuxMode::MacBin => {
+                // patch data offsets
+                for track in self.tracks.iter_mut() {
+                    for offset in track.pkt_demux.chunk_offsets.iter_mut() {
+                        *offset += 0x80;
+                    }
+                }
+            },
+            DemuxMode::ResFork => {
+                // patch data offsets
+                for track in self.tracks.iter_mut() {
+                    for offset in track.pkt_demux.chunk_offsets.iter_mut() {
+                        *offset += data_start;
+                    }
+                }
+            },
+            _ => {},
+        }
+        for track in self.tracks.iter() {
+            track.pkt_demux.fill_seek_index(track.track_no, seek_index);
+        }
+        self.src.seek(SeekFrom::Start(self.mdat_pos))?;
+        self.cur_track = 0;
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        if self.tracks.is_empty() {
+            return Err(DemuxerError::EOF);
+        }
+        let mut has_all_time = true;
+        let mut min_ts = u64::MAX;
+        for (trk_no, trk) in self.tracks.iter().enumerate() {
+            if strmgr.is_ignored(trk_no) {
+                continue;
+            }
+            if let Some(ts) = trk.pkt_demux.cur_ts {
+                min_ts = min_ts.min(ts);
+            } else {
+                has_all_time = false;
+                break;
+            }
+        }
+        if has_all_time {
+            for (trk_no, track) in self.tracks.iter_mut().enumerate() {
+                if let Some(ts) = track.pkt_demux.cur_ts {
+                    if ts == min_ts {
+                        let first = track.pkt_demux.is_first();
+                        if let Some((cts, offset, size, is_kf)) = track.pkt_demux.get_next_chunk() {
+                            self.cur_track = trk_no + 1;
+                            return process_packet(self.src, strmgr, track, cts, offset, size, first, is_kf);
+                        }
+                    }
+                }
+            }
+        }
+
+        for _ in 0..self.tracks.len() {
+            if self.cur_track >= self.tracks.len() {
+                self.cur_track = 0;
+            }
+            let track = &mut self.tracks[self.cur_track];
+            self.cur_track += 1;
+            if strmgr.is_ignored(self.cur_track - 1) {
+                continue;
+            }
+            let first = track.pkt_demux.is_first();
+            if let Some((cts, offset, size, is_kf)) = track.pkt_demux.get_next_chunk() {
+                return process_packet(self.src, strmgr, track, cts, offset, size, first, is_kf);
+            }
+        }
+        Err(DemuxerError::EOF)
+    }
+
+    fn seek(&mut self, time: NATimePoint, seek_index: &SeekIndex) -> DemuxerResult<()> {
+        let ret = seek_index.find_pos(time);
+        if ret.is_none() {
+            if let NATimePoint::Milliseconds(_) = time {
+                let mut aonly = true;
+                for track in self.tracks.iter() {
+                    if track.stream_type != StreamType::Audio {
+                        aonly = false;
+                        break;
+                    }
+                }
+                if aonly {
+                    for track in self.tracks.iter_mut() {
+                        track.pkt_demux.seek(0, time, false)?;
+                    }
+                    return Ok(());
+                }
+            }
+            if self.force_seek {
+                let mut ret = Ok(());
+                for track in self.tracks.iter_mut() {
+                    if let Err(err) = track.pkt_demux.seek(0, time, true) {
+                        ret = Err(err);
+                        break;
+                    }
+                }
+                if ret.is_err() {
+                    for track in self.tracks.iter_mut() {
+                        track.pkt_demux.reset();
+                    }
+                }
+                return ret;
+            }
+            return Err(DemuxerError::SeekError);
+        }
+        let seek_info = ret.unwrap();
+        let tbn = self.tracks[seek_info.str_id as usize].tb_num;
+        let tbd = self.tracks[seek_info.str_id as usize].tb_den;
+        let mut vpts = None;
+        let mut apts = None;
+        for track in self.tracks.iter_mut() {
+            let cur_pts = if track.track_id == seek_info.str_id {
+                    seek_info.pts
+                } else {
+                    seek_info.pts * u64::from(tbn) * u64::from(track.tb_den) / (u64::from(tbd) * u64::from(track.tb_num))
+                };
+            let actual_time = track.pkt_demux.seek(cur_pts, time, false)?;
+            match track.stream_type {
+                StreamType::Video => vpts = Some(actual_time),
+                StreamType::Audio => apts = Some(actual_time),
+                _ => {},
+            };
+        }
+        /* For audio+video stream case when the post-seek actual times differ
+           by more than half a second try to seek audio to a closer position
+           to video.
+        */
+        if let (Some(vtime), Some(atime)) = (vpts, apts) {
+            let acount = self.tracks.iter().filter(|trk| trk.stream_type == StreamType::Audio).count();
+            let vcount = self.tracks.iter().filter(|trk| trk.stream_type == StreamType::Video).count();
+            if (vcount == 1 && acount == 1) && (vtime.max(atime) - vtime.min(atime) > 500) && (atime != 0) {
+                for track in self.tracks.iter_mut() {
+                    if track.stream_type == StreamType::Audio {
+                        let new_pts = NATimeInfo::rescale_ts(vtime, 1, 1000, track.tb_num, track.tb_den);
+                        track.pkt_demux.seek(new_pts, NATimePoint::Milliseconds(vtime), false)?;
+                    }
+                }
+            }
+        }
+
+        Ok(())
+    }
+    fn get_duration(&self) -> u64 {
+        if self.tb_den != 0 {
+            u64::from(self.duration) * 1000 / u64::from(self.tb_den)
+        } else {
+            0
+        }
+    }
+}
+
+const PRINT_CHUNKS: &str = "print_chunks";
+
+const DEMUXER_OPTIONS: &[NAOptionDefinition] = &[
+    NAOptionDefinition {
+        name:           PRINT_CHUNKS,
+        description:    "Print parsed file structure",
+        opt_type:       NAOptionDefinitionType::Bool },
+    NAOptionDefinition {
+        name:           FORCE_SEEK_OPTION,
+        description:    FORCE_SEEK_OPTION_DESC,
+        opt_type:       NAOptionDefinitionType::Bool },
+];
+
+impl<'a> NAOptionHandler for MOVDemuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { DEMUXER_OPTIONS }
+    #[allow(clippy::single_match)]
+    fn set_options(&mut self, options: &[NAOption]) {
+        for option in options.iter() {
+            for opt_def in DEMUXER_OPTIONS.iter() {
+                if opt_def.check(option).is_ok() {
+                    match (option.name, &option.value) {
+                        (PRINT_CHUNKS, NAValue::Bool(val)) => {
+                            self.print_chunks = *val;
+                        },
+                        (FORCE_SEEK_OPTION, NAValue::Bool(val)) => {
+                            self.force_seek = *val;
+                        },
+                        _ => {},
+                    }
+                }
+            }
+        }
+    }
+    fn query_option_value(&self, name: &str) -> Option<NAValue> {
+        match name {
+            PRINT_CHUNKS    => Some(NAValue::Bool(self.print_chunks)),
+            FORCE_SEEK_OPTION => Some(NAValue::Bool(self.force_seek)),
+            _ => None,
+        }
+    }
+}
+
+impl<'a> MOVDemuxer<'a> {
+    fn new(io: &'a mut dyn ByteIO) -> Self {
+        Self::new_common(io, DemuxMode::Normal)
+    }
+    fn new_macbinary(io: &'a mut dyn ByteIO) -> Self {
+        Self::new_common(io, DemuxMode::MacBin)
+    }
+    fn new_resfork(io: &'a mut dyn ByteIO) -> Self {
+        Self::new_common(io, DemuxMode::ResFork)
+    }
+    fn new_common(io: &'a mut dyn ByteIO, demux_mode: DemuxMode,) -> Self {
+        MOVDemuxer {
+            src:            io,
+            depth:          0,
+            mdat_pos:       0,
+            mdat_size:      0,
+            tracks:         Vec::with_capacity(2),
+            cur_track:      0,
+            tb_den:         0,
+            duration:       0,
+            pal:            None,
+            ver_m1:         false,
+
+            moof_off:       0,
+
+            print_chunks:   false,
+
+            demux_mode,
+            force_seek:     false,
+        }
+    }
+    fn read_root(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> {
+        self.depth = 0;
+        while self.src.left() != 0 {
+            let ret = read_chunk_header(self.src);
+            if ret.is_err() { break; }
+            let (ctype, size) = ret.unwrap();
+            if self.print_chunks {
+                print_cname(ctype, size, self.src.tell(), 0);
+            }
+            if IGNORED_CHUNKS.contains(&ctype) {
+                self.src.skip64(size)?;
+                continue;
+            }
+            let handler = ROOT_CHUNK_HANDLERS.iter().find(|x| x.ctype == ctype);
+            let read_size;
+            if let Some(handler) = handler {
+                read_size = (handler.parse)(self, strmgr, size)?;
+            } else {
+                println!("skipping unknown chunk {:08X} size {}", ctype, size);
+                read_size = 0;
+            }
+            validate!(read_size <= size);
+            self.src.skip64(size - read_size)?;
+        }
+//todo check if all needed chunks are found
+        Ok(())
+    }
+    read_chunk_list!(root; "moov", read_moov, MOOV_CHUNK_HANDLERS);
+    read_chunk_list!(root; "moof", read_moof, MOOF_CHUNK_HANDLERS);
+}
+
+pub struct MOVDemuxerCreator { }
+
+impl DemuxerCreator for MOVDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut dyn ByteIO) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(MOVDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "mov" }
+}
+
+pub struct MacBinaryMOVDemuxerCreator { }
+
+impl DemuxerCreator for MacBinaryMOVDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut dyn ByteIO) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(MOVDemuxer::new_macbinary(br))
+    }
+    fn get_name(&self) -> &'static str { "mov-macbin" }
+}
+
+pub struct MacResForkMOVDemuxerCreator { }
+
+impl DemuxerCreator for MacResForkMOVDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut dyn ByteIO) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(MOVDemuxer::new_resfork(br))
+    }
+    fn get_name(&self) -> &'static str { "mov-resfork" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn test_mov_demux() {
+        // sample: https://samples.mplayerhq.hu/V-codecs/IV32/cubes.mov
+        let mut file = File::open("assets/Indeo/cubes.mov").unwrap();
+        let mut br = FileReader::new_read(&mut file);
+        let mut dmx = MOVDemuxer::new(&mut br);
+        let mut sm = StreamManager::new();
+        let mut si = SeekIndex::new();
+        dmx.open(&mut sm, &mut si).unwrap();
+
+        loop {
+            let pktres = dmx.get_frame(&mut sm);
+            if let Err(e) = pktres {
+                if e == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+
+    #[test]
+    fn test_dash_demux() {
+        // sample: a stream downloaded with youtube-dl
+        let mut file = File::open("assets/ITU/dash.m4a").unwrap();
+        let mut br = FileReader::new_read(&mut file);
+        let mut dmx = MOVDemuxer::new(&mut br);
+        let mut sm = StreamManager::new();
+        let mut si = SeekIndex::new();
+        dmx.open(&mut sm, &mut si).unwrap();
+
+        loop {
+            let pktres = dmx.get_frame(&mut sm);
+            if let Err(e) = pktres {
+                if e == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+
+    #[test]
+    fn test_macbinary_demux() {
+        // sample from King's Quest VI Macintosh edition
+        let mut file = File::open("assets/QT/Halfdome.bin").unwrap();
+        let mut br = FileReader::new_read(&mut file);
+        let mut dmx = MOVDemuxer::new_macbinary(&mut br);
+        let mut sm = StreamManager::new();
+        let mut si = SeekIndex::new();
+        dmx.open(&mut sm, &mut si).unwrap();
+
+        loop {
+            let pktres = dmx.get_frame(&mut sm);
+            if let Err(e) = pktres {
+                if e == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+
+
+    #[test]
+    fn test_beta_qt() {
+        // sample from Apple Reference & Presentation Library 8
+        let mut file = File::open("assets/QT/Smallwhale2").unwrap();
+        let mut br = FileReader::new_read(&mut file);
+        let mut dmx = MOVDemuxer::new_macbinary(&mut br);
+        let mut sm = StreamManager::new();
+        let mut si = SeekIndex::new();
+        dmx.open(&mut sm, &mut si).unwrap();
+
+        loop {
+            let pktres = dmx.get_frame(&mut sm);
+            if let Err(e) = pktres {
+                if e == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+
+    #[test]
+    fn test_resfork_demux() {
+        // sample from The Wonders of Electricity: An Adventure in Safety
+        let mut file = File::open("assets/QT/car.mov").unwrap();
+        let mut br = FileReader::new_read(&mut file);
+        let mut dmx = MOVDemuxer::new_resfork(&mut br);
+        let mut sm = StreamManager::new();
+        let mut si = SeekIndex::new();
+        dmx.open(&mut sm, &mut si).unwrap();
+
+        loop {
+            let pktres = dmx.get_frame(&mut sm);
+            if let Err(e) = pktres {
+                if e == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+}
diff --git a/nihav-commonfmt/src/demuxers/mov/pktread.rs b/nihav-commonfmt/src/demuxers/mov/pktread.rs
new file mode 100644 (file)
index 0000000..4d656f3
--- /dev/null
@@ -0,0 +1,483 @@
+use nihav_core::demuxers::*;
+
+#[derive(Default)]
+pub struct TimeSearcher {
+    pub idx:        usize,
+    pub base:       u64,
+    pub sbase:      u32,
+    pub cur_len:    u32,
+    pub cur_mul:    u32,
+}
+
+impl TimeSearcher {
+    pub fn new() -> Self { Self::default() }
+    pub fn reset(&mut self) {
+        *self = Self::default();
+    }
+    pub fn map_time(&mut self, sample: u32, tts: &[(u32, u32)]) -> u64 {
+        if tts.is_empty() {
+            u64::from(sample)
+        } else if sample >= self.sbase {
+            let mut sample = sample - self.sbase;
+            if self.idx == 0 {
+                let (cur_len, cur_mul) = tts[0];
+                self.cur_len = cur_len;
+                self.cur_mul = cur_mul;
+                self.idx += 1;
+            }
+            while self.idx < tts.len() && sample > self.cur_len {
+                sample -= self.cur_len;
+                self.sbase += self.cur_len;
+                self.base += u64::from(self.cur_len) * u64::from(self.cur_mul);
+                self.cur_len = tts[self.idx].0;
+                self.cur_mul = tts[self.idx].1;
+                self.idx += 1;
+            }
+            self.base + u64::from(sample) * u64::from(self.cur_mul)
+        } else {
+            self.reset();
+            self.map_time(sample, tts)
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone,Copy,Default)]
+pub struct RLEPair<T>(pub u32, pub T);
+
+#[derive(Default)]
+pub struct RLESearcher<T> {
+    pub array:      Vec<RLEPair<T>>,
+    pub idx:        usize,
+    pub start:      u64,
+    pub next:       u64,
+}
+
+impl<T:Default+Copy> RLESearcher<T> {
+    //pub fn new() -> Self { Self::default() }
+    pub fn resize(&mut self, size: usize) {
+        self.array.clear();
+        self.array.reserve(size);
+    }
+    pub fn reserve(&mut self, size: usize) {
+        self.array.reserve(size);
+    }
+    pub fn add(&mut self, len: u32, val: T) {
+        self.array.push(RLEPair(len, val));
+    }
+    pub fn reset(&mut self) {
+        self.start = 0;
+        if !self.array.is_empty() {
+            self.idx = 0;
+            self.next = u64::from(self.array[0].0);
+        } else {
+            self.idx = self.array.len();
+            self.next = 0;
+        }
+    }
+    pub fn map(&mut self, sample: u64) -> Option<T> {
+        if sample < self.start {
+            self.reset();
+        }
+        if self.idx < self.array.len() {
+            if sample < self.next {
+                Some(self.array[self.idx].1)
+            } else {
+                while (self.idx < self.array.len()) && (sample >= self.next) {
+                    self.start = self.next;
+                    self.idx += 1;
+                    if self.idx < self.array.len() {
+                        self.next += u64::from(self.array[self.idx].0);
+                    }
+                }
+                if self.idx < self.array.len() {
+                    Some(self.array[self.idx].1)
+                } else {
+                    None
+                }
+            }
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(Default)]
+pub struct QTPacketDemuxer {
+    pub stream_type:    StreamType,
+
+    pub tb_num:         u32,
+    pub tb_den:         u32,
+    pub cur_chunk:      usize,
+    pub cur_sample:     usize,
+    pub cur_ts:         Option<u64>,
+    pub samples_left:   usize,
+    pub last_offset:    u64,
+    pub raw_audio:      bool,
+    pub raw_apos:       u64,
+    pub duration:       u32,
+    pub channels:       usize,
+    pub bits:           usize,
+    pub bsize:          usize,
+    pub fcc:            [u8; 4],
+
+    pub keyframes:      Vec<u32>,
+    pub chunk_sizes:    Vec<u32>,
+    pub chunk_offsets:  Vec<u64>,
+    pub time_to_sample: Vec<(u32, u32)>,
+    pub sample_map:     Vec<(u32, u32)>,
+    pub sample_size:    u32,
+    pub frame_samples:  usize,
+    pub ctts_map:       RLESearcher<u32>,
+    pub ctts_version:   u8,
+    pub timesearch:     TimeSearcher,
+}
+
+impl QTPacketDemuxer {
+    pub fn new() -> Self { Self::default() }
+    pub fn is_first(&self) -> bool { self.cur_sample == 0 }
+    pub fn fill_seek_index(&self, track_no: u32, seek_index: &mut SeekIndex) {
+        if !self.keyframes.is_empty() {
+            seek_index.mode = SeekIndexMode::Present;
+        }
+        let mut tsearch = TimeSearcher::new();
+        for kf_time in self.keyframes.iter() {
+            let pts = tsearch.map_time(*kf_time - 1, &self.time_to_sample);
+            let time = NATimeInfo::rescale_ts(pts, self.tb_num, self.tb_den, 1, 1000);
+            seek_index.add_entry(track_no, SeekEntry { time, pts: u64::from(*kf_time - 1), pos: 0 });
+        }
+    }
+    pub fn invent_keyframes(&mut self, intraonly: bool) {
+        self.keyframes.reserve(self.time_to_sample.len());
+        if !self.time_to_sample.is_empty() {
+            let mut abs_csamp = 1u64;
+            for &(count, _scount) in self.time_to_sample.iter() {
+                let count = u64::from(count);
+                self.keyframes.push(abs_csamp as u32);
+                abs_csamp += count;
+            }
+        } else if intraonly && !self.chunk_sizes.is_empty() {
+            for samp in 0..self.chunk_sizes.len() {
+                self.keyframes.push((samp + 1) as u32);
+            }
+        }
+    }
+    fn calculate_chunk_size(&self, nsamp: usize) -> usize {
+        if nsamp == 0 {
+            self.bsize
+        } else {
+            match &self.fcc {
+                b"NONE" | b"raw " | b"twos" | b"sowt" | &[0, 0, 0, 0] => {
+                    (nsamp * self.bits * self.channels + 7) >> 3
+                },
+                b"ima4" => {
+                    let nblocks = (nsamp + 63) >> 6;
+                    nblocks * 34 * self.channels
+                },
+                b"MAC3" => {
+                    (nsamp + 5) / 6 * 2 * self.channels
+                },
+                b"MAC6" => {
+                    (nsamp + 5) / 6 * self.channels
+                },
+                b"in24" => nsamp * 3 * self.channels,
+                b"in32" | b"fl32" => nsamp * 4 * self.channels,
+                b"fl64" => nsamp * 8 * self.channels,
+                b"ulaw" | b"alaw" => nsamp,
+                b"ms\x00\x02" => { //MS ADPCM
+                    ((nsamp - 1) / 2 + 7) * self.channels
+                },
+                b"ms\x00\x11" => { //IMA ADPCM
+                    (nsamp / 2 + 4) * self.channels
+                },
+                _ => self.bsize,
+            }
+        }
+    }
+    pub fn reset(&mut self) {
+        self.cur_chunk = self.chunk_offsets.len();
+        self.samples_left = 0;
+        self.cur_sample = self.chunk_sizes.len();
+    }
+    pub fn get_next_chunk(&mut self) -> Option<(NATimeInfo, u64, usize, bool)> {
+        let dts_val = self.timesearch.map_time(self.cur_sample as u32, &self.time_to_sample);
+        let pts = if let Some(dts_corr) = self.ctts_map.map(self.cur_sample as u64) {
+                let pts = match self.ctts_version {
+                        0 => dts_val.wrapping_add(u64::from(dts_corr)),
+                        1 => dts_val.wrapping_add(i64::from(dts_corr as i32) as u64),
+                        _ => unimplemented!(),
+                    };
+                if (pts as i64) < 0 {
+                    None
+                } else {
+                    Some(pts)
+                }
+            } else {
+                Some(dts_val)
+            };
+        let mut ts = NATimeInfo::new(pts, Some(dts_val), None, self.tb_num, self.tb_den);
+        if self.chunk_offsets.len() == self.chunk_sizes.len() { // simple one-to-one mapping
+            if self.cur_sample >= self.chunk_sizes.len() {
+                return None;
+            }
+            let offset = self.chunk_offsets[self.cur_sample];
+            let size   = self.chunk_sizes[self.cur_sample] as usize;
+            self.cur_sample += 1;
+            let is_kf = self.keyframes.contains(&(self.cur_sample as u32));
+            Some((ts, offset, size, is_kf))
+        } else {
+            let chunk_start = self.samples_left == 0;
+            if self.samples_left == 0 {
+                if self.cur_chunk >= self.chunk_offsets.len() {
+                    return None;
+                }
+                for (idx, samples) in self.sample_map.iter() {
+                    if *idx as usize <= self.cur_chunk + 1 {
+                        self.samples_left = *samples as usize;
+                    } else {
+                        break;
+                    }
+                }
+                self.last_offset = self.chunk_offsets[self.cur_chunk];
+                self.cur_chunk += 1;
+            }
+            let offset = self.last_offset;
+            let size = self.get_size(self.cur_sample);
+            self.last_offset += size as u64;
+            let is_kf = chunk_start && (self.stream_type == StreamType::Audio || self.keyframes.contains(&(self.cur_chunk as u32)));
+            if self.stream_type == StreamType::Video {
+                self.samples_left -= 1;
+            } else if self.frame_samples != 0 && self.bsize != 0 {
+                let nblocks = size / self.bsize;
+                if self.raw_audio {
+                    ts.pts = Some(self.raw_apos);
+                    ts.duration = Some(nblocks as u64);
+                    self.raw_apos += nblocks as u64;
+                }
+                if nblocks > 0 {
+                    let consumed = (nblocks * self.frame_samples).min(self.samples_left);
+                    self.samples_left -= consumed;
+                } else {
+                    self.samples_left = 0;
+                }
+            } else if !self.raw_audio {
+                self.samples_left -= 1;
+            } else {
+                const BLOCK_SAMPLES: usize = 1024 * 6; // should be multiple of 64 and 6 to fit both IMA ADPCM and MACE 6:1 blocks
+                self.last_offset -= size as u64;
+                let samples = self.samples_left.min(BLOCK_SAMPLES);
+                let cur_size = self.calculate_chunk_size(samples);
+                self.cur_sample   += samples;
+                self.samples_left -= samples;
+                self.last_offset += cur_size as u64;
+                return Some((ts, offset, cur_size, true));
+            }
+            self.cur_sample += 1;
+            Some((ts, offset, size, is_kf))
+        }
+    }
+    fn get_size(&self, sample_no: usize) -> usize {
+        if !self.chunk_sizes.is_empty() {
+            self.chunk_sizes[sample_no] as usize
+        } else if self.stream_type != StreamType::Audio && self.sample_map.len() <= 1 && self.bsize > 0 {
+            self.bsize
+        } else if !self.sample_map.is_empty() && self.sample_size == 0 {
+            let mut nsamp = 0;
+            for (idx, samples) in self.sample_map.iter() {
+                if *idx as usize <= self.cur_chunk {
+                    nsamp = *samples;
+                } else {
+                    break;
+                }
+            }
+            self.calculate_chunk_size(nsamp as usize)
+        } else {
+            self.bsize
+        }
+    }
+    pub fn seek(&mut self, pts: u64, tpoint: NATimePoint, forced: bool) -> DemuxerResult<u64> {
+        let tgt_pts = match tpoint {
+            NATimePoint::Milliseconds(ms) => NATimeInfo::rescale_ts(ms, 1, 1000, self.tb_num, self.tb_den),
+            NATimePoint::PTS(ts) => ts,
+            _ => 0,
+        };
+        self.cur_sample = pts as usize;
+        self.samples_left = 0;
+        self.cur_ts = None;
+        if forced && pts.abs_diff(tgt_pts) > 5 * u64::from(self.tb_den) {
+            self.cur_sample = tgt_pts as usize;
+        }
+        if self.stream_type == StreamType::Audio {
+            if let NATimePoint::Milliseconds(ms) = tpoint {
+                let exp_pts = NATimeInfo::rescale_ts(ms, 1, 1000, self.tb_num, self.tb_den);
+                if self.raw_audio {
+                    if self.frame_samples != 0 {
+                        self.raw_apos = exp_pts / (self.frame_samples as u64);
+                        let mut apos = 0;
+                        self.cur_sample = 0;
+                        self.cur_chunk = 0;
+                        let mut cmap = self.sample_map.iter();
+                        let mut cur_samps = 0;
+                        let (mut next_idx, mut next_samples) = cmap.next().unwrap();
+                        loop {
+                            if self.cur_chunk + 1 == next_idx as usize {
+                                self.samples_left = cur_samps;
+                                cur_samps = next_samples as usize;
+                                if let Some((new_idx, new_samples)) = cmap.next() {
+                                    next_idx = *new_idx;
+                                    next_samples = *new_samples;
+                                }
+                            }
+                            self.raw_apos = apos;
+                            apos += (cur_samps / self.frame_samples) as u64;
+                            if apos > exp_pts && (cur_samps == self.frame_samples || apos > exp_pts + 1) {
+                                if self.cur_chunk >= self.chunk_offsets.len() {
+                                    return Err(DemuxerError::SeekError);
+                                }
+                                self.last_offset = self.chunk_offsets[self.cur_chunk];
+                                break;
+                            }
+                            self.cur_chunk += 1;
+                        }
+                        self.samples_left = cur_samps;
+                        self.cur_chunk += 1;
+                    } else {
+                        self.raw_apos = exp_pts;
+                        self.cur_sample = exp_pts as usize;
+                        let mut csamp = 0;
+                        self.cur_chunk = 0;
+                        let mut cmap = self.sample_map.iter();
+                        let mut cur_samps = 0;
+                        let (mut next_idx, mut next_samples) = cmap.next().unwrap();
+                        loop {
+                            if self.cur_chunk + 1 == next_idx as usize {
+                                self.samples_left = cur_samps;
+                                cur_samps = next_samples as usize;
+                                if let Some((new_idx, new_samples)) = cmap.next() {
+                                    next_idx = *new_idx;
+                                    next_samples = *new_samples;
+                                }
+                            }
+                            csamp += cur_samps;
+                            if csamp > self.cur_sample {
+                                if self.cur_chunk >= self.chunk_offsets.len() {
+                                    return Err(DemuxerError::SeekError);
+                                }
+                                self.last_offset = self.chunk_offsets[self.cur_chunk];
+                                break;
+                            }
+                            self.cur_chunk += 1;
+                        }
+                        self.samples_left = csamp - self.cur_sample;
+                        self.cur_chunk += 1;
+                    }
+                } else if self.chunk_offsets.len() == self.chunk_sizes.len() && self.duration != 0 {
+                    let new_sample = (self.chunk_sizes.len() as u64 * exp_pts / u64::from(self.duration)) as usize;
+                    self.cur_sample = new_sample;
+                    self.cur_chunk = self.cur_sample;
+                } else {
+                    if !self.time_to_sample.is_empty() {
+                        let mut remaining = exp_pts;
+                        let mut abs_csamp = 0;
+                        for &(count, scount) in self.time_to_sample.iter() {
+                            let count = u64::from(count);
+                            let scount = u64::from(scount);
+                            let nblk = remaining / scount;
+                            if nblk < count {
+                                abs_csamp += nblk;
+                                break;
+                            }
+                            remaining -= count * scount;
+                            abs_csamp += count;
+                        }
+                        self.cur_sample = abs_csamp as usize;
+                    } else {
+                        self.cur_sample = exp_pts as usize;
+                    }
+                    let tgt_sample = self.cur_sample;
+                    let mut csamp = 0;
+                    self.cur_chunk = 0;
+                    let mut cmap = self.sample_map.iter();
+                    let mut cur_samps = 0;
+                    let (mut next_idx, mut next_samples) = cmap.next().unwrap();
+                    loop {
+                        if self.cur_chunk + 1 == next_idx as usize {
+                            self.samples_left = cur_samps;
+                            cur_samps = next_samples as usize;
+                            if let Some((new_idx, new_samples)) = cmap.next() {
+                                next_idx = *new_idx;
+                                next_samples = *new_samples;
+                            }
+                        }
+                        csamp += cur_samps;
+                        if csamp > self.cur_sample {
+                            if self.cur_chunk >= self.chunk_offsets.len() {
+                                self.cur_sample = csamp - cur_samps;
+                                self.samples_left = 0;
+                                self.cur_sample = csamp;
+                                return Err(DemuxerError::SeekError);
+                            }
+                            self.last_offset = self.chunk_offsets[self.cur_chunk];
+                            break;
+                        }
+                        self.cur_chunk += 1;
+                    }
+                    self.cur_sample = csamp - cur_samps;
+                    self.samples_left = cur_samps;
+                    self.last_offset = self.chunk_offsets[self.cur_chunk];
+                    self.cur_chunk += 1;
+
+                    // try to refine sample position
+                    if self.chunk_sizes.len() > self.chunk_offsets.len() {
+                        for i in self.cur_sample..tgt_sample {
+                            self.cur_sample   += 1;
+                            self.samples_left -= 1;
+                            self.last_offset  += u64::from(self.chunk_sizes[i]);
+                        }
+                    }
+                }
+            } else {
+                self.cur_chunk = self.cur_sample;
+            }
+        } else if self.chunk_offsets.len() != self.chunk_sizes.len() && !self.sample_map.is_empty() {
+            let mut csamp = 0;
+            self.cur_chunk = 0;
+            let mut cmap = self.sample_map.iter();
+            let mut cur_samps = 0;
+            let (mut next_idx, mut next_samples) = cmap.next().unwrap();
+            loop {
+                if self.cur_chunk + 1 == next_idx as usize {
+                    self.samples_left = cur_samps;
+                    cur_samps = next_samples as usize;
+                    if let Some((new_idx, new_samples)) = cmap.next() {
+                        next_idx = *new_idx;
+                        next_samples = *new_samples;
+                    }
+                }
+                csamp += cur_samps;
+                if csamp >= self.cur_sample {
+                    if self.cur_chunk >= self.chunk_offsets.len() {
+                        if self.stream_type == StreamType::Data {
+                            self.cur_chunk = self.chunk_offsets.len();
+                            self.samples_left = 0;
+                            return Ok(NATimeInfo::rescale_ts(tgt_pts, self.tb_num, self.tb_den, 1, 1000));
+                        }
+                        return Err(DemuxerError::SeekError);
+                    }
+                    self.last_offset = self.chunk_offsets[self.cur_chunk];
+                    break;
+                }
+                self.cur_chunk += 1;
+            }
+            csamp -= cur_samps;
+            for sample_no in csamp..self.cur_sample {
+                self.last_offset += self.get_size(sample_no) as u64;
+            }
+            self.samples_left = csamp + cur_samps - self.cur_sample;
+            self.cur_chunk += 1;
+        }
+        let cur_pts = self.timesearch.map_time(self.cur_sample as u32, &self.time_to_sample);
+        let cur_time = NATimeInfo::rescale_ts(cur_pts, self.tb_num, self.tb_den, 1, 1000);
+        Ok(cur_time)
+    }
+}
diff --git a/nihav-commonfmt/src/demuxers/mov/track.rs b/nihav-commonfmt/src/demuxers/mov/track.rs
new file mode 100644 (file)
index 0000000..581f331
--- /dev/null
@@ -0,0 +1,967 @@
+use nihav_core::demuxers::*;
+use nihav_registry::register::*;
+use nihav_codec_support::codecs::qt_pal::*;
+
+use super::*;
+use super::pktread::*;
+
+macro_rules! mktag {
+    ($a:expr, $b:expr, $c:expr, $d:expr) => ({
+        (($a as u32) << 24) | (($b as u32) << 16) | (($c as u32) << 8) | ($d as u32)
+    });
+    ($arr:expr) => ({
+        (($arr[0] as u32) << 24) | (($arr[1] as u32) << 16) | (($arr[2] as u32) << 8) | ($arr[3] as u32)
+    });
+}
+
+const QT_RGB555_FORMAT: NAPixelFormaton = NAPixelFormaton::make_rgb16_fmt(5, 5, 5, true, true);
+
+pub const ARGB_FORMAT: NAPixelFormaton = NAPixelFormaton {
+        model: ColorModel::RGB(RGBSubmodel::RGB), components: 4,
+        comp_info: [
+            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 1, next_elem: 4 }),
+            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 2, next_elem: 4 }),
+            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 3, next_elem: 4 }),
+            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 0, next_elem: 4 }),
+            None ],
+        elem_size: 4, be: false, alpha: true, palette: false };
+
+struct TrackChunkHandler {
+    ctype:  u32,
+    parse:  fn(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64>,
+}
+
+const IGNORED_CHUNKS: &[u32] = &[
+    mktag!(b"free"), mktag!(b"skip"), mktag!(b"udta"), mktag!(b"wide")
+];
+
+fn skip_chunk(_track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    br.skip64(size)?;
+    Ok(size)
+}
+
+const TRAK_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
+    TrackChunkHandler { ctype: mktag!(b"clip"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"matt"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"edts"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"tref"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"load"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"imap"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"tkhd"), parse: read_tkhd },
+    TrackChunkHandler { ctype: mktag!(b"mdia"), parse: read_mdia },
+];
+
+fn read_tkhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_TKHD_SIZE: u64 = 84;
+    validate!(size >= KNOWN_TKHD_SIZE);
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let _ctime              = br.read_u32be()?;
+    let _mtime              = br.read_u32be()?;
+    let track_id            = br.read_u32be()?;
+                              br.read_skip(4)?;
+    let duration            = br.read_u32be()?;
+                              br.read_skip(8)?;
+    let _layer              = br.read_u16be()?;
+    let _alt_group          = br.read_u16be()?;
+    let _volume             = br.read_u16be()?;
+                              br.read_skip(2)?;
+                              br.read_skip(36)?; // matrix
+    let width               = br.read_u32be()? as usize;
+    let height              = br.read_u32be()? as usize;
+    track.width  = width  >> 16;
+    track.height = height >> 16;
+    track.track_id = track_id;
+    track.pkt_demux.duration = duration;
+
+    track.tkhd_found = true;
+    Ok(KNOWN_TKHD_SIZE)
+}
+
+fn read_mdia(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    track.stream_type = StreamType::None;
+    track.read_mdia(br, size)?;
+    Ok(size)
+}
+
+const MDIA_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
+    TrackChunkHandler { ctype: mktag!(b"mdhd"), parse: read_mdhd },
+    TrackChunkHandler { ctype: mktag!(b"hdlr"), parse: read_hdlr },
+    TrackChunkHandler { ctype: mktag!(b"minf"), parse: read_minf },
+];
+
+fn read_mdhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_MDHD_SIZE: u64 = 24;
+    validate!(size >= KNOWN_MDHD_SIZE);
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let flags               = br.read_u24be()?;
+    validate!(flags == 0);
+    let _ctime              = br.read_u32be()?;
+    let _mtime              = br.read_u32be()?;
+    track.tb_den            = br.read_u32be()?;
+    validate!(track.tb_den != 0);
+    track.pkt_demux.tb_den = track.tb_den;
+    track.pkt_demux.duration = br.read_u32be()?;
+    let _language           = br.read_u16be()?;
+    let _quality            = br.read_u16be()?;
+
+    Ok(KNOWN_MDHD_SIZE)
+}
+
+fn read_hdlr(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_HDLR_SIZE: u64 = 24;
+    validate!(size >= KNOWN_HDLR_SIZE);
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let flags               = br.read_u24be()?;
+    validate!(flags == 0);
+    let comp_type           = br.read_u32be()?;
+    let comp_subtype        = br.read_u32be()?;
+    let _comp_manufacturer  = br.read_u32be()?;
+    let _comp_flags         = br.read_u32be()?;
+    let _comp_flags_mask    = br.read_u32be()?;
+
+    if comp_type == mktag!(b"mhlr") || comp_type == 0 {
+        if comp_subtype == mktag!(b"vide") {
+            track.stream_type = StreamType::Video;
+        } else if comp_subtype == mktag!(b"soun") {
+            track.stream_type = StreamType::Audio;
+        } else {
+            track.stream_type = StreamType::Data;
+        }
+    } else if comp_type == mktag!(b"dhlr") {
+        track.stream_type = StreamType::Data;
+    } else {
+        println!("Unknown stream type");
+        track.stream_type = StreamType::Data;
+    }
+
+    Ok(KNOWN_HDLR_SIZE)
+}
+
+fn read_minf(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    track.read_minf(br, size)?;
+    Ok(size)
+}
+
+const MINF_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
+    TrackChunkHandler { ctype: mktag!(b"hdlr"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"dinf"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"vmhd"), parse: read_vmhd },
+    TrackChunkHandler { ctype: mktag!(b"smhd"), parse: read_smhd },
+    TrackChunkHandler { ctype: mktag!(b"gmhd"), parse: read_gmhd },
+    TrackChunkHandler { ctype: mktag!(b"gmin"), parse: read_gmin },
+    TrackChunkHandler { ctype: mktag!(b"stbl"), parse: read_stbl },
+];
+
+fn read_vmhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_VMHD_SIZE: u64 = 12;
+    validate!(track.stream_type == StreamType::Video);
+    validate!(size >= KNOWN_VMHD_SIZE);
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+                              br.read_skip(2)?; // graphics mode
+                              br.read_skip(6)?; // opcolor
+    Ok(KNOWN_VMHD_SIZE)
+}
+
+fn read_smhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_SMHD_SIZE: u64 = 8;
+    validate!(track.stream_type == StreamType::Audio);
+    validate!(size >= KNOWN_SMHD_SIZE);
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+                              br.read_skip(2)?; // balance
+                              br.read_skip(2)?;
+    Ok(KNOWN_SMHD_SIZE)
+}
+
+fn read_gmhd(track: &mut Track, _br: &mut dyn ByteIO, _size: u64) -> DemuxerResult<u64> {
+    validate!(track.stream_type == StreamType::Data);
+    Ok(0)
+}
+
+fn read_gmin(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    validate!(track.stream_type == StreamType::Data);
+    const KNOWN_GMIN_SIZE: u64 = 16;
+    validate!(size >= KNOWN_GMIN_SIZE);
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+                              br.read_skip(2)?; // graphics mode
+                              br.read_skip(6)?; // opcolor
+                              br.read_skip(2)?; // balance
+                              br.read_skip(2)?;
+    Ok(KNOWN_GMIN_SIZE)
+}
+
+fn read_stbl(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    track.read_stbl(br, size)?;
+    Ok(size)
+}
+
+const STBL_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
+    TrackChunkHandler { ctype: mktag!(b"stsd"), parse: read_stsd },
+    TrackChunkHandler { ctype: mktag!(b"stts"), parse: read_stts },
+    TrackChunkHandler { ctype: mktag!(b"stgs"), parse: read_stgs },
+    TrackChunkHandler { ctype: mktag!(b"stss"), parse: read_stss },
+    TrackChunkHandler { ctype: mktag!(b"stsc"), parse: read_stsc },
+    TrackChunkHandler { ctype: mktag!(b"stsz"), parse: read_stsz },
+    TrackChunkHandler { ctype: mktag!(b"stco"), parse: read_stco },
+    TrackChunkHandler { ctype: mktag!(b"co64"), parse: read_co64 },
+    TrackChunkHandler { ctype: mktag!(b"stsh"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"ctts"), parse: read_ctts },
+];
+
+fn parse_audio_edata(br: &mut dyn ByteIO, start_pos: u64, size: u64) -> DemuxerResult<Option<Vec<u8>>> {
+    let read_part = br.tell() - start_pos;
+    if read_part + 8 < size {
+        let mut buf = [0; 8];
+                              br.peek_buf(&mut buf)?;
+        if &buf[4..8] != b"wave" {
+            let mut buf = vec![0; (size - read_part) as usize];
+                              br.read_buf(&mut buf)?;
+            return Ok(Some(buf));
+        }
+
+        let csize           = br.read_u32be()? as u64;
+        let ctag            = br.read_u32be()?;
+        validate!(read_part + csize <= size);
+        validate!(ctag == mktag!(b"wave"));
+        if csize == 8 {
+            return Ok(None);
+        }
+        let mut buf = [0; 8];
+                              br.peek_buf(&mut buf)?;
+        if &buf[4..8] == b"frma" {
+                              br.read_skip(12)?;
+            if csize > 20 {
+                let mut buf = vec![0; (csize - 20) as usize];
+                              br.read_buf(&mut buf)?;
+                Ok(Some(buf))
+            } else {
+                Ok(None)
+            }
+        } else if csize > 8 {
+            let mut buf = vec![0; (csize as usize) - 8];
+                              br.read_buf(&mut buf)?;
+            Ok(Some(buf))
+        } else {
+            Ok(None)
+        }
+    } else {
+        Ok(None)
+    }
+}
+
+#[allow(clippy::neg_cmp_op_on_partial_ord)]
+fn read_stsd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    const KNOWN_STSD_SIZE: u64 = 24;
+    validate!(size >= KNOWN_STSD_SIZE);
+    let start_pos = br.tell();
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let entries             = br.read_u32be()?;
+    validate!(entries > 0);
+    let esize               = u64::from(br.read_u32be()?);
+    validate!(esize + 8 <= size);
+    let mut fcc = [0u8; 4];
+                              br.read_buf(&mut fcc)?;
+                              br.read_skip(6)?;
+    let _data_ref           = br.read_u16be()?;
+
+    track.pkt_demux.fcc = fcc;
+
+    let codec_info;
+    match track.stream_type {
+        StreamType::Video => {
+            let _ver            = br.read_u16be()?;
+            let _revision       = br.read_u16le()?;
+            let _vendor         = br.read_u32be()?;
+            let _temp_quality   = br.read_u32be()?;
+            let _spat_quality   = br.read_u32be()?;
+            let width           = br.read_u16be()? as usize;
+            let height          = br.read_u16be()? as usize;
+            let _hor_res        = br.read_u32be()?;
+            let _vert_res       = br.read_u32be()?;
+            let data_size       = br.read_u32be()?;
+            if !track.ver_m1 {
+                validate!(data_size == 0);
+            }
+            let _frame_count    = br.read_u16be()? as usize;
+            let _cname_len      = br.read_byte()? as usize;
+                                  br.read_skip(31)?; // actual compressor name
+            let depth           = br.read_u16be()?;
+            let ctable_id       = br.read_u16be()?;
+            let grayscale = depth > 0x20 || depth == 1;
+            let depth = if grayscale { depth & 0x1F } else { depth };
+            if ctable_id == 0 {
+                let max_pal_size = start_pos + size - br.tell();
+                if depth <= 8 {
+                    let mut pal = [0; 1024];
+                    read_palette(br, max_pal_size, &mut pal)?;
+                    track.pal = Some(Arc::new(pal));
+                } else {
+                                  br.read_skip(max_pal_size as usize)?;
+                }
+            } else if (depth <= 8) && !grayscale {
+                match depth & 0x1F {
+                    2 => {
+                        let mut pal = [0; 1024];
+                        pal[..4 * 4].copy_from_slice(&MOV_DEFAULT_PAL_2BIT);
+                        track.pal = Some(Arc::new(pal));
+                    },
+                    4 => {
+                        let mut pal = [0; 1024];
+                        pal[..16 * 4].copy_from_slice(&MOV_DEFAULT_PAL_4BIT);
+                        track.pal = Some(Arc::new(pal));
+                    },
+                    8 => {
+                        track.pal = Some(Arc::new(MOV_DEFAULT_PAL_8BIT));
+                    },
+                    _ => {},
+                };
+            } else if grayscale && ctable_id != 0xFFFF {
+                let mut pal = [0; 1024];
+                let cdepth = depth & 0x1F;
+                let size = 1 << cdepth;
+                for i in 0..size {
+                    let mut clr = ((size - 1 - i) as u8) << (8 - cdepth);
+                    let mut off = 8 - cdepth;
+                    while off >= cdepth {
+                        clr |= clr >> (8 - off);
+                        off -= cdepth;
+                    }
+                    if off > 0 {
+                        clr |= clr >> (8 - off);
+                    }
+                    pal[i * 4]     = clr;
+                    pal[i * 4 + 1] = clr;
+                    pal[i * 4 + 2] = clr;
+                }
+                track.pal = Some(Arc::new(pal));
+            }
+// todo other atoms, put as extradata
+            let cname = if let Some(name) = find_codec_from_mov_video_fourcc(&fcc) {
+                    name
+                } else if let Some(name) = find_codec_from_avi_fourcc(&fcc) {
+                    name
+                } else {
+                    "unknown"
+                };
+            let format = if cname == "rawvideo" {
+                    if &fcc == b"j420" {
+                        validate!(depth == 12);
+                        YUV420_FORMAT
+                    } else {
+                        match depth {
+                            1..=8 | 33..=40 => PAL8_FORMAT,
+                            15 | 16 => QT_RGB555_FORMAT,
+                            24 => RGB24_FORMAT,
+                            32 => ARGB_FORMAT,
+                            _ => {
+                                println!("unknown depth {depth}");
+                                return Err(DemuxerError::NotImplemented);
+                            }
+                        }
+                    }
+                } else if depth > 8 && depth <= 32 {
+                    RGB24_FORMAT
+                } else {
+                    PAL8_FORMAT
+                };
+            let mut vhdr = NAVideoInfo::new(width, height, false, format);
+            vhdr.bits = depth as u8;
+            if track.ver_m1 { // skip the rest for the beta version of QT
+                br.seek(SeekFrom::Start(start_pos + size))?;
+            }
+            //skip various common atoms
+            while br.tell() - start_pos + 8 < size {
+                let mut buf = [0u8; 8];
+                br.peek_buf(&mut buf)?;
+                let tsize = read_u32be(&buf).unwrap() as usize;
+                let tag = &buf[4..8];
+                validate!(tsize >= 8);
+                match tag {
+                    b"pasp" | b"clap" | b"gama" => {
+                        br.read_skip(tsize)?;
+                    },
+                    _ => break,
+                };
+            }
+            let edata = if br.tell() - start_pos + 4 < size {
+                    let edata_size  = br.read_u32be()? as usize;
+                    validate!(edata_size >= 4);
+                    let mut buf = vec![0; edata_size - 4];
+                                  br.read_buf(buf.as_mut_slice())?;
+                    Some(buf)
+                } else {
+                    None
+                };
+            codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Video(vhdr), edata);
+        },
+        StreamType::Audio if track.ver_m1 => {
+                                  br.read_skip(8)?;
+            let nchannels       = br.read_u16be()?;
+            validate!(nchannels == 1 || nchannels == 2);
+            let sample_size     = br.read_u16be()?;
+            validate!(sample_size == 8 || sample_size == 16);
+                                  br.read_u32be()?;
+            let sample_rate     = br.read_u32be()? >> 16;
+            let cname = if fcc == [0; 4] { "pcm" } else { "unknown" };
+            let mut soniton = NASoniton::new(sample_size as u8, SONITON_FLAG_SIGNED | SONITON_FLAG_BE);
+            if sample_size == 8 {
+                soniton.signed = false;
+            }
+            let ahdr = NAAudioInfo::new(sample_rate, nchannels as u8, soniton, 1);
+            let edata = parse_audio_edata(br, start_pos, size)?;
+            codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Audio(ahdr), edata);
+            track.pkt_demux.raw_audio = cname == "pcm";
+            track.pkt_demux.bsize     = (sample_size / 8) as usize;
+            track.pkt_demux.channels  = nchannels as usize;
+            track.pkt_demux.bits      = sample_size as usize;
+            if track.tb_den <= 1 {
+                track.tb_den = sample_rate;
+            }
+        },
+        StreamType::Audio => {
+            let sver            = br.read_u16be()?;
+            let _revision       = br.read_u16le()?;
+            let _vendor         = br.read_u32be()?;
+            let mut nchannels   = br.read_u16be()?;
+            if sver != 2 {
+                validate!(nchannels <= 64);
+            }
+            let sample_size     = br.read_u16be()?;
+            validate!(sample_size <= 128);
+            let _compr_id       = br.read_u16be()?;
+            let packet_size     = br.read_u16be()? as usize;
+            validate!(packet_size == 0);
+            let mut sample_rate = br.read_u32be()? >> 16;
+            if sver != 2 {
+                validate!(sample_rate > 0);
+            }
+            let cname = if let Some(name) = find_codec_from_mov_audio_fourcc(&fcc) {
+                    name
+                } else if let (true, Some(name)) = ((fcc[0] == b'm' && fcc[1] == b's'),  find_codec_from_wav_twocc(u16::from(fcc[2]) * 256 + u16::from(fcc[3]))) {
+                    name
+                } else {
+                    "unknown"
+                };
+            let mut soniton = NASoniton::new(sample_size as u8, SONITON_FLAG_SIGNED | SONITON_FLAG_BE);
+            if &fcc == b"raw " && sample_size == 8 {
+                soniton.signed = false;
+            }
+            if &fcc == b"sowt" {
+                soniton.be = false;
+            }
+            let mut block_align = 1;
+            match sver {
+                1 => {
+                    let samples_per_packet  = br.read_u32be()?;
+                    let _bytes_per_packet   = br.read_u32be()?;
+                    let bytes_per_frame     = br.read_u32be()?;
+                    let _bytes_per_sample   = br.read_u32be()?;
+                    track.pkt_demux.bsize = bytes_per_frame as usize;
+                    track.pkt_demux.frame_samples = samples_per_packet as usize;
+                    track.tb_num = samples_per_packet.max(1);
+                    block_align = bytes_per_frame as usize;
+                },
+                2 => {
+                                              br.read_u32be()?; // some size
+                    let srate               = br.read_f64be()?;
+                    validate!(srate > 1.0);
+                    sample_rate = srate as u32;
+                    let channels            = br.read_u32be()?;
+                    validate!(channels > 0 && channels < 255);
+                    nchannels = channels as u16;
+                                              br.read_u32be()?; // always 0x7F000000
+                    let _bits_per_csample   = br.read_u32be()?;
+                    let _codec_flags        = br.read_u32be()?;
+                    let bytes_per_frame     = br.read_u32be()?;
+                    let samples_per_packet  = br.read_u32be()?;
+                    track.pkt_demux.bsize = bytes_per_frame as usize;
+                    track.pkt_demux.frame_samples = samples_per_packet as usize;
+                    track.tb_num = samples_per_packet;
+                },
+                _ => {
+                    track.pkt_demux.bsize = (sample_size / 8) as usize;
+                },
+            };
+            if track.tb_den <= 1 {
+                track.tb_den = sample_rate;
+                track.pkt_demux.tb_den = sample_rate;
+            }
+            track.pkt_demux.raw_audio = matches!(&fcc,
+                    b"NONE" | b"raw " | b"twos" | b"sowt" |
+                    b"in24" | b"in32" | b"fl32" | b"fl64" |
+                    b"ima4" | b"ms\x00\x02" | b"ms\x00\x11" |
+                    b"alaw" | b"ulaw" |
+                    b"MAC3" | b"MAC6");
+            let ahdr = NAAudioInfo::new(sample_rate, nchannels as u8, soniton, block_align);
+            let edata = parse_audio_edata(br, start_pos, size)?;
+            codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Audio(ahdr), edata);
+            track.pkt_demux.channels  = nchannels as usize;
+            track.pkt_demux.bits      = sample_size as usize;
+        },
+        StreamType::None => {
+            return Err(DemuxerError::InvalidData);
+        },
+        _ => {
+//todo put it all into extradata
+            let edata = None;
+            codec_info = NACodecInfo::new("unknown", NACodecTypeInfo::None, edata);
+        },
+    };
+    track.pkt_demux.tb_num = track.tb_num;
+    track.pkt_demux.tb_den = track.tb_den;
+    track.pkt_demux.stream_type = track.stream_type;
+    let read_size = br.tell() - start_pos;
+    validate!(read_size <= size);
+    track.stream = Some(NAStream::new(track.stream_type, track.track_no, codec_info, track.tb_num, track.tb_den, u64::from(track.pkt_demux.duration)));
+    track.stsd_found = true;
+    Ok(read_size)
+}
+
+fn read_stts(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    validate!(size >= 8);
+    let start_pos = br.tell();
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let entries             = br.read_u32be()? as usize;
+    validate!(entries as u64 <= (size - 8) / 8);
+    if entries == 0 {
+    } else if entries == 1 {
+        let _count          = br.read_u32be()?;
+        let tb_num          = br.read_u32be()?;
+        validate!(tb_num != 0);
+        track.rescale(tb_num);
+    } else {
+        track.pkt_demux.time_to_sample.clear();
+        track.pkt_demux.time_to_sample.reserve(entries);
+        for _ in 0..entries {
+            let count       = br.read_u32be()?;
+            let mult        = br.read_u32be()?;
+            track.pkt_demux.time_to_sample.push((count, mult));
+        }
+    }
+    let read_size = br.tell() - start_pos;
+    validate!(read_size <= size);
+    Ok(read_size)
+}
+
+fn read_stgs(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    validate!(size >= 8);
+    validate!(track.ver_m1);
+    let start_pos = br.tell();
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let entries             = br.read_u32be()? as usize;
+    validate!(entries as u64 <= (size - 8) / 8);
+    if entries == 0 {
+    } else if entries == 1 {
+        let _count          = br.read_u32be()?;
+        let tb_num          = br.read_u32be()?;
+        validate!(tb_num != 0);
+        track.rescale(tb_num);
+    } else {
+        track.pkt_demux.time_to_sample.clear();
+        track.pkt_demux.time_to_sample.reserve(entries);
+        for _ in 0..entries {
+            let count       = br.read_u32be()?;
+            let mult        = br.read_u32be()?;
+            track.pkt_demux.time_to_sample.push((count, mult));
+        }
+    }
+    let read_size = br.tell() - start_pos;
+    validate!(read_size <= size);
+    Ok(read_size)
+}
+
+fn read_stss(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let entries             = br.read_u32be()? as usize;
+    validate!(entries < ((u32::MAX >> 2) - 8) as usize);
+    validate!((entries * 4 + 8) as u64 == size);
+    track.pkt_demux.keyframes = Vec::with_capacity(entries);
+    let mut last_sample_no = 0;
+    for _ in 0..entries {
+        let sample_no       = br.read_u32be()?;
+        validate!(sample_no > last_sample_no);
+        track.pkt_demux.keyframes.push(sample_no);
+        last_sample_no = sample_no;
+    }
+    track.stss_found = true;
+    Ok(size)
+}
+
+fn read_stsc(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let entries             = br.read_u32be()? as usize;
+    if track.ver_m1 {
+        if entries != 1 || size != 24 {
+            return Err(DemuxerError::NotImplemented);
+        }
+                              br.read_u32be()?;
+        let sample_no       = br.read_u32be()?;
+        validate!(sample_no == 1);
+        let nsamples        = br.read_u32be()?;
+                              br.read_u32be()?; // maybe sample descriptor
+        track.pkt_demux.sample_map.push((sample_no, nsamples));
+        return Ok(size);
+    }
+    validate!(entries < ((u32::MAX / 12) - 8) as usize);
+    validate!((entries * 12 + 8) as u64 == size);
+    track.pkt_demux.sample_map = Vec::with_capacity(entries);
+    let mut last_sample_no = 0;
+    for _i in 0..entries {
+        let sample_no       = br.read_u32be()?;
+        validate!(sample_no > last_sample_no);
+        let nsamples        = br.read_u32be()?;
+        let _sample_desc    = br.read_u32be()?;
+        track.pkt_demux.sample_map.push((sample_no, nsamples));
+        last_sample_no = sample_no;
+    }
+    Ok(size)
+}
+
+fn read_stsz(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let sample_size         = br.read_u32be()?;
+    if sample_size != 0 {
+        track.pkt_demux.sample_size = sample_size;
+        if track.pkt_demux.sample_size != 1 || track.pkt_demux.bsize == 0 {
+            track.pkt_demux.bsize = sample_size as usize;
+        }
+        Ok(8)
+    } else {
+        let entries             = br.read_u32be()? as usize;
+        validate!((entries * 4 + 12) as u64 == size);
+        track.pkt_demux.chunk_sizes = vec![0; entries];
+                                  br.read_u32be_arr(&mut track.pkt_demux.chunk_sizes)?;
+        Ok(size)
+    }
+}
+
+fn read_stco(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let entries             = br.read_u32be()? as usize;
+    validate!((entries * 4 + 8) as u64 == size);
+    if entries < 1024 {
+        track.pkt_demux.chunk_offsets = Vec::with_capacity(entries);
+        for _i in 0..entries {
+            let sample_offset   = br.read_u32be()?;
+            track.pkt_demux.chunk_offsets.push(u64::from(sample_offset));
+        }
+    } else {
+        // reading whole array at once and converting it later is much faster
+        track.pkt_demux.chunk_offsets = vec![0; entries];
+        let mut tmp = vec![0u32; entries];
+        unsafe {
+            let data = tmp.as_mut_ptr();
+            let ptr = std::slice::from_raw_parts_mut(data as *mut u8, entries * 4);
+            br.read_buf(ptr)?;
+        }
+        for (dst, &src) in track.pkt_demux.chunk_offsets.iter_mut().zip(tmp.iter()) {
+            *dst = u64::from(u32::from_be(src));
+        }
+    }
+    Ok(size)
+}
+
+fn read_co64(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    let version             = br.read_byte()?;
+    validate!(version == 0);
+    let _flags              = br.read_u24be()?;
+    let entries             = br.read_u32be()? as usize;
+    validate!(entries > 0 && (entries * 8 + 8) as u64 == size);
+    track.pkt_demux.chunk_offsets = vec![0; entries];
+                              br.read_u64be_arr(&mut track.pkt_demux.chunk_offsets)?;
+    Ok(size)
+}
+
+fn read_ctts(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    validate!(size >= 8);
+    let version             = br.read_byte()?;
+    let _flags              = br.read_u24be()?;
+    if version > 1 {
+        return Err(DemuxerError::NotImplemented);
+    }
+    let entries             = br.read_u32be()? as usize;
+    track.pkt_demux.ctts_version = version;
+    track.pkt_demux.ctts_map.resize(entries);
+    match version {
+        0 | 1 => {
+            validate!(size == (entries as u64) * 8 + 8);
+            if entries < 1024 {
+                for _ in 0..entries {
+                    let samp_count  = br.read_u32be()?;
+                    let samp_offset = br.read_u32be()?;
+                    track.pkt_demux.ctts_map.add(samp_count, samp_offset / track.tb_div);
+                }
+            } else {
+                // reading whole array at once and converting it later is much faster
+                track.pkt_demux.ctts_map.array.resize(entries, RLEPair(0, 0));
+                unsafe {
+                    let data = track.pkt_demux.ctts_map.array.as_mut_ptr();
+                    let ptr = std::slice::from_raw_parts_mut(data as *mut u8, entries * 8);
+                    br.read_buf(ptr)?;
+                }
+                for RLEPair(count, offset) in track.pkt_demux.ctts_map.array.iter_mut() {
+                    *count = u32::from_be(*count);
+                    *offset = u32::from_be(*offset) / track.tb_div;
+                }
+            }
+        },
+        _ => unreachable!(),
+    };
+    track.pkt_demux.ctts_map.reset();
+
+    Ok(size)
+}
+
+const TRAF_CHUNK_HANDLERS: &[TrackChunkHandler] = &[
+    TrackChunkHandler { ctype: mktag!(b"tfhd"), parse: read_tfhd },
+    TrackChunkHandler { ctype: mktag!(b"trun"), parse: read_trun },
+    TrackChunkHandler { ctype: mktag!(b"sbgp"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"sgpd"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"subs"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"saiz"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"saio"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"tfdt"), parse: skip_chunk },
+    TrackChunkHandler { ctype: mktag!(b"meta"), parse: skip_chunk },
+];
+
+fn read_tfhd(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    validate!(size >= 8);
+    let start = br.tell();
+    let _version            = br.read_byte()?;
+    let flags               = br.read_u24be()?;
+    let _track_id           = br.read_u32be()?;
+    if (flags & 0x000001) != 0 {
+        let base_offset     = br.read_u64be()?;
+        track.moof_off = base_offset;
+    }
+    if (flags & 0x000002) != 0 {
+        let _sample_description_index = br.read_u32be()?;
+    }
+    if (flags & 0x000008) != 0 {
+        let default_sample_duration = br.read_u32be()?;
+        if track.tb_div == 1 {
+            track.rescale(default_sample_duration);
+        }
+    }
+    if (flags & 0x000010) != 0 {
+        let _default_sample_size = br.read_u32be()?;
+    }
+    if (flags & 0x000020) != 0 {
+        let _default_sample_flags = br.read_u32be()?;
+    }
+    /*if (flags & 0x010000) != 0 { // duration is empty
+    }*/
+    /*if (flags & 0x020000) != 0 { // base offset is moof start
+    }*/
+    Ok(br.tell() - start)
+}
+
+fn read_trun(track: &mut Track, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<u64> {
+    validate!(size >= 8);
+    let version             = br.read_byte()?;
+    let flags               = br.read_u24be()?;
+    let data_off_present        = (flags & 0x000001) != 0;
+    let first_sample_flags      = (flags & 0x000004) != 0;
+    let sample_duration_present = (flags & 0x000100) != 0;
+    let sample_size_present     = (flags & 0x000200) != 0;
+    let sample_flags_present    = (flags & 0x000400) != 0;
+    let sample_ct_off_present   = (flags & 0x000800) != 0;
+
+    let sample_count            = br.read_u32be()? as usize;
+
+    let mut hdr_size = 8;
+    let mut arr_size = 0;
+    if data_off_present {
+        hdr_size += 4;
+    }
+    if first_sample_flags {
+        hdr_size += 4;
+    }
+    if sample_duration_present {
+        arr_size += 4;
+    }
+    if sample_size_present {
+        arr_size += 4;
+    }
+    if sample_flags_present {
+        arr_size += 4;
+    }
+    if sample_ct_off_present {
+        arr_size += 4;
+    }
+    validate!(size == hdr_size + arr_size * (sample_count as u64));
+
+    let mut data_off = if data_off_present {
+            let off             = br.read_u32be()? as i32;
+            let new_off = (track.moof_off as i64) + i64::from(off);
+            validate!(new_off > 0);
+            new_off as u64
+        } else {
+            track.moof_off
+        };
+    if first_sample_flags {
+        let _flags              = br.read_u32be()?;
+    }
+
+    if sample_size_present {
+        track.pkt_demux.chunk_sizes.reserve(sample_count);
+        track.pkt_demux.chunk_offsets.reserve(sample_count);
+    }
+
+    if sample_ct_off_present {
+        if track.pkt_demux.ctts_version != version {
+            track.pkt_demux.ctts_version = version;
+        }
+        track.pkt_demux.ctts_map.reserve(sample_count);
+    }
+
+    if track.pkt_demux.chunk_offsets.len() < (u32::MAX as usize) {
+        track.pkt_demux.keyframes.push((track.pkt_demux.chunk_offsets.len() + 1) as u32);
+    }
+    for _ in 0..sample_count {
+        if sample_duration_present {
+            let _duration       = br.read_u32be()?;
+        }
+        if sample_size_present {
+            let ssize           = br.read_u32be()?;
+            track.pkt_demux.chunk_sizes.push(ssize);
+            track.pkt_demux.chunk_offsets.push(data_off);
+            data_off += u64::from(ssize);
+        }
+        if sample_flags_present {
+            let _flags          = br.read_u32be()?;
+        }
+        if sample_ct_off_present {
+            let samp_offset     = br.read_u32be()?;
+            if version == 0 {
+                track.pkt_demux.ctts_map.add(1, samp_offset / track.tb_div);
+            } else {
+                track.pkt_demux.ctts_map.add(1, ((samp_offset as i32) / (track.tb_div as i32)) as u32);
+            }
+        }
+    }
+    track.pkt_demux.ctts_map.reset();
+
+    Ok(size)
+}
+
+pub struct Track {
+    pub ver_m1:         bool,
+    pub track_id:       u32,
+    pub track_str_id:   usize,
+    pub track_no:       u32,
+    pub tb_num:         u32,
+    pub tb_den:         u32,
+    pub tb_div:         u32,
+    pub depth:          u8,
+    pub tkhd_found:     bool,
+    pub stsd_found:     bool,
+    pub stss_found:     bool,
+    pub stream_type:    StreamType,
+    pub width:          usize,
+    pub height:         usize,
+    pub stream:         Option<NAStream>,
+    pub pal:            Option<Arc<[u8; 1024]>>,
+    pub pkt_demux:      QTPacketDemuxer,
+
+    pub moof_off:       u64,
+
+    pub print_chunks:   bool,
+}
+
+impl Track {
+    pub fn new(track_no: u32, tb_den: u32) -> Self {
+        Self {
+            tkhd_found:     false,
+            stsd_found:     false,
+            stss_found:     false,
+            track_id:       0,
+            track_str_id:   0,
+            track_no,
+            tb_num: 1,
+            tb_den,
+            tb_div:         1,
+            stream_type:    StreamType::None,
+            width:          0,
+            height:         0,
+            stream:         None,
+            depth:          0,
+            pal:            None,
+            ver_m1:         false,
+            pkt_demux:      QTPacketDemuxer::new(),
+
+            moof_off:       0,
+
+            print_chunks:   false,
+        }
+    }
+    read_chunk_list!(track; "trak", read_trak, TRAK_CHUNK_HANDLERS);
+    read_chunk_list!(track; "mdia", read_mdia, MDIA_CHUNK_HANDLERS);
+    read_chunk_list!(track; "minf", read_minf, MINF_CHUNK_HANDLERS);
+    read_chunk_list!(track; "stbl", read_stbl, STBL_CHUNK_HANDLERS);
+    read_chunk_list!(track; "traf", read_traf, TRAF_CHUNK_HANDLERS);
+    pub fn call_read_trak(&mut self, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<()> {
+        self.read_trak(br, size)
+    }
+    pub fn call_read_traf(&mut self, br: &mut dyn ByteIO, size: u64) -> DemuxerResult<()> {
+        self.read_traf(br, size)
+    }
+    fn rescale(&mut self, tb_num: u32) {
+        self.tb_div = tb_num;
+        if let Some(ref mut stream) = self.stream {
+            let tb_den = stream.tb_den;
+            let (tb_num, tb_den) = reduce_timebase(tb_num * stream.tb_num, tb_den);
+            stream.duration /= u64::from(self.tb_div);
+            stream.tb_num = tb_num;
+            stream.tb_den = tb_den;
+            self.tb_num = tb_num;
+            self.tb_den = tb_den;
+            self.pkt_demux.tb_num = tb_num;
+            self.pkt_demux.tb_den = tb_den;
+            self.pkt_demux.duration /= self.tb_div;
+        }
+    }
+}
+
+#[allow(clippy::too_many_arguments)]
+pub fn process_packet(src: &mut dyn ByteIO, strmgr: &StreamManager, track: &mut Track, ts: NATimeInfo, offset: u64, size: usize, first: bool, is_kf: bool) -> DemuxerResult<NAPacket> {
+    if let Some(cpts) = ts.get_pts() {
+        let cts = NATimeInfo::rescale_ts(cpts, ts.tb_num, ts.tb_den, 1, 1000);
+        track.pkt_demux.cur_ts = Some(cts);
+    } else {
+        track.pkt_demux.cur_ts = None;
+    }
+    let stream = strmgr.get_stream(track.track_str_id);
+    if stream.is_none() { return Err(DemuxerError::InvalidData); }
+    let stream = stream.unwrap();
+    src.seek(SeekFrom::Start(offset))?;
+    let mut pkt = src.read_packet(stream, ts, first | is_kf, size)?;
+    if let Some(ref pal) = track.pal {
+        let side_data = NASideData::Palette(first, pal.clone());
+        pkt.add_side_data(side_data);
+    }
+    Ok(pkt)
+}