1#![doc = include_str!("../README.md")]
2
3use std::io::{Cursor, Read, Seek};
4use std::ops::Range;
5use std::slice::SliceIndex;
6
7use gufo_common::error::ErrorWithData;
8use gufo_common::image::ImageMetadata;
9
10pub const RIFF_MAGIC_BYTES: &[u8] = b"RIFF";
11pub const WEBP_MAGIC_BYTES: &[u8] = b"WEBP";
12
13#[derive(Debug, Clone)]
14pub struct WebP {
15 data: Vec<u8>,
16 chunks: Vec<RawChunk>,
17}
18
19impl ImageMetadata for WebP {
20 fn exif(&self) -> Vec<Vec<u8>> {
21 let Some(exif) = self.exif_data() else {
22 return vec![];
23 };
24
25 vec![exif.to_vec()]
26 }
27}
28
29impl WebP {
31 pub fn new(data: Vec<u8>) -> Result<Self, ErrorWithData<Error>> {
35 match Self::find_chunks(&data) {
36 Ok(chunks) => Ok(Self { chunks, data }),
37 Err(err) => Err(ErrorWithData::new(err, data)),
38 }
39 }
40
41 pub fn is_filetype(data: &[u8]) -> bool {
42 data.starts_with(RIFF_MAGIC_BYTES) && data.get(8..12) == Some(WEBP_MAGIC_BYTES)
43 }
44
45 pub fn into_inner(self) -> Vec<u8> {
46 self.data
47 }
48
49 pub fn get(&self, index: impl SliceIndex<[u8], Output = [u8]>) -> Option<&[u8]> {
50 self.data.get(index)
51 }
52
53 pub fn chunks(&self) -> Vec<Chunk<'_>> {
55 self.chunks.iter().map(|x| x.chunk(self)).collect()
56 }
57
58 fn exif_data(&self) -> Option<&[u8]> {
59 self.chunks
60 .iter()
61 .find(|x| x.four_cc == FourCC::EXIF)
62 .and_then(|x| self.get(x.payload.clone()))
63 }
64
65 fn find_chunks(data: &[u8]) -> Result<Vec<RawChunk>, Error> {
67 let mut cur = Cursor::new(data);
68
69 let riff_magic_bytes = &mut [0; WEBP_MAGIC_BYTES.len()];
71 cur.read_exact(riff_magic_bytes)
72 .map_err(|_| Error::UnexpectedEof)?;
73 if riff_magic_bytes != RIFF_MAGIC_BYTES {
74 return Err(Error::RiffMagicBytesMissing(*riff_magic_bytes));
75 }
76
77 let file_length_data = &mut [0; 4];
79 cur.read_exact(file_length_data)
80 .map_err(|_| Error::UnexpectedEof)?;
81 let file_length = u32::from_le_bytes(*file_length_data);
82
83 let webp_magic_bytes = &mut [0; WEBP_MAGIC_BYTES.len()];
85 cur.read_exact(webp_magic_bytes)
86 .map_err(|_| Error::UnexpectedEof)?;
87 if webp_magic_bytes != WEBP_MAGIC_BYTES {
88 return Err(Error::WebpMagicBytesMissing(*webp_magic_bytes));
89 }
90
91 let mut chunks = Vec::new();
92 loop {
93 let four_cc_data = &mut [0; 4];
95 cur.read_exact(four_cc_data)
96 .map_err(|_| Error::UnexpectedEof)?;
97 let four_cc = FourCC::from(u32::from_le_bytes(*four_cc_data));
98
99 let size_data = &mut [0; 4];
101 cur.read_exact(size_data)
102 .map_err(|_| Error::UnexpectedEof)?;
103 let size = u32::from_le_bytes(*size_data);
104
105 let payload_start: usize = cur
107 .position()
108 .try_into()
109 .map_err(|_| Error::PositionTooLarge)?;
110 let payload_end = payload_start
111 .checked_add(size as usize)
112 .ok_or(Error::PositionTooLarge)?;
113 let payload = payload_start..payload_end;
114
115 let chunk = RawChunk { four_cc, payload };
116
117 cur.set_position(payload_end as u64);
119
120 if size % 2 != 0 {
122 cur.seek(std::io::SeekFrom::Current(1))
123 .map_err(|_| Error::UnexpectedEof)?;
124 }
125
126 chunks.push(chunk);
127
128 if cur.position() >= file_length.into() {
129 break;
130 }
131 }
132
133 Ok(chunks)
134 }
135}
136
137#[derive(Debug, Clone)]
138pub struct RawChunk {
139 four_cc: FourCC,
140 payload: Range<usize>,
141}
142
143impl RawChunk {
144 fn chunk<'a>(&self, webp: &'a WebP) -> Chunk<'a> {
145 Chunk {
146 four_cc: self.four_cc,
147 payload: self.payload.clone(),
148 webp,
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
154pub struct Chunk<'a> {
155 four_cc: FourCC,
156 payload: Range<usize>,
157 webp: &'a WebP,
158}
159
160impl<'a> Chunk<'a> {
161 pub fn four_cc(&self) -> FourCC {
162 self.four_cc
163 }
164
165 pub fn payload(&self) -> &[u8] {
166 self.webp
167 .data
168 .get(self.payload.clone())
169 .expect("Unreachable: Chunk must be part of the data")
170 }
171}
172
173#[derive(Debug, Clone, thiserror::Error)]
174pub enum Error {
175 #[error("RIFF magic bytes missing: {0:?}")]
176 RiffMagicBytesMissing([u8; 4]),
177 #[error("WEBP magic bytes missing: {0:?}")]
178 WebpMagicBytesMissing([u8; 4]),
179 #[error("Unexpected end of file")]
180 UnexpectedEof,
181 #[error("Position too large")]
182 PositionTooLarge,
183}
184
185gufo_common::utils::convertible_enum!(
186 #[repr(u32)]
187 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
188 #[non_exhaustive]
189 #[allow(non_camel_case_types)]
190 pub enum FourCC {
195 VP8X = b(b"VP8X"),
197 ICCP = b(b"ICCP"),
199 ANIM = b(b"ANIM"),
201
202 ANMF = b(b"ANMF"),
204 ALPH = b(b"ALPH"),
206 VP8 = b(b"VP8 "),
208 VP8L = b(b"VP8L"),
210
211 EXIF = b(b"EXIF"),
212 XMP = b(b"XMP "),
213 }
214);
215
216impl FourCC {
217 pub fn bytes(self) -> [u8; 4] {
219 u32::to_le_bytes(self.into())
220 }
221}
222
223const fn b(d: &[u8; 4]) -> u32 {
225 u32::from_le_bytes(*d)
226}