1 use crate::storage::{PlainStorage, Storage};
2 use crate::stream::{frame, unsynch};
3 use crate::tag::{Tag, Version};
4 use crate::{Error, ErrorKind};
5 use bitflags::bitflags;
6 use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
7 use std::cmp;
8 use std::fs;
9 use std::io::{self, Read, Write};
10 use std::ops::Range;
11 use std::path::Path;
12
13 static DEFAULT_FILE_DISCARD: &[&str] = &[
14 "AENC", "ETCO", "EQUA", "MLLT", "POSS", "SYLT", "SYTC", "RVAD", "TENC", "TLEN", "TSIZ",
15 ];
16
17 bitflags! {
18 struct Flags: u8 {
19 const UNSYNCHRONISATION = 0x80; // All versions
20 const COMPRESSION = 0x40; // =ID3v2.2
21 const EXTENDED_HEADER = 0x40; // >ID3v2.3, duplicate with TAG_COMPRESSION :(
22 const EXPERIMENTAL = 0x20; // >ID3v2.3
23 const FOOTER = 0x10; // >ID3v2.4
24 }
25 }
26
27 struct Header {
28 version: Version,
29 flags: Flags,
30 tag_size: u32,
31 // TODO: Extended header.
32 }
33
34 impl Header {
size(&self) -> u6435 fn size(&self) -> u64 {
36 10 // Raw header.
37 }
38
frame_bytes(&self) -> u6439 fn frame_bytes(&self) -> u64 {
40 u64::from(self.tag_size)
41 }
42
tag_size(&self) -> u6443 fn tag_size(&self) -> u64 {
44 self.size() + self.frame_bytes()
45 }
46 }
47
48 impl Header {
decode(mut reader: impl io::Read) -> crate::Result<Header>49 fn decode(mut reader: impl io::Read) -> crate::Result<Header> {
50 let mut header = [0; 10];
51 let nread = reader.read(&mut header)?;
52 if nread < header.len() || &header[0..3] != b"ID3" {
53 return Err(Error::new(
54 ErrorKind::NoTag,
55 "reader does not contain an id3 tag",
56 ));
57 }
58
59 let (ver_major, ver_minor) = (header[3], header[4]);
60 let version = match (ver_major, ver_minor) {
61 (2, _) => Version::Id3v22,
62 (3, _) => Version::Id3v23,
63 (4, _) => Version::Id3v24,
64 (_, _) => {
65 return Err(Error::new(
66 ErrorKind::UnsupportedVersion(ver_major, ver_minor),
67 "unsupported id3 tag version",
68 ));
69 }
70 };
71 let flags = Flags::from_bits(header[5])
72 .ok_or_else(|| Error::new(ErrorKind::Parsing, "unknown tag header flags are set"))?;
73 let tag_size = unsynch::decode_u32(BigEndian::read_u32(&header[6..10]));
74
75 // compression only exists on 2.2 and conflicts with 2.3+'s extended header
76 if version == Version::Id3v22 && flags.contains(Flags::COMPRESSION) {
77 return Err(Error::new(
78 ErrorKind::UnsupportedFeature,
79 "id3v2.2 compression is not supported",
80 ));
81 }
82
83 // TODO: actually use the extended header data.
84 if flags.contains(Flags::EXTENDED_HEADER) {
85 let ext_size = unsynch::decode_u32(reader.read_u32::<BigEndian>()?) as usize;
86 // the extended header size includes itself
87 if ext_size < 6 {
88 return Err(Error::new(
89 ErrorKind::Parsing,
90 "Extended header has a minimum size of 6",
91 ));
92 }
93 let ext_remaining_size = ext_size - 4;
94 let mut ext_header = Vec::with_capacity(cmp::min(ext_remaining_size, 0xffff));
95 reader
96 .by_ref()
97 .take(ext_remaining_size as u64)
98 .read_to_end(&mut ext_header)?;
99 if flags.contains(Flags::UNSYNCHRONISATION) {
100 unsynch::decode_vec(&mut ext_header);
101 }
102 }
103
104 Ok(Header {
105 version,
106 flags,
107 tag_size,
108 })
109 }
110 }
111
decode(mut reader: impl io::Read) -> crate::Result<Tag>112 pub fn decode(mut reader: impl io::Read) -> crate::Result<Tag> {
113 let header = Header::decode(&mut reader)?;
114
115 if header.version == Version::Id3v22 {
116 // Limit the reader only to the given tag_size, don't return any more bytes after that.
117 let v2_reader = reader.take(header.frame_bytes());
118
119 if header.flags.contains(Flags::UNSYNCHRONISATION) {
120 // Unwrap all 'unsynchronized' bytes in the tag before parsing frames.
121 decode_v2_frames(unsynch::Reader::new(v2_reader))
122 } else {
123 decode_v2_frames(v2_reader)
124 }
125 } else {
126 let mut offset = 0;
127 let mut tag = Tag::new();
128 while offset < header.frame_bytes() {
129 let rs = frame::decode(
130 &mut reader,
131 header.version,
132 header.flags.contains(Flags::UNSYNCHRONISATION),
133 );
134 let v = match rs {
135 Ok(v) => v,
136 Err(err) => return Err(err.with_tag(tag)),
137 };
138 let (bytes_read, frame) = match v {
139 Some(v) => v,
140 None => break, // Padding.
141 };
142 tag.add_frame(frame);
143 offset += bytes_read as u64;
144 }
145 Ok(tag)
146 }
147 }
148
decode_v2_frames(mut reader: impl io::Read) -> crate::Result<Tag>149 pub fn decode_v2_frames(mut reader: impl io::Read) -> crate::Result<Tag> {
150 let mut tag = Tag::new();
151 // Add all frames, until either an error is thrown or there are no more frames to parse
152 // (because of EOF or a Padding).
153 loop {
154 let v = match frame::v2::decode(&mut reader) {
155 Ok(v) => v,
156 Err(err) => return Err(err.with_tag(tag)),
157 };
158 match v {
159 Some((_bytes_read, frame)) => {
160 tag.add_frame(frame);
161 }
162 None => break Ok(tag),
163 }
164 }
165 }
166
167 /// The `Encoder` may be used to encode tags.
168 #[derive(Clone, Debug)]
169 pub struct Encoder {
170 version: Version,
171 unsynchronisation: bool,
172 compression: bool,
173 file_altered: bool,
174 }
175
176 impl Encoder {
177 /// Constructs a new `Encoder` with the following configuration:
178 ///
179 /// * version is ID3v2.4
180 /// * unsynchronization is disabled due to compatibility issues
181 /// * no compression
182 /// * file is not marked as altered
new() -> Self183 pub fn new() -> Self {
184 Self {
185 version: Version::Id3v24,
186 unsynchronisation: false,
187 compression: false,
188 file_altered: false,
189 }
190 }
191
192 /// Sets the ID3 version.
version(mut self, version: Version) -> Self193 pub fn version(mut self, version: Version) -> Self {
194 self.version = version;
195 self
196 }
197
198 /// Enables or disables the unsynchronisation scheme.
199 ///
200 /// This avoids patterns that resemble MP3-frame headers from being
201 /// encoded. If you are encoding to MP3 files and wish to be compatible
202 /// with very old tools, you probably want this enabled.
unsynchronisation(mut self, unsynchronisation: bool) -> Self203 pub fn unsynchronisation(mut self, unsynchronisation: bool) -> Self {
204 self.unsynchronisation = unsynchronisation;
205 self
206 }
207
208 /// Enables or disables compression.
compression(mut self, compression: bool) -> Self209 pub fn compression(mut self, compression: bool) -> Self {
210 self.compression = compression;
211 self
212 }
213
214 /// Informs the encoder whether the file this tag belongs to has been changed.
215 ///
216 /// This subsequently discards any tags that have their File Alter Preservation bits set and
217 /// that have a relation to the file contents:
218 ///
219 /// AENC, ETCO, EQUA, MLLT, POSS, SYLT, SYTC, RVAD, TENC, TLEN, TSIZ
file_altered(mut self, file_altered: bool) -> Self220 pub fn file_altered(mut self, file_altered: bool) -> Self {
221 self.file_altered = file_altered;
222 self
223 }
224
225 /// Encodes the specified tag using the settings set in the encoder.
226 ///
227 /// Note that the plain tag is written, regardless of the original contents. To safely encode a
228 /// tag to an MP3 file, use `Encoder::encode_to_path`.
encode(&self, tag: &Tag, mut writer: impl io::Write) -> crate::Result<()>229 pub fn encode(&self, tag: &Tag, mut writer: impl io::Write) -> crate::Result<()> {
230 // remove frames which have the flags indicating they should be removed
231 let saved_frames = tag
232 .frames()
233 // Assert that by encoding, we are changing the tag. If the Tag Alter Preservation bit
234 // is set, discard the frame.
235 .filter(|frame| !frame.tag_alter_preservation())
236 // If the file this tag belongs to is updated, check for the File Alter Preservation
237 // bit.
238 .filter(|frame| !self.file_altered || !frame.file_alter_preservation())
239 // Check whether this frame is part of the set of frames that should always be
240 // discarded when the file is changed.
241 .filter(|frame| !self.file_altered || !DEFAULT_FILE_DISCARD.contains(&frame.id()));
242
243 let mut flags = Flags::empty();
244 flags.set(Flags::UNSYNCHRONISATION, self.unsynchronisation);
245 if self.version == Version::Id3v22 {
246 flags.set(Flags::COMPRESSION, self.compression);
247 }
248
249 let mut frame_data = Vec::new();
250 for frame in saved_frames {
251 frame::encode(&mut frame_data, frame, self.version, self.unsynchronisation)?;
252 }
253 // In ID3v2, Unsynchronization is applied to the whole tag data at once, not for each frame
254 // separately.
255 if self.version == Version::Id3v22 && self.unsynchronisation {
256 unsynch::encode_vec(&mut frame_data)
257 }
258 let tag_size = 10 + frame_data.len() + 1;
259 writer.write_all(b"ID3")?;
260 writer.write_all(&[self.version.minor() as u8, 0])?;
261 writer.write_u8(flags.bits())?;
262 writer.write_u32::<BigEndian>(unsynch::encode_u32(tag_size as u32))?;
263 writer.write_all(&frame_data[..])?;
264 writer.write_u8(0)?;
265 Ok(())
266 }
267
268 /// Encodes a tag and replaces any existing tag in the file pointed to by the specified path.
encode_to_path(&self, tag: &Tag, path: impl AsRef<Path>) -> crate::Result<()>269 pub fn encode_to_path(&self, tag: &Tag, path: impl AsRef<Path>) -> crate::Result<()> {
270 let mut file = fs::OpenOptions::new().read(true).write(true).open(path)?;
271 #[allow(clippy::reversed_empty_ranges)]
272 let location = locate_id3v2(&mut file)?.unwrap_or(0..0); // Create a new tag if none could be located.
273
274 let mut storage = PlainStorage::new(file, location);
275 let mut w = storage.writer()?;
276 self.encode(tag, &mut w)?;
277 w.flush()?;
278 Ok(())
279 }
280 }
281
282 impl Default for Encoder {
default() -> Self283 fn default() -> Self {
284 Self::new()
285 }
286 }
287
locate_id3v2(mut reader: impl io::Read + io::Seek) -> crate::Result<Option<Range<u64>>>288 pub fn locate_id3v2(mut reader: impl io::Read + io::Seek) -> crate::Result<Option<Range<u64>>> {
289 let header = match Header::decode(&mut reader) {
290 Ok(v) => v,
291 Err(err) => match err.kind {
292 ErrorKind::NoTag => return Ok(None),
293 _ => return Err(err),
294 },
295 };
296
297 let tag_size = header.tag_size();
298 reader.seek(io::SeekFrom::Start(tag_size))?;
299 let num_padding = reader
300 .bytes()
301 .take_while(|rs| rs.as_ref().map(|b| *b == 0x00).unwrap_or(false))
302 .count();
303 eprintln!(
304 "location: size=0x{:x} padding=0x{:x}",
305 tag_size, num_padding
306 );
307 Ok(Some(0..tag_size + num_padding as u64))
308 }
309
310 #[cfg(all(test, feature = "unstable"))]
311 mod benchmarks {
312 use super::*;
313 use std::fs;
314
315 #[bench]
read_id3v23(b: &mut test::Bencher)316 fn read_id3v23(b: &mut test::Bencher) {
317 let mut buf = Vec::new();
318 fs::File::open("testdata/id3v23.id3")
319 .unwrap()
320 .read_to_end(&mut buf)
321 .unwrap();
322 b.iter(|| {
323 decode(&mut io::Cursor::new(buf.as_slice())).unwrap();
324 });
325 }
326
327 #[bench]
read_id3v24(b: &mut test::Bencher)328 fn read_id3v24(b: &mut test::Bencher) {
329 let mut buf = Vec::new();
330 fs::File::open("testdata/id3v24.id3")
331 .unwrap()
332 .read_to_end(&mut buf)
333 .unwrap();
334 b.iter(|| {
335 decode(&mut io::Cursor::new(buf.as_slice())).unwrap();
336 });
337 }
338 }
339
340 #[cfg(test)]
341 mod tests {
342 use super::*;
343 use crate::frame::{
344 Content, Frame, Picture, PictureType, SynchronisedLyrics, SynchronisedLyricsType,
345 TimestampFormat,
346 };
347 use std::fs;
348 use std::io::{self, Read};
349
make_tag() -> Tag350 fn make_tag() -> Tag {
351 let mut tag = Tag::new();
352 tag.set_title("Title");
353 tag.set_artist("Artist");
354 tag.set_genre("Genre");
355 tag.set_duration(1337);
356 let mut image_data = Vec::new();
357 fs::File::open("testdata/image.jpg")
358 .unwrap()
359 .read_to_end(&mut image_data)
360 .unwrap();
361 tag.add_picture(Picture {
362 mime_type: "image/jpeg".to_string(),
363 picture_type: PictureType::CoverFront,
364 description: "an image".to_string(),
365 data: image_data,
366 });
367 tag.add_synchronised_lyrics(SynchronisedLyrics {
368 lang: "eng".to_string(),
369 timestamp_format: TimestampFormat::MS,
370 content_type: SynchronisedLyricsType::Lyrics,
371 content: vec![
372 (1000, "he".to_string()),
373 (1100, "llo".to_string()),
374 (1200, "world".to_string()),
375 ],
376 });
377 tag
378 }
379
380 #[test]
read_id3v22()381 fn read_id3v22() {
382 let mut file = fs::File::open("testdata/id3v22.id3").unwrap();
383 let tag: Tag = decode(&mut file).unwrap();
384 assert_eq!("Henry Frottey INTRO", tag.title().unwrap());
385 assert_eq!("Hörbuch & Gesprochene Inhalte", tag.genre().unwrap());
386 assert_eq!(1, tag.disc().unwrap());
387 assert_eq!(27, tag.total_discs().unwrap());
388 assert_eq!(2015, tag.year().unwrap());
389 assert_eq!(
390 PictureType::Other,
391 tag.pictures().nth(0).unwrap().picture_type
392 );
393 assert_eq!("", tag.pictures().nth(0).unwrap().description);
394 assert_eq!("image/jpeg", tag.pictures().nth(0).unwrap().mime_type);
395 }
396
397 #[test]
read_id3v23()398 fn read_id3v23() {
399 let mut file = fs::File::open("testdata/id3v23.id3").unwrap();
400 let tag = decode(&mut file).unwrap();
401 assert_eq!("Title", tag.title().unwrap());
402 assert_eq!("Genre", tag.genre().unwrap());
403 assert_eq!(1, tag.disc().unwrap());
404 assert_eq!(1, tag.total_discs().unwrap());
405 assert_eq!(
406 PictureType::CoverFront,
407 tag.pictures().nth(0).unwrap().picture_type
408 );
409 }
410
411 #[test]
read_id3v24()412 fn read_id3v24() {
413 let mut file = fs::File::open("testdata/id3v24.id3").unwrap();
414 let tag = decode(&mut file).unwrap();
415 assert_eq!("Title", tag.title().unwrap());
416 assert_eq!(1, tag.disc().unwrap());
417 assert_eq!(1, tag.total_discs().unwrap());
418 assert_eq!(
419 PictureType::CoverFront,
420 tag.pictures().nth(0).unwrap().picture_type
421 );
422 }
423
424 #[test]
read_id3v24_extended()425 fn read_id3v24_extended() {
426 let mut file = fs::File::open("testdata/id3v24_ext.id3").unwrap();
427 let tag = decode(&mut file).unwrap();
428 assert_eq!("Title", tag.title().unwrap());
429 assert_eq!("Genre", tag.genre().unwrap());
430 assert_eq!("Artist", tag.artist().unwrap());
431 assert_eq!("Album", tag.album().unwrap());
432 assert_eq!(2, tag.track().unwrap());
433 }
434
435 #[test]
write_id3v22()436 fn write_id3v22() {
437 let tag = make_tag();
438 let mut buffer = Vec::new();
439 Encoder::new()
440 .version(Version::Id3v22)
441 .encode(&tag, &mut buffer)
442 .unwrap();
443 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
444 assert_eq!(tag, tag_read);
445 }
446
447 #[test]
write_id3v22_unsynch()448 fn write_id3v22_unsynch() {
449 let tag = make_tag();
450 let mut buffer = Vec::new();
451 Encoder::new()
452 .unsynchronisation(true)
453 .version(Version::Id3v22)
454 .encode(&tag, &mut buffer)
455 .unwrap();
456 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
457 assert_eq!(tag, tag_read);
458 }
459
460 #[test]
write_id3v22_invalid_id()461 fn write_id3v22_invalid_id() {
462 let mut tag = make_tag();
463 tag.add_frame(Frame::with_content("XXX", Content::Unknown(vec![1, 2, 3])));
464 tag.add_frame(Frame::with_content("YYY", Content::Unknown(vec![4, 5, 6])));
465 tag.add_frame(Frame::with_content("ZZZ", Content::Unknown(vec![7, 8, 9])));
466 let mut buffer = Vec::new();
467 Encoder::new()
468 .version(Version::Id3v22)
469 .encode(&tag, &mut buffer)
470 .unwrap();
471 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
472 assert_eq!(tag, tag_read);
473 }
474
475 #[test]
write_id3v23()476 fn write_id3v23() {
477 let tag = make_tag();
478 let mut buffer = Vec::new();
479 Encoder::new()
480 .version(Version::Id3v23)
481 .encode(&tag, &mut buffer)
482 .unwrap();
483 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
484 assert_eq!(tag, tag_read);
485 }
486
487 #[test]
write_id3v23_compression()488 fn write_id3v23_compression() {
489 let tag = make_tag();
490 let mut buffer = Vec::new();
491 Encoder::new()
492 .compression(true)
493 .version(Version::Id3v23)
494 .encode(&tag, &mut buffer)
495 .unwrap();
496 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
497 assert_eq!(tag, tag_read);
498 }
499
500 #[test]
write_id3v23_unsynch()501 fn write_id3v23_unsynch() {
502 let tag = make_tag();
503 let mut buffer = Vec::new();
504 Encoder::new()
505 .unsynchronisation(true)
506 .version(Version::Id3v23)
507 .encode(&tag, &mut buffer)
508 .unwrap();
509 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
510 assert_eq!(tag, tag_read);
511 }
512
513 #[test]
write_id3v24()514 fn write_id3v24() {
515 let tag = make_tag();
516 let mut buffer = Vec::new();
517 Encoder::new()
518 .version(Version::Id3v24)
519 .encode(&tag, &mut buffer)
520 .unwrap();
521 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
522 assert_eq!(tag, tag_read);
523 }
524
525 #[test]
write_id3v24_compression()526 fn write_id3v24_compression() {
527 let tag = make_tag();
528 let mut buffer = Vec::new();
529 Encoder::new()
530 .compression(true)
531 .version(Version::Id3v24)
532 .encode(&tag, &mut buffer)
533 .unwrap();
534 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
535 assert_eq!(tag, tag_read);
536 }
537
538 #[test]
write_id3v24_unsynch()539 fn write_id3v24_unsynch() {
540 let tag = make_tag();
541 let mut buffer = Vec::new();
542 Encoder::new()
543 .unsynchronisation(true)
544 .version(Version::Id3v24)
545 .encode(&tag, &mut buffer)
546 .unwrap();
547 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
548 assert_eq!(tag, tag_read);
549 }
550
551 #[test]
write_id3v24_alter_file()552 fn write_id3v24_alter_file() {
553 let mut tag = Tag::new();
554 tag.set_duration(1337);
555
556 let mut buffer = Vec::new();
557 Encoder::new()
558 .version(Version::Id3v24)
559 .file_altered(true)
560 .encode(&tag, &mut buffer)
561 .unwrap();
562
563 let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
564 assert!(tag_read.get("TLEN").is_none());
565 }
566
567 #[test]
test_locate_id3v22()568 fn test_locate_id3v22() {
569 let file = fs::File::open("testdata/id3v22.id3").unwrap();
570 let location = locate_id3v2(file).unwrap();
571 assert_eq!(Some(0..0x0000c3ea), location);
572 }
573
574 #[test]
test_locate_id3v23()575 fn test_locate_id3v23() {
576 let file = fs::File::open("testdata/id3v23.id3").unwrap();
577 let location = locate_id3v2(file).unwrap();
578 assert_eq!(Some(0..0x00006c0a), location);
579 }
580
581 #[test]
test_locate_id3v24()582 fn test_locate_id3v24() {
583 let file = fs::File::open("testdata/id3v24.id3").unwrap();
584 let location = locate_id3v2(file).unwrap();
585 assert_eq!(Some(0..0x00006c0a), location);
586 }
587
588 #[test]
test_locate_id3v24_ext()589 fn test_locate_id3v24_ext() {
590 let file = fs::File::open("testdata/id3v24_ext.id3").unwrap();
591 let location = locate_id3v2(file).unwrap();
592 assert_eq!(Some(0..0x0000018d), location);
593 }
594
595 #[test]
test_locate_no_tag()596 fn test_locate_no_tag() {
597 let file = fs::File::open("testdata/mpeg-header").unwrap();
598 let location = locate_id3v2(file).unwrap();
599 assert_eq!(None, location);
600 }
601 }
602