]> git.nihav.org Git - nihav.git/commitdiff
add support for GIF in MOV master
authorKostya Shishkov <kostya.shishkov@gmail.com>
Mon, 12 Oct 2026 12:56:37 +0000 (14:56 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Mon, 12 Oct 2026 12:56:37 +0000 (14:56 +0200)
nihav-commonfmt/src/codecs/gif.rs
nihav-commonfmt/src/codecs/mod.rs
nihav-registry/src/register.rs

index 570d4d2d882c11f45451f7eca7fc0721d0ebf30d..443f9227e8066e73e63eb018c01b9ac7e1c03535 100644 (file)
@@ -37,7 +37,9 @@ impl<'a> BitReader<'a> {
                 }
                 self.left = self.src[self.pos];
                 self.pos += 1;
-                validate!(self.left > 0);
+                if self.left == 0 { // abuse this code to signal end of stream
+                    return Err(DecoderError::MissingReference);
+                }
                 if self.pos + usize::from(self.left) > self.src.len() {
                     return Err(DecoderError::ShortData);
                 }
@@ -117,7 +119,14 @@ impl LZWState {
         let mut pos = 0;
         let mut lastidx = INVALID_POS;
         loop {
-            let idx         = br.read(self.idx_bits)? as usize;
+            let idx         = match br.read(self.idx_bits) {
+                    Ok(val) => val as usize,
+                    Err(DecoderError::MissingReference) => {
+                        validate!(pos == dst.len());
+                        return Ok(());
+                    },
+                    Err(err) => return Err(err)
+                };
             if idx == reset_sym {
                 self.reset(bits);
                 lastidx = INVALID_POS;
@@ -167,6 +176,7 @@ struct GIFDecoder {
     height:     usize,
     lzw:        LZWState,
     transp:     Option<u8>,
+    is_qt:      bool,
 }
 
 fn deinterlace(sbuf: &[u8], dbuf: &mut [u8], width: usize) {
@@ -179,7 +189,7 @@ fn deinterlace(sbuf: &[u8], dbuf: &mut [u8], width: usize) {
 }
 
 impl GIFDecoder {
-    fn new() -> Self {
+    fn new(is_qt: bool) -> Self {
         Self {
             info:       NACodecInfoRef::default(),
             gpal:       [0; 768],
@@ -191,6 +201,7 @@ impl GIFDecoder {
             height:     0,
             lzw:        LZWState::new(),
             transp:     None,
+            is_qt,
         }
     }
 }
@@ -235,6 +246,28 @@ impl NADecoder for GIFDecoder {
         }
 
         let mut br = MemoryReader::new_read(&src);
+        if self.is_qt { // skip header and image descriptor
+            if let Ok([b'G', b'I', b'F', b'8']) = br.peek_tag() {
+                                          br.read_skip(6)?;
+                let width               = usize::from(br.read_u16le()?);
+                let height              = usize::from(br.read_u16le()?);
+                validate!(width == self.width && height == self.height);
+                let flags               = br.read_byte()?;
+                let edata_size = 2 + if (flags & 0x80) != 0 { 3 << ((flags & 7) + 1) } else { 0 };
+                                          br.read_skip(edata_size)?;
+            }
+            while let Ok(0x21) = br.peek_byte() {
+                                          br.read_byte()?;
+                let  _label             = br.read_byte()?;
+                loop {
+                    let len             = usize::from(br.read_byte()?);
+                    if len == 0 {
+                        break;
+                    }
+                                          br.read_skip(len)?;
+                }
+            }
+        }
         let tag                         = br.read_byte()?;
         validate!(tag == 0x2C);
         let left                        = usize::from(br.read_u16le()?);
@@ -303,7 +336,10 @@ impl NAOptionHandler for GIFDecoder {
 }
 
 pub fn get_decoder() -> Box<dyn NADecoder + Send> {
-    Box::new(GIFDecoder::new())
+    Box::new(GIFDecoder::new(false))
+}
+pub fn get_decoder_qt() -> Box<dyn NADecoder + Send> {
+    Box::new(GIFDecoder::new(true))
 }
 
 #[cfg(test)]
@@ -328,4 +364,19 @@ mod test {
                             [0xdf920e8c, 0xeb57c5f8, 0xd862507e, 0xd733fca3],
                             [0x75bee5cb, 0xefb2076c, 0xfce61f8a, 0x2d2b30df]]));
     }
+    // sample from Tex-Edit Plus 4.x distribution
+    #[test]
+    fn test_gif_qt_decoder() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        generic_register_all_decoders(&mut dec_reg);
+
+        test_decoding("mov", "qt-gif", "assets/Misc/The Duke",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x2c62c68c, 0x3c8a076f, 0xd8914e96, 0xb26819f6],
+                            [0xc7243219, 0x322077f4, 0x7fca320f, 0xc96f6cc1],
+                            [0x0eaeeb20, 0xdc8b2c70, 0x861ce6a1, 0xef0e1e4f]]));
+    }
 }
index 793d2c6dc688d1444c7bc06bcc3698110f2c4bb5..4cfb6d82eaeeeda73872fbf0c76e47850043c5c5 100644 (file)
@@ -51,6 +51,8 @@ const DECODERS: &[DecoderInfo] = &[
     DecoderInfo { name: "clearvideo_rm", get_decoder: clearvideo::get_decoder_rm },
 #[cfg(feature="decoder_gif")]
     DecoderInfo { name: "gif", get_decoder: gif::get_decoder },
+#[cfg(feature="decoder_gif")]
+    DecoderInfo { name: "qt-gif", get_decoder: gif::get_decoder_qt },
 #[cfg(feature="decoder_jpeg")]
     DecoderInfo { name: "jpeg", get_decoder: jpeg::get_decoder },
 #[cfg(feature="decoder_rawvideo")]
index b0aa91a283563134d0c60f300f67d086072a7ae6..5f69a3a893271ca55ac6043e7e978201c382159e 100644 (file)
@@ -332,6 +332,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(audio-hyb; "wavpack",      "WavPack"),
 
     desc!(video-ll; "gif",           "GIF"),
+    desc!(video-ll; "qt-gif",        "GIF (in MOV)"),
     desc!(video-im; "jpeg",          "JPEG"),
     desc!(video;    "h264",          "ITU H.264", CODEC_CAP_COMPLEX_REORDER | CODEC_CAP_HYBRID),
     desc!(video-modern; "mpeg4asp",  "MPEG-4 ASP"),
@@ -478,6 +479,7 @@ static MOV_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[
     (b"svqi", "sorenson-video"),
     (b"SVQ3", "sorenson-video3"),
     (b"svq3", "sorenson-video3"),
+    (b"gif ", "qt-gif"),
 
     (b"msvc", "msvideo1"),