1use std::io::Cursor;
9
10use rusty_webp::{ColorType, WebPDecoder, WebPEncoder};
11use rff_codec::{Codec, CodecRegistry, Decoder, Encoder};
12use rff_core::{Error, Frame, MediaType, Packet, PixelFormat, Result, VideoFrame};
13
14pub fn register(registry: &mut CodecRegistry) {
16 registry.register(Codec {
17 id: rff_core::CodecId::Webp,
18 name: "webp",
19 long_name: "WebP image (VP8 / VP8L)",
20 media_type: MediaType::Video,
21 decoder: Some(|| Box::new(WebpDecoder::default())),
22 encoder: Some(|| Box::new(WebpEncoder::default())),
23 });
24}
25
26#[derive(Default)]
27struct WebpDecoder {
28 frames: std::collections::VecDeque<Frame>,
29 eof: bool,
30}
31
32impl Decoder for WebpDecoder {
33 fn send_packet(&mut self, packet: &Packet) -> Result<()> {
34 self.frames = decode_webp(&packet.data)?;
35 Ok(())
36 }
37
38 fn receive_frame(&mut self) -> Result<Frame> {
39 if let Some(frame) = self.frames.pop_front() {
40 return Ok(frame);
41 }
42 if self.eof {
43 Err(Error::Eof)
44 } else {
45 Err(Error::Again)
46 }
47 }
48
49 fn flush(&mut self) {
50 self.eof = true;
51 }
52}
53
54fn decode_webp(data: &[u8]) -> Result<std::collections::VecDeque<Frame>> {
55 let mut options = rusty_webp::WebPDecodeOptions::default();
59 if std::env::var("RFF_WEBP_UPSAMPLE").as_deref() == Ok("simple") {
60 options.lossy_upsampling = rusty_webp::UpsamplingMethod::Simple;
61 }
62 let mut decoder = WebPDecoder::new_with_options(Cursor::new(data), options)
63 .map_err(|e| Error::invalid(format!("webp decode: {e}")))?;
64 let (w, h) = decoder.dimensions();
65 let has_alpha = decoder.has_alpha();
66
67 if decoder.is_animated() {
72 let mut frames = std::collections::VecDeque::new();
73 let mut pts_ms = 0i64;
74 for _ in 0..decoder.num_frames() {
75 let mut buf = vec![0u8; w as usize * h as usize * 4];
76 let duration = decoder
77 .read_frame(&mut buf)
78 .map_err(|e| Error::invalid(format!("webp decode (animation): {e}")))?;
79 frames.push_back(Frame::Video(VideoFrame {
80 width: w,
81 height: h,
82 format: PixelFormat::Rgba,
83 planes: vec![buf],
84 strides: vec![w as usize * 4],
85 pts: Some(pts_ms),
86 }));
87 pts_ms += i64::from(duration);
88 }
89 return Ok(frames);
90 }
91
92 if std::env::var("RFF_WEBP_UPSAMPLE").is_err() {
98 if let Some(yuv) = decoder
99 .read_yuv420()
100 .map_err(|e| Error::invalid(format!("webp decode: {e}")))?
101 {
102 return Ok(std::collections::VecDeque::from([Frame::Video(VideoFrame {
103 width: w,
104 height: h,
105 format: PixelFormat::Yuv420p,
106 strides: vec![yuv.y_stride, yuv.uv_stride, yuv.uv_stride],
107 planes: vec![yuv.y, yuv.u, yuv.v],
108 pts: None,
109 })]));
110 }
111 }
112
113 let channels = if has_alpha { 4 } else { 3 };
114
115 let mut buf = vec![0u8; w as usize * h as usize * channels];
116 decoder
117 .read_image(&mut buf)
118 .map_err(|e| Error::invalid(format!("webp decode: {e}")))?;
119
120 let (format, stride) = if has_alpha {
121 (PixelFormat::Rgba, w as usize * 4)
122 } else {
123 (PixelFormat::Rgb24, w as usize * 3)
124 };
125 Ok(std::collections::VecDeque::from([Frame::Video(VideoFrame {
126 width: w,
127 height: h,
128 format,
129 planes: vec![buf],
130 strides: vec![stride],
131 pts: None,
132 })]))
133}
134
135#[derive(Default)]
136struct WebpEncoder {
137 packet: Option<Packet>,
138 eof: bool,
139}
140
141impl Encoder for WebpEncoder {
142 fn accepted_pixel_formats(&self) -> Option<Vec<PixelFormat>> {
145 Some(vec![PixelFormat::Rgb24, PixelFormat::Rgba])
146 }
147
148 fn send_frame(&mut self, frame: &Frame) -> Result<()> {
149 let vf = match frame {
150 Frame::Video(v) => v,
151 Frame::Audio(_) => {
152 return Err(Error::unsupported(
153 "webp encode: audio frame on an image codec",
154 ))
155 }
156 };
157 self.packet = Some(Packet::from_data(0, encode_webp(vf)?));
158 Ok(())
159 }
160
161 fn receive_packet(&mut self) -> Result<Packet> {
162 if let Some(packet) = self.packet.take() {
163 return Ok(packet);
164 }
165 if self.eof {
166 Err(Error::Eof)
167 } else {
168 Err(Error::Again)
169 }
170 }
171
172 fn flush(&mut self) {
173 self.eof = true;
174 }
175}
176
177fn encode_webp(vf: &VideoFrame) -> Result<Vec<u8>> {
178 let (color, channels) = match vf.format {
179 PixelFormat::Rgb24 => (ColorType::Rgb8, 3usize),
180 PixelFormat::Rgba => (ColorType::Rgba8, 4usize),
181 other => {
182 return Err(Error::unsupported(format!(
183 "webp encode: needs rgb24/rgba, got `{}` (convert with -vf format=rgb24)",
184 other.name()
185 )))
186 }
187 };
188 let (w, h) = (vf.width as usize, vf.height as usize);
189 let row = w * channels;
190 let stride = vf.strides[0];
191 let packed: Vec<u8> = if stride == row {
192 vf.planes[0].clone()
193 } else {
194 let mut p = Vec::with_capacity(row * h);
195 for j in 0..h {
196 p.extend_from_slice(&vf.planes[0][j * stride..j * stride + row]);
197 }
198 p
199 };
200
201 let mut out = Vec::new();
202 let mut encoder = WebPEncoder::new(&mut out);
203 if std::env::var("RFF_WEBP_BACKREFS").as_deref() == Ok("0") {
207 let mut params = rusty_webp::EncoderParams::default();
208 params.use_backrefs = false;
209 encoder.set_params(params);
210 }
211 encoder
212 .encode(&packed, vf.width, vf.height, color)
213 .map_err(|e| Error::invalid(format!("webp encode: {e}")))?;
214 Ok(out)
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn webp_lossless_encode_decode_roundtrips() {
223 let (w, h) = (32u32, 24u32);
224 let (wi, hi) = (w as usize, h as usize);
225 let mut rgb = vec![0u8; wi * hi * 3];
226 for j in 0..hi {
227 for i in 0..wi {
228 let o = (j * wi + i) * 3;
229 rgb[o] = (i * 255 / (wi - 1)) as u8;
230 rgb[o + 1] = (j * 255 / (hi - 1)) as u8;
231 rgb[o + 2] = 64;
232 }
233 }
234 let src = VideoFrame {
235 width: w,
236 height: h,
237 format: PixelFormat::Rgb24,
238 planes: vec![rgb.clone()],
239 strides: vec![wi * 3],
240 pts: None,
241 };
242
243 let bytes = encode_webp(&src).unwrap();
244 assert_eq!(&bytes[0..4], b"RIFF");
245 assert_eq!(&bytes[8..12], b"WEBP");
246
247 let Frame::Video(decoded) = decode_webp(&bytes).unwrap().pop_front().unwrap() else {
248 unreachable!()
249 };
250 assert_eq!((decoded.width, decoded.height), (w, h));
251 let got_rgb: Vec<u8> = match decoded.format {
253 PixelFormat::Rgb24 => decoded.planes[0].clone(),
254 PixelFormat::Rgba => decoded.planes[0]
255 .chunks_exact(4)
256 .flat_map(|p| p[0..3].to_vec())
257 .collect(),
258 other => panic!("unexpected format {other:?}"),
259 };
260 assert_eq!(got_rgb, rgb);
261 }
262}