]>
Commit | Line | Data |
---|---|---|
fc39649d KS |
1 | use nihav_core::muxers::*; |
2 | ||
3 | struct GIFMuxer<'a> { | |
4 | bw: &'a mut ByteWriter<'a>, | |
5 | single: bool, | |
6 | gif87: bool, | |
7 | pal_written: bool, | |
8 | nloops: u16, | |
9 | } | |
10 | ||
11 | impl<'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 | ||
44 | impl<'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 | ||
140 | const 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 | ||
152 | impl<'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 | ||
190 | pub struct GIFMuxerCreator {} | |
191 | ||
192 | impl 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)] | |
201 | mod 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 | } |