GIF support
[nihav.git] / nihav-commonfmt / src / muxers / gif.rs
CommitLineData
fc39649d
KS
1use nihav_core::muxers::*;
2
3struct GIFMuxer<'a> {
4 bw: &'a mut ByteWriter<'a>,
5 single: bool,
6 gif87: bool,
7 pal_written: bool,
8 nloops: u16,
9}
10
11impl<'a> GIFMuxer<'a> {
12 fn new(bw: &'a mut ByteWriter<'a>) -> Self {
13 Self {
14 bw,
15 single: false,
16 gif87: false,
17 pal_written: false,
18 nloops: 0,
19 }
20 }
21 fn write_pal(&mut self, pal: &[u8; 1024]) -> MuxerResult<()> {
22 let mut nclr = 256;
23 for quad in pal.chunks_exact(4).rev() {
24 if quad[0] == 0 && quad[1] == 0 && quad[2] == 0 {
25 nclr -= 1;
26 } else {
27 break;
28 }
29 }
30 let mut pal_bits = 1;
31 while (1 << pal_bits) < nclr {
32 pal_bits += 1;
33 }
34 self.bw.write_byte(0xF0 | (pal_bits - 1))?;
35 self.bw.write_byte(0)?; // background colour index
36 self.bw.write_byte(0)?; // aspect ratio
37 for quad in pal.chunks_exact(4).take(1 << pal_bits) {
38 self.bw.write_buf(&quad[..3])?;
39 }
40 Ok(())
41 }
42}
43
44impl<'a> MuxCore<'a> for GIFMuxer<'a> {
45 fn create(&mut self, strmgr: &StreamManager) -> MuxerResult<()> {
46 if strmgr.get_num_streams() != 1 {
47 return Err(MuxerError::InvalidArgument);
48 }
49 let vstr = strmgr.get_stream(0).unwrap();
50 if vstr.get_media_type() != StreamType::Video {
51 return Err(MuxerError::UnsupportedFormat);
52 }
53 let info = vstr.get_info();
54 let vinfo = info.get_properties().get_video_info().unwrap();
55 if vinfo.width > 65535 || vinfo.height > 65535 || !vinfo.format.palette {
56 return Err(MuxerError::UnsupportedFormat);
57 }
58
59 if self.gif87 {
60 self.single = true;
61 self.bw.write_buf(b"GIF87a")?;
62 } else {
63 self.bw.write_buf(b"GIF89a")?;
64 }
65 self.bw.write_u16le(vinfo.width as u16)?;
66 self.bw.write_u16le(vinfo.height as u16)?;
67
68 Ok(())
69 }
70 fn mux_frame(&mut self, strmgr: &StreamManager, pkt: NAPacket) -> MuxerResult<()> {
71 if self.bw.tell() == 0 {
72 return Err(MuxerError::NotCreated);
73 }
74 if !self.pal_written {
75 let info = strmgr.get_stream(0).unwrap().get_info();
76 let mut tr_idx = None;
77 if let Some(ref edata) = info.get_extradata() {
78 if edata.len() == 1 {
79 tr_idx = Some(edata[0]);
80 } else if edata.len() >= 3 {
81 self.bw.write_buf(edata)?;
82 self.pal_written = true;
83 }
84 }
85 if !self.pal_written {
86 let mut pal_found = false;
87 for sdata in pkt.side_data.iter() {
88 if let NASideData::Palette(_, ref pal) = sdata {
89 self.write_pal(pal,)?;
90 pal_found = true;
91 break;
92 }
93 }
94 if !pal_found {
95 return Err(MuxerError::InvalidArgument);
96 }
97 }
98 self.pal_written = true;
99
100 if !self.single {
101 let vstr = strmgr.get_stream(0).unwrap();
102
103 let delay = NATimeInfo::ts_to_time(1, 100, vstr.tb_num, vstr.tb_den) as u16;
104 self.bw.write_byte(0x21)?; // graphic control
105 self.bw.write_byte(0xF9)?; // graphic control extension
106 self.bw.write_byte(4)?; // block size
107 self.bw.write_byte(if tr_idx.is_some() { 1 } else { 0 })?; // flags
108 self.bw.write_u16le(delay)?;
109 self.bw.write_byte(tr_idx.unwrap_or(0))?; // transparent colour index
110 self.bw.write_byte(0x00)?; // block terminator
111
112 self.bw.write_byte(0x21)?; // graphic control
113 self.bw.write_byte(0xFF)?; // application extension
114 let app_id = b"NETSCAPE2.0";
115 self.bw.write_byte(app_id.len() as u8)?;
116 self.bw.write_buf(app_id)?;
117 self.bw.write_byte(3)?; // application data block length
118 self.bw.write_byte(0x01)?;
119 self.bw.write_u16le(self.nloops)?;
120 self.bw.write_byte(0x00)?; // block terminator
121 }
122 } else if self.single { // just one frame is expected
123 return Err(MuxerError::InvalidArgument);
124 }
125
126 // buffer is supposed to have all the data starting from image descriptor
127 let src = pkt.get_buffer();
128 self.bw.write_buf(&src)?;
129 Ok(())
130 }
131 fn flush(&mut self) -> MuxerResult<()> {
132 Ok(())
133 }
134 fn end(&mut self) -> MuxerResult<()> {
135 self.bw.write_byte(0x3B)?; // GIF terminator
136 Ok(())
137 }
138}
139
140const MUXER_OPTS: &[NAOptionDefinition] = &[
141 NAOptionDefinition {
142 name: "gif87", description: "Create GIF87 image",
143 opt_type: NAOptionDefinitionType::Bool },
144 NAOptionDefinition {
145 name: "single", description: "Create single image",
146 opt_type: NAOptionDefinitionType::Bool },
147 NAOptionDefinition {
148 name: "loops", description: "Number of times to loop the animation",
149 opt_type: NAOptionDefinitionType::Int(Some(0), Some(65535)) },
150];
151
152impl<'a> NAOptionHandler for GIFMuxer<'a> {
153 fn get_supported_options(&self) -> &[NAOptionDefinition] { MUXER_OPTS }
154 fn set_options(&mut self, options: &[NAOption]) {
155 for option in options.iter() {
156 for opt_def in MUXER_OPTS.iter() {
157 if opt_def.check(option).is_ok() {
158 match option.name {
159 "gif87" => {
160 if let NAValue::Bool(bval) = option.value {
161 self.gif87 = bval;
162 }
163 },
164 "single" => {
165 if let NAValue::Bool(bval) = option.value {
166 self.single = bval;
167 }
168 },
169 "loops" => {
170 if let NAValue::Int(ival) = option.value {
171 self.nloops = ival as u16;
172 }
173 },
174 _ => {},
175 };
176 }
177 }
178 }
179 }
180 fn query_option_value(&self, name: &str) -> Option<NAValue> {
181 match name {
182 "gif87" => Some(NAValue::Bool(self.gif87)),
183 "single" => Some(NAValue::Bool(self.single)),
184 "loops" => Some(NAValue::Int(i64::from(self.nloops))),
185 _ => None,
186 }
187 }
188}
189
190pub struct GIFMuxerCreator {}
191
192impl MuxerCreator for GIFMuxerCreator {
193 fn new_muxer<'a>(&self, bw: &'a mut ByteWriter<'a>) -> Box<dyn MuxCore<'a> + 'a> {
194 Box::new(GIFMuxer::new(bw))
195 }
196 fn get_name(&self) -> &'static str { "gif" }
197 fn get_capabilities(&self) -> MuxerCapabilities { MuxerCapabilities::SingleVideo("gif") }
198}
199
200#[cfg(test)]
201mod test {
202 use nihav_core::codecs::*;
203 use nihav_core::demuxers::*;
204 use nihav_core::muxers::*;
205 use nihav_codec_support::test::enc_video::*;
206 use crate::*;
207
208 #[test]
209 fn test_gif_muxer() {
210 let mut dmx_reg = RegisteredDemuxers::new();
211 generic_register_all_demuxers(&mut dmx_reg);
212 // sample: https://samples.mplayerhq.hu/image-samples/GIF/3D.gif
213 let dec_config = DecoderTestParams {
214 demuxer: "gif",
215 in_name: "assets/Misc/3D.gif",
216 limit: None,
217 stream_type: StreamType::None,
218 dmx_reg, dec_reg: RegisteredDecoders::new(),
219 };
220 let mut mux_reg = RegisteredMuxers::new();
221 generic_register_all_muxers(&mut mux_reg);
222 /*let enc_config = EncoderTestParams {
223 muxer: "gif",
224 enc_name: "",
225 out_name: "muxed.gif",
226 mux_reg, enc_reg: RegisteredEncoders::new(),
227 };
228 test_remuxing(&dec_config, &enc_config);*/
229 test_remuxing_md5(&dec_config, "gif", &mux_reg,
230 [0x7192b724, 0x2bc4fd05, 0xaa65f268, 0x3929e8bf]);
231 }
232}