X-Git-Url: https://git.nihav.org/?p=nihav.git;a=blobdiff_plain;f=nihav-commonfmt%2Fsrc%2Fmuxers%2Fgif.rs;fp=nihav-commonfmt%2Fsrc%2Fmuxers%2Fgif.rs;h=435d1b887fa2618a723018db8d75a35ba7cd1404;hp=0000000000000000000000000000000000000000;hb=fc39649daffd54a1f7277656f470df2751fcf620;hpb=94c520bf3df0c0e0b436d7c9a2e4b0b40550a453 diff --git a/nihav-commonfmt/src/muxers/gif.rs b/nihav-commonfmt/src/muxers/gif.rs new file mode 100644 index 0000000..435d1b8 --- /dev/null +++ b/nihav-commonfmt/src/muxers/gif.rs @@ -0,0 +1,232 @@ +use nihav_core::muxers::*; + +struct GIFMuxer<'a> { + bw: &'a mut ByteWriter<'a>, + single: bool, + gif87: bool, + pal_written: bool, + nloops: u16, +} + +impl<'a> GIFMuxer<'a> { + fn new(bw: &'a mut ByteWriter<'a>) -> Self { + Self { + bw, + single: false, + gif87: false, + pal_written: false, + nloops: 0, + } + } + fn write_pal(&mut self, pal: &[u8; 1024]) -> MuxerResult<()> { + let mut nclr = 256; + for quad in pal.chunks_exact(4).rev() { + if quad[0] == 0 && quad[1] == 0 && quad[2] == 0 { + nclr -= 1; + } else { + break; + } + } + let mut pal_bits = 1; + while (1 << pal_bits) < nclr { + pal_bits += 1; + } + self.bw.write_byte(0xF0 | (pal_bits - 1))?; + self.bw.write_byte(0)?; // background colour index + self.bw.write_byte(0)?; // aspect ratio + for quad in pal.chunks_exact(4).take(1 << pal_bits) { + self.bw.write_buf(&quad[..3])?; + } + Ok(()) + } +} + +impl<'a> MuxCore<'a> for GIFMuxer<'a> { + fn create(&mut self, strmgr: &StreamManager) -> MuxerResult<()> { + if strmgr.get_num_streams() != 1 { + return Err(MuxerError::InvalidArgument); + } + let vstr = strmgr.get_stream(0).unwrap(); + if vstr.get_media_type() != StreamType::Video { + return Err(MuxerError::UnsupportedFormat); + } + let info = vstr.get_info(); + let vinfo = info.get_properties().get_video_info().unwrap(); + if vinfo.width > 65535 || vinfo.height > 65535 || !vinfo.format.palette { + return Err(MuxerError::UnsupportedFormat); + } + + if self.gif87 { + self.single = true; + self.bw.write_buf(b"GIF87a")?; + } else { + self.bw.write_buf(b"GIF89a")?; + } + self.bw.write_u16le(vinfo.width as u16)?; + self.bw.write_u16le(vinfo.height as u16)?; + + Ok(()) + } + fn mux_frame(&mut self, strmgr: &StreamManager, pkt: NAPacket) -> MuxerResult<()> { + if self.bw.tell() == 0 { + return Err(MuxerError::NotCreated); + } + if !self.pal_written { + let info = strmgr.get_stream(0).unwrap().get_info(); + let mut tr_idx = None; + if let Some(ref edata) = info.get_extradata() { + if edata.len() == 1 { + tr_idx = Some(edata[0]); + } else if edata.len() >= 3 { + self.bw.write_buf(edata)?; + self.pal_written = true; + } + } + if !self.pal_written { + let mut pal_found = false; + for sdata in pkt.side_data.iter() { + if let NASideData::Palette(_, ref pal) = sdata { + self.write_pal(pal,)?; + pal_found = true; + break; + } + } + if !pal_found { + return Err(MuxerError::InvalidArgument); + } + } + self.pal_written = true; + + if !self.single { + let vstr = strmgr.get_stream(0).unwrap(); + + let delay = NATimeInfo::ts_to_time(1, 100, vstr.tb_num, vstr.tb_den) as u16; + self.bw.write_byte(0x21)?; // graphic control + self.bw.write_byte(0xF9)?; // graphic control extension + self.bw.write_byte(4)?; // block size + self.bw.write_byte(if tr_idx.is_some() { 1 } else { 0 })?; // flags + self.bw.write_u16le(delay)?; + self.bw.write_byte(tr_idx.unwrap_or(0))?; // transparent colour index + self.bw.write_byte(0x00)?; // block terminator + + self.bw.write_byte(0x21)?; // graphic control + self.bw.write_byte(0xFF)?; // application extension + let app_id = b"NETSCAPE2.0"; + self.bw.write_byte(app_id.len() as u8)?; + self.bw.write_buf(app_id)?; + self.bw.write_byte(3)?; // application data block length + self.bw.write_byte(0x01)?; + self.bw.write_u16le(self.nloops)?; + self.bw.write_byte(0x00)?; // block terminator + } + } else if self.single { // just one frame is expected + return Err(MuxerError::InvalidArgument); + } + + // buffer is supposed to have all the data starting from image descriptor + let src = pkt.get_buffer(); + self.bw.write_buf(&src)?; + Ok(()) + } + fn flush(&mut self) -> MuxerResult<()> { + Ok(()) + } + fn end(&mut self) -> MuxerResult<()> { + self.bw.write_byte(0x3B)?; // GIF terminator + Ok(()) + } +} + +const MUXER_OPTS: &[NAOptionDefinition] = &[ + NAOptionDefinition { + name: "gif87", description: "Create GIF87 image", + opt_type: NAOptionDefinitionType::Bool }, + NAOptionDefinition { + name: "single", description: "Create single image", + opt_type: NAOptionDefinitionType::Bool }, + NAOptionDefinition { + name: "loops", description: "Number of times to loop the animation", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(65535)) }, +]; + +impl<'a> NAOptionHandler for GIFMuxer<'a> { + fn get_supported_options(&self) -> &[NAOptionDefinition] { MUXER_OPTS } + fn set_options(&mut self, options: &[NAOption]) { + for option in options.iter() { + for opt_def in MUXER_OPTS.iter() { + if opt_def.check(option).is_ok() { + match option.name { + "gif87" => { + if let NAValue::Bool(bval) = option.value { + self.gif87 = bval; + } + }, + "single" => { + if let NAValue::Bool(bval) = option.value { + self.single = bval; + } + }, + "loops" => { + if let NAValue::Int(ival) = option.value { + self.nloops = ival as u16; + } + }, + _ => {}, + }; + } + } + } + } + fn query_option_value(&self, name: &str) -> Option { + match name { + "gif87" => Some(NAValue::Bool(self.gif87)), + "single" => Some(NAValue::Bool(self.single)), + "loops" => Some(NAValue::Int(i64::from(self.nloops))), + _ => None, + } + } +} + +pub struct GIFMuxerCreator {} + +impl MuxerCreator for GIFMuxerCreator { + fn new_muxer<'a>(&self, bw: &'a mut ByteWriter<'a>) -> Box + 'a> { + Box::new(GIFMuxer::new(bw)) + } + fn get_name(&self) -> &'static str { "gif" } + fn get_capabilities(&self) -> MuxerCapabilities { MuxerCapabilities::SingleVideo("gif") } +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::*; + use nihav_core::demuxers::*; + use nihav_core::muxers::*; + use nihav_codec_support::test::enc_video::*; + use crate::*; + + #[test] + fn test_gif_muxer() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + // sample: https://samples.mplayerhq.hu/image-samples/GIF/3D.gif + let dec_config = DecoderTestParams { + demuxer: "gif", + in_name: "assets/Misc/3D.gif", + limit: None, + stream_type: StreamType::None, + dmx_reg, dec_reg: RegisteredDecoders::new(), + }; + let mut mux_reg = RegisteredMuxers::new(); + generic_register_all_muxers(&mut mux_reg); + /*let enc_config = EncoderTestParams { + muxer: "gif", + enc_name: "", + out_name: "muxed.gif", + mux_reg, enc_reg: RegisteredEncoders::new(), + }; + test_remuxing(&dec_config, &enc_config);*/ + test_remuxing_md5(&dec_config, "gif", &mux_reg, + [0x7192b724, 0x2bc4fd05, 0xaa65f268, 0x3929e8bf]); + } +}