symphonia_metadata/id3v2/
mod.rs
1use symphonia_core::errors::{decode_error, unsupported_error, Result};
11use symphonia_core::io::*;
12use symphonia_core::meta::{MetadataBuilder, MetadataOptions, MetadataReader, MetadataRevision};
13use symphonia_core::probe::{Descriptor, Instantiate, QueryDescriptor};
14use symphonia_core::support_metadata;
15
16use log::{info, trace, warn};
17
18mod frames;
19mod unsync;
20
21use frames::*;
22use unsync::{read_syncsafe_leq32, UnsyncStream};
23
24#[derive(Debug)]
25#[allow(clippy::enum_variant_names)]
26enum TagSizeRestriction {
27 Max128Frames1024KiB,
28 Max64Frames128KiB,
29 Max32Frames40KiB,
30 Max32Frames4KiB,
31}
32
33#[derive(Debug)]
34enum TextEncodingRestriction {
35 None,
36 Utf8OrIso88591,
37}
38
39#[derive(Debug)]
40enum TextFieldSize {
41 None,
42 Max1024Characters,
43 Max128Characters,
44 Max30Characters,
45}
46
47#[derive(Debug)]
48enum ImageEncodingRestriction {
49 None,
50 PngOrJpegOnly,
51}
52
53#[derive(Debug)]
54enum ImageSizeRestriction {
55 None,
56 LessThan256x256,
57 LessThan64x64,
58 Exactly64x64,
59}
60
61#[derive(Debug)]
62#[allow(dead_code)]
63struct Header {
64 major_version: u8,
65 minor_version: u8,
66 size: u32,
67 unsynchronisation: bool,
68 has_extended_header: bool,
69 experimental: bool,
70 has_footer: bool,
71}
72
73#[derive(Debug)]
74#[allow(dead_code)]
75struct Restrictions {
76 tag_size: TagSizeRestriction,
77 text_encoding: TextEncodingRestriction,
78 text_field_size: TextFieldSize,
79 image_encoding: ImageEncodingRestriction,
80 image_size: ImageSizeRestriction,
81}
82
83#[derive(Debug)]
84#[allow(dead_code)]
85struct ExtendedHeader {
86 padding_size: Option<u32>,
88 crc32: Option<u32>,
90 is_update: Option<bool>,
92 restrictions: Option<Restrictions>,
94}
95
96fn read_id3v2_header<B: ReadBytes>(reader: &mut B) -> Result<Header> {
98 let marker = reader.read_triple_bytes()?;
99
100 if marker != *b"ID3" {
101 return unsupported_error("id3v2: not an ID3v2 tag");
102 }
103
104 let major_version = reader.read_u8()?;
105 let minor_version = reader.read_u8()?;
106 let flags = reader.read_u8()?;
107 let size = unsync::read_syncsafe_leq32(reader, 28)?;
108
109 let mut header = Header {
110 major_version,
111 minor_version,
112 size,
113 unsynchronisation: false,
114 has_extended_header: false,
115 experimental: false,
116 has_footer: false,
117 };
118
119 if major_version == 0xff || minor_version == 0xff {
121 return decode_error("id3v2: invalid version number(s)");
122 }
123
124 if major_version < 2 || major_version > 4 {
127 return unsupported_error("id3v2: unsupported ID3v2 version");
128 }
129
130 if major_version == 2 && (flags & 0x40) != 0 {
135 return unsupported_error("id3v2: ID3v2.2 compression is not supported");
136 }
137
138 if major_version >= 2 {
141 header.unsynchronisation = flags & 0x80 != 0;
142 }
143
144 if major_version >= 3 {
145 header.has_extended_header = flags & 0x40 != 0;
146 header.experimental = flags & 0x20 != 0;
147 }
148
149 if major_version >= 4 {
150 header.has_footer = flags & 0x10 != 0;
151 }
152
153 Ok(header)
154}
155
156fn read_id3v2p3_extended_header<B: ReadBytes>(reader: &mut B) -> Result<ExtendedHeader> {
158 let size = reader.read_be_u32()?;
159 let flags = reader.read_be_u16()?;
160 let padding_size = reader.read_be_u32()?;
161
162 if !(size == 6 || size == 10) {
163 return decode_error("id3v2: invalid extended header size");
164 }
165
166 let mut header = ExtendedHeader {
167 padding_size: Some(padding_size),
168 crc32: None,
169 is_update: None,
170 restrictions: None,
171 };
172
173 if size == 10 && flags & 0x8000 != 0 {
175 header.crc32 = Some(reader.read_be_u32()?);
176 }
177
178 Ok(header)
179}
180
181fn read_id3v2p4_extended_header<B: ReadBytes>(reader: &mut B) -> Result<ExtendedHeader> {
183 let _size = read_syncsafe_leq32(reader, 28)?;
184
185 if reader.read_u8()? != 1 {
186 return decode_error("id3v2: extended flags should have a length of 1");
187 }
188
189 let flags = reader.read_u8()?;
190
191 let mut header = ExtendedHeader {
192 padding_size: None,
193 crc32: None,
194 is_update: Some(false),
195 restrictions: None,
196 };
197
198 if flags & 0x40 != 0x0 {
200 let len = reader.read_u8()?;
201 if len != 1 {
202 return decode_error("id3v2: is update extended flag has invalid size");
203 }
204
205 header.is_update = Some(true);
206 }
207
208 if flags & 0x20 != 0x0 {
210 let len = reader.read_u8()?;
211 if len != 5 {
212 return decode_error("id3v2: CRC32 extended flag has invalid size");
213 }
214
215 header.crc32 = Some(read_syncsafe_leq32(reader, 32)?);
216 }
217
218 if flags & 0x10 != 0x0 {
220 let len = reader.read_u8()?;
221 if len != 1 {
222 return decode_error("id3v2: restrictions extended flag has invalid size");
223 }
224
225 let restrictions = reader.read_u8()?;
226
227 let tag_size = match (restrictions & 0xc0) >> 6 {
228 0 => TagSizeRestriction::Max128Frames1024KiB,
229 1 => TagSizeRestriction::Max64Frames128KiB,
230 2 => TagSizeRestriction::Max32Frames40KiB,
231 3 => TagSizeRestriction::Max32Frames4KiB,
232 _ => unreachable!(),
233 };
234
235 let text_encoding = match (restrictions & 0x40) >> 5 {
236 0 => TextEncodingRestriction::None,
237 1 => TextEncodingRestriction::Utf8OrIso88591,
238 _ => unreachable!(),
239 };
240
241 let text_field_size = match (restrictions & 0x18) >> 3 {
242 0 => TextFieldSize::None,
243 1 => TextFieldSize::Max1024Characters,
244 2 => TextFieldSize::Max128Characters,
245 3 => TextFieldSize::Max30Characters,
246 _ => unreachable!(),
247 };
248
249 let image_encoding = match (restrictions & 0x04) >> 2 {
250 0 => ImageEncodingRestriction::None,
251 1 => ImageEncodingRestriction::PngOrJpegOnly,
252 _ => unreachable!(),
253 };
254
255 let image_size = match restrictions & 0x03 {
256 0 => ImageSizeRestriction::None,
257 1 => ImageSizeRestriction::LessThan256x256,
258 2 => ImageSizeRestriction::LessThan64x64,
259 3 => ImageSizeRestriction::Exactly64x64,
260 _ => unreachable!(),
261 };
262
263 header.restrictions = Some(Restrictions {
264 tag_size,
265 text_encoding,
266 text_field_size,
267 image_encoding,
268 image_size,
269 })
270 }
271
272 Ok(header)
273}
274
275fn read_id3v2_body<B: ReadBytes + FiniteStream>(
276 reader: &mut B,
277 header: &Header,
278 metadata: &mut MetadataBuilder,
279) -> Result<()> {
280 if header.has_extended_header {
282 let extended = match header.major_version {
283 3 => read_id3v2p3_extended_header(reader)?,
284 4 => read_id3v2p4_extended_header(reader)?,
285 _ => unreachable!(),
286 };
287 trace!("{:#?}", &extended);
288 }
289
290 let min_frame_size = match header.major_version {
291 2 => 6,
292 3 | 4 => 10,
293 _ => unreachable!(),
294 };
295
296 loop {
297 let frame = match header.major_version {
299 2 => read_id3v2p2_frame(reader),
300 3 => read_id3v2p3_frame(reader),
301 4 => read_id3v2p4_frame(reader),
302 _ => break,
303 }?;
304
305 match frame {
306 FrameResult::Padding => break,
308 FrameResult::Tag(tag) => {
310 metadata.add_tag(tag);
311 }
312 FrameResult::MultipleTags(multi_tags) => {
314 for tag in multi_tags {
315 metadata.add_tag(tag);
316 }
317 }
318 FrameResult::Visual(visual) => {
320 metadata.add_visual(visual);
321 }
322 FrameResult::UnsupportedFrame(ref id) => {
324 info!("unsupported frame {}", id);
325 }
326 FrameResult::InvalidData(ref id) => {
328 warn!("invalid data for {} frame", id);
329 }
330 }
331
332 if reader.bytes_available() < min_frame_size {
334 break;
335 }
336 }
337
338 Ok(())
339}
340
341pub fn read_id3v2<B: ReadBytes>(reader: &mut B, metadata: &mut MetadataBuilder) -> Result<()> {
342 let header = read_id3v2_header(reader)?;
344
345 let mut scoped = if header.unsynchronisation && header.major_version < 4 {
348 let mut unsync = UnsyncStream::new(ScopedStream::new(reader, u64::from(header.size)));
349
350 read_id3v2_body(&mut unsync, &header, metadata)?;
351
352 unsync.into_inner()
353 }
354 else {
357 let mut scoped = ScopedStream::new(reader, u64::from(header.size));
358
359 read_id3v2_body(&mut scoped, &header, metadata)?;
360
361 scoped
362 };
363
364 scoped.ignore()?;
366
367 Ok(())
368}
369
370pub mod util {
371 use symphonia_core::meta::StandardVisualKey;
372
373 pub fn apic_picture_type_to_visual_key(apic: u32) -> Option<StandardVisualKey> {
375 match apic {
376 0x01 => Some(StandardVisualKey::FileIcon),
377 0x02 => Some(StandardVisualKey::OtherIcon),
378 0x03 => Some(StandardVisualKey::FrontCover),
379 0x04 => Some(StandardVisualKey::BackCover),
380 0x05 => Some(StandardVisualKey::Leaflet),
381 0x06 => Some(StandardVisualKey::Media),
382 0x07 => Some(StandardVisualKey::LeadArtistPerformerSoloist),
383 0x08 => Some(StandardVisualKey::ArtistPerformer),
384 0x09 => Some(StandardVisualKey::Conductor),
385 0x0a => Some(StandardVisualKey::BandOrchestra),
386 0x0b => Some(StandardVisualKey::Composer),
387 0x0c => Some(StandardVisualKey::Lyricist),
388 0x0d => Some(StandardVisualKey::RecordingLocation),
389 0x0e => Some(StandardVisualKey::RecordingSession),
390 0x0f => Some(StandardVisualKey::Performance),
391 0x10 => Some(StandardVisualKey::ScreenCapture),
392 0x12 => Some(StandardVisualKey::Illustration),
393 0x13 => Some(StandardVisualKey::BandArtistLogo),
394 0x14 => Some(StandardVisualKey::PublisherStudioLogo),
395 _ => None,
396 }
397 }
398}
399
400pub struct Id3v2Reader;
401
402impl QueryDescriptor for Id3v2Reader {
403 fn query() -> &'static [Descriptor] {
404 &[support_metadata!("id3v2", "ID3v2", &[], &[], &[b"ID3"])]
405 }
406
407 fn score(_context: &[u8]) -> u8 {
408 255
409 }
410}
411
412impl MetadataReader for Id3v2Reader {
413 fn new(_options: &MetadataOptions) -> Self {
414 Id3v2Reader {}
415 }
416
417 fn read_all(&mut self, reader: &mut MediaSourceStream) -> Result<MetadataRevision> {
418 let mut builder = MetadataBuilder::new();
419 read_id3v2(reader, &mut builder)?;
420 Ok(builder.metadata())
421 }
422}