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