1use std::io::Cursor;
9
10use image_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 frame: Option<Frame>,
29 eof: bool,
30}
31
32impl Decoder for WebpDecoder {
33 fn send_packet(&mut self, packet: &Packet) -> Result<()> {
34 self.frame = Some(decode_webp(&packet.data)?);
35 Ok(())
36 }
37
38 fn receive_frame(&mut self) -> Result<Frame> {
39 if let Some(frame) = self.frame.take() {
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<Frame> {
55 let mut decoder = WebPDecoder::new(Cursor::new(data))
56 .map_err(|e| Error::invalid(format!("webp decode: {e}")))?;
57 let (w, h) = decoder.dimensions();
58 let has_alpha = decoder.has_alpha();
59 let channels = if has_alpha { 4 } else { 3 };
60
61 let mut buf = vec![0u8; w as usize * h as usize * channels];
62 decoder
63 .read_image(&mut buf)
64 .map_err(|e| Error::invalid(format!("webp decode: {e}")))?;
65
66 let (format, stride) = if has_alpha {
67 (PixelFormat::Rgba, w as usize * 4)
68 } else {
69 (PixelFormat::Rgb24, w as usize * 3)
70 };
71 Ok(Frame::Video(VideoFrame {
72 width: w,
73 height: h,
74 format,
75 planes: vec![buf],
76 strides: vec![stride],
77 pts: None,
78 }))
79}
80
81#[derive(Default)]
82struct WebpEncoder {
83 packet: Option<Packet>,
84 eof: bool,
85}
86
87impl Encoder for WebpEncoder {
88 fn accepted_pixel_formats(&self) -> Option<Vec<PixelFormat>> {
91 Some(vec![PixelFormat::Rgb24, PixelFormat::Rgba])
92 }
93
94 fn send_frame(&mut self, frame: &Frame) -> Result<()> {
95 let vf = match frame {
96 Frame::Video(v) => v,
97 Frame::Audio(_) => {
98 return Err(Error::unsupported(
99 "webp encode: audio frame on an image codec",
100 ))
101 }
102 };
103 self.packet = Some(Packet::from_data(0, encode_webp(vf)?));
104 Ok(())
105 }
106
107 fn receive_packet(&mut self) -> Result<Packet> {
108 if let Some(packet) = self.packet.take() {
109 return Ok(packet);
110 }
111 if self.eof {
112 Err(Error::Eof)
113 } else {
114 Err(Error::Again)
115 }
116 }
117
118 fn flush(&mut self) {
119 self.eof = true;
120 }
121}
122
123fn encode_webp(vf: &VideoFrame) -> Result<Vec<u8>> {
124 let (color, channels) = match vf.format {
125 PixelFormat::Rgb24 => (ColorType::Rgb8, 3usize),
126 PixelFormat::Rgba => (ColorType::Rgba8, 4usize),
127 other => {
128 return Err(Error::unsupported(format!(
129 "webp encode: needs rgb24/rgba, got `{}` (convert with -vf format=rgb24)",
130 other.name()
131 )))
132 }
133 };
134 let (w, h) = (vf.width as usize, vf.height as usize);
135 let row = w * channels;
136 let stride = vf.strides[0];
137 let packed: Vec<u8> = if stride == row {
138 vf.planes[0].clone()
139 } else {
140 let mut p = Vec::with_capacity(row * h);
141 for j in 0..h {
142 p.extend_from_slice(&vf.planes[0][j * stride..j * stride + row]);
143 }
144 p
145 };
146
147 let mut out = Vec::new();
148 WebPEncoder::new(&mut out)
149 .encode(&packed, vf.width, vf.height, color)
150 .map_err(|e| Error::invalid(format!("webp encode: {e}")))?;
151 Ok(out)
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn webp_lossless_encode_decode_roundtrips() {
160 let (w, h) = (32u32, 24u32);
161 let (wi, hi) = (w as usize, h as usize);
162 let mut rgb = vec![0u8; wi * hi * 3];
163 for j in 0..hi {
164 for i in 0..wi {
165 let o = (j * wi + i) * 3;
166 rgb[o] = (i * 255 / (wi - 1)) as u8;
167 rgb[o + 1] = (j * 255 / (hi - 1)) as u8;
168 rgb[o + 2] = 64;
169 }
170 }
171 let src = VideoFrame {
172 width: w,
173 height: h,
174 format: PixelFormat::Rgb24,
175 planes: vec![rgb.clone()],
176 strides: vec![wi * 3],
177 pts: None,
178 };
179
180 let bytes = encode_webp(&src).unwrap();
181 assert_eq!(&bytes[0..4], b"RIFF");
182 assert_eq!(&bytes[8..12], b"WEBP");
183
184 let Frame::Video(decoded) = decode_webp(&bytes).unwrap() else {
185 unreachable!()
186 };
187 assert_eq!((decoded.width, decoded.height), (w, h));
188 let got_rgb: Vec<u8> = match decoded.format {
190 PixelFormat::Rgb24 => decoded.planes[0].clone(),
191 PixelFormat::Rgba => decoded.planes[0]
192 .chunks_exact(4)
193 .flat_map(|p| p[0..3].to_vec())
194 .collect(),
195 other => panic!("unexpected format {other:?}"),
196 };
197 assert_eq!(got_rgb, rgb);
198 }
199}