1use std::io::Cursor;
9
10use rff_codec::{Codec, CodecRegistry, Decoder, Encoder};
11use rff_core::{Error, Frame, MediaType, Packet, PixelFormat, Result, VideoFrame};
12use rusty_webp::{ColorType, WebPDecoder, WebPEncoder};
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(
103 VideoFrame {
104 width: w,
105 height: h,
106 format: PixelFormat::Yuv420p,
107 strides: vec![yuv.y_stride, yuv.uv_stride, yuv.uv_stride],
108 planes: vec![yuv.y, yuv.u, yuv.v],
109 pts: None,
110 },
111 )]));
112 }
113 }
114
115 let channels = if has_alpha { 4 } else { 3 };
116
117 let mut buf = vec![0u8; w as usize * h as usize * channels];
118 decoder
119 .read_image(&mut buf)
120 .map_err(|e| Error::invalid(format!("webp decode: {e}")))?;
121
122 let (format, stride) = if has_alpha {
123 (PixelFormat::Rgba, w as usize * 4)
124 } else {
125 (PixelFormat::Rgb24, w as usize * 3)
126 };
127 Ok(std::collections::VecDeque::from([Frame::Video(
128 VideoFrame {
129 width: w,
130 height: h,
131 format,
132 planes: vec![buf],
133 strides: vec![stride],
134 pts: None,
135 },
136 )]))
137}
138
139#[derive(Default)]
140struct WebpEncoder {
141 packet: Option<Packet>,
142 eof: bool,
143}
144
145impl Encoder for WebpEncoder {
146 fn accepted_pixel_formats(&self) -> Option<Vec<PixelFormat>> {
149 Some(vec![PixelFormat::Rgb24, PixelFormat::Rgba])
150 }
151
152 fn send_frame(&mut self, frame: &Frame) -> Result<()> {
153 let vf = match frame {
154 Frame::Video(v) => v,
155 Frame::Audio(_) => {
156 return Err(Error::unsupported(
157 "webp encode: audio frame on an image codec",
158 ))
159 }
160 };
161 self.packet = Some(Packet::from_data(0, encode_webp(vf)?));
162 Ok(())
163 }
164
165 fn receive_packet(&mut self) -> Result<Packet> {
166 if let Some(packet) = self.packet.take() {
167 return Ok(packet);
168 }
169 if self.eof {
170 Err(Error::Eof)
171 } else {
172 Err(Error::Again)
173 }
174 }
175
176 fn flush(&mut self) {
177 self.eof = true;
178 }
179}
180
181fn encode_webp(vf: &VideoFrame) -> Result<Vec<u8>> {
182 let (color, channels) = match vf.format {
183 PixelFormat::Rgb24 => (ColorType::Rgb8, 3usize),
184 PixelFormat::Rgba => (ColorType::Rgba8, 4usize),
185 other => {
186 return Err(Error::unsupported(format!(
187 "webp encode: needs rgb24/rgba, got `{}` (convert with -vf format=rgb24)",
188 other.name()
189 )))
190 }
191 };
192 let (w, h) = (vf.width as usize, vf.height as usize);
193 let row = w * channels;
194 let stride = vf.strides[0];
195 let packed: Vec<u8> = if stride == row {
196 vf.planes[0].clone()
197 } else {
198 let mut p = Vec::with_capacity(row * h);
199 for j in 0..h {
200 p.extend_from_slice(&vf.planes[0][j * stride..j * stride + row]);
201 }
202 p
203 };
204
205 let mut out = Vec::new();
206 let mut encoder = WebPEncoder::new(&mut out);
207 if std::env::var("RFF_WEBP_BACKREFS").as_deref() == Ok("0") {
211 let mut params = rusty_webp::EncoderParams::default();
212 params.use_backrefs = false;
213 encoder.set_params(params);
214 }
215 encoder
216 .encode(&packed, vf.width, vf.height, color)
217 .map_err(|e| Error::invalid(format!("webp encode: {e}")))?;
218 Ok(out)
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn webp_lossless_encode_decode_roundtrips() {
227 let (w, h) = (32u32, 24u32);
228 let (wi, hi) = (w as usize, h as usize);
229 let mut rgb = vec![0u8; wi * hi * 3];
230 for j in 0..hi {
231 for i in 0..wi {
232 let o = (j * wi + i) * 3;
233 rgb[o] = (i * 255 / (wi - 1)) as u8;
234 rgb[o + 1] = (j * 255 / (hi - 1)) as u8;
235 rgb[o + 2] = 64;
236 }
237 }
238 let src = VideoFrame {
239 width: w,
240 height: h,
241 format: PixelFormat::Rgb24,
242 planes: vec![rgb.clone()],
243 strides: vec![wi * 3],
244 pts: None,
245 };
246
247 let bytes = encode_webp(&src).unwrap();
248 assert_eq!(&bytes[0..4], b"RIFF");
249 assert_eq!(&bytes[8..12], b"WEBP");
250
251 let Frame::Video(decoded) = decode_webp(&bytes).unwrap().pop_front().unwrap() else {
252 unreachable!()
253 };
254 assert_eq!((decoded.width, decoded.height), (w, h));
255 let got_rgb: Vec<u8> = match decoded.format {
257 PixelFormat::Rgb24 => decoded.planes[0].clone(),
258 PixelFormat::Rgba => decoded.planes[0]
259 .chunks_exact(4)
260 .flat_map(|p| p[0..3].to_vec())
261 .collect(),
262 other => panic!("unexpected format {other:?}"),
263 };
264 assert_eq!(got_rgb, rgb);
265 }
266}