Skip to content

Commit 2b562c4

Browse files
uklotzdeSerial-ATA
authored andcommitted
Split and rejoin tags for read/modify/write round trips
1 parent 19fe23c commit 2b562c4

File tree

12 files changed

+244
-120
lines changed

12 files changed

+244
-120
lines changed

src/ape/tag/mod.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::ape::tag::item::{ApeItem, ApeItemRef};
66
use crate::error::{LoftyError, Result};
77
use crate::tag::item::{ItemKey, ItemValue, TagItem};
88
use crate::tag::{Tag, TagType};
9-
use crate::traits::{Accessor, TagExt};
9+
use crate::traits::{Accessor, SplitAndRejoinTag, TagExt};
1010

1111
use std::borrow::Cow;
1212
use std::convert::TryInto;
@@ -289,8 +289,8 @@ impl TagExt for ApeTag {
289289
}
290290
}
291291

292-
impl From<ApeTag> for Tag {
293-
fn from(input: ApeTag) -> Self {
292+
impl SplitAndRejoinTag for ApeTag {
293+
fn split_tag(&mut self) -> Tag {
294294
fn split_pair(
295295
content: &str,
296296
tag: &mut Tag,
@@ -312,7 +312,7 @@ impl From<ApeTag> for Tag {
312312

313313
let mut tag = Tag::new(TagType::APE);
314314

315-
for item in input.items {
315+
for item in std::mem::take(&mut self.items) {
316316
let item_key = ItemKey::from_key(TagType::APE, item.key());
317317

318318
// The text pairs need some special treatment
@@ -321,13 +321,13 @@ impl From<ApeTag> for Tag {
321321
if split_pair(val, &mut tag, ItemKey::TrackNumber, ItemKey::TrackTotal)
322322
.is_some() =>
323323
{
324-
continue
324+
continue; // Item consumed
325325
},
326326
(ItemKey::DiscNumber | ItemKey::DiscTotal, ItemValue::Text(val))
327327
if split_pair(val, &mut tag, ItemKey::DiscNumber, ItemKey::DiscTotal)
328328
.is_some() =>
329329
{
330-
continue
330+
continue; // Item consumed
331331
},
332332
(ItemKey::MovementNumber | ItemKey::MovementTotal, ItemValue::Text(val))
333333
if split_pair(
@@ -338,36 +338,46 @@ impl From<ApeTag> for Tag {
338338
)
339339
.is_some() =>
340340
{
341-
continue
341+
continue; // Item consumed
342+
},
343+
(k, _) => {
344+
tag.items.push(TagItem::new(k, item.value));
342345
},
343-
(k, _) => tag.items.push(TagItem::new(k, item.value)),
344346
}
345347
}
346348

347349
tag
348350
}
349-
}
350-
351-
impl From<Tag> for ApeTag {
352-
fn from(input: Tag) -> Self {
353-
let mut ape_tag = Self::default();
354351

355-
for item in input.items {
352+
fn rejoin_tag(&mut self, tag: Tag) {
353+
for item in tag.items {
356354
if let Ok(ape_item) = item.try_into() {
357-
ape_tag.insert(ape_item)
355+
self.insert(ape_item)
358356
}
359357
}
360358

361-
for pic in input.pictures {
359+
for pic in tag.pictures {
362360
if let Some(key) = pic.pic_type.as_ape_key() {
363361
if let Ok(item) =
364362
ApeItem::new(key.to_string(), ItemValue::Binary(pic.as_ape_bytes()))
365363
{
366-
ape_tag.insert(item)
364+
self.insert(item)
367365
}
368366
}
369367
}
368+
}
369+
}
370+
371+
impl From<ApeTag> for Tag {
372+
fn from(mut input: ApeTag) -> Self {
373+
input.split_tag()
374+
}
375+
}
370376

377+
impl From<Tag> for ApeTag {
378+
fn from(input: Tag) -> Self {
379+
let mut ape_tag = Self::default();
380+
ape_tag.rejoin_tag(input);
371381
ape_tag
372382
}
373383
}

src/id3/v1/tag.rs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::error::{LoftyError, Result};
22
use crate::id3::v1::constants::GENRES;
33
use crate::tag::item::{ItemKey, ItemValue, TagItem};
44
use crate::tag::{Tag, TagType};
5-
use crate::traits::{Accessor, TagExt};
5+
use crate::traits::{Accessor, SplitAndRejoinTag, TagExt};
66

77
use std::borrow::Cow;
88
use std::fs::{File, OpenOptions};
@@ -238,33 +238,49 @@ impl TagExt for ID3v1Tag {
238238
}
239239
}
240240

241-
impl From<ID3v1Tag> for Tag {
242-
fn from(input: ID3v1Tag) -> Self {
243-
let mut tag = Self::new(TagType::ID3v1);
241+
impl SplitAndRejoinTag for ID3v1Tag {
242+
fn split_tag(&mut self) -> Tag {
243+
let mut tag = Tag::new(TagType::ID3v1);
244244

245-
input.title.map(|t| tag.insert_text(ItemKey::TrackTitle, t));
246-
input
247-
.artist
245+
self.title
246+
.take()
247+
.map(|t| tag.insert_text(ItemKey::TrackTitle, t));
248+
self.artist
249+
.take()
248250
.map(|a| tag.insert_text(ItemKey::TrackArtist, a));
249-
input.album.map(|a| tag.insert_text(ItemKey::AlbumTitle, a));
250-
input.year.map(|y| tag.insert_text(ItemKey::Year, y));
251-
input.comment.map(|c| tag.insert_text(ItemKey::Comment, c));
252-
253-
if let Some(t) = input.track_number {
251+
self.album
252+
.take()
253+
.map(|a| tag.insert_text(ItemKey::AlbumTitle, a));
254+
self.year.take().map(|y| tag.insert_text(ItemKey::Year, y));
255+
self.comment
256+
.take()
257+
.map(|c| tag.insert_text(ItemKey::Comment, c));
258+
259+
if let Some(t) = self.track_number.take() {
254260
tag.items.push(TagItem::new(
255261
ItemKey::TrackNumber,
256262
ItemValue::Text(t.to_string()),
257263
))
258264
}
259265

260-
if let Some(genre_index) = input.genre {
266+
if let Some(genre_index) = self.genre.take() {
261267
if let Some(genre) = GENRES.get(genre_index as usize) {
262268
tag.insert_text(ItemKey::Genre, (*genre).to_string());
263269
}
264270
}
265271

266272
tag
267273
}
274+
275+
fn rejoin_tag(&mut self, tag: Tag) {
276+
*self = tag.into();
277+
}
278+
}
279+
280+
impl From<ID3v1Tag> for Tag {
281+
fn from(mut input: ID3v1Tag) -> Self {
282+
input.split_tag()
283+
}
268284
}
269285

270286
impl From<Tag> for ID3v1Tag {

src/id3/v2/tag.rs

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use crate::error::{LoftyError, Result};
66
use crate::id3::v2::frame::FrameRef;
77
use crate::id3::v2::items::encoded_text_frame::EncodedTextFrame;
88
use crate::id3::v2::items::language_frame::LanguageFrame;
9-
use crate::picture::{Picture, PictureType};
9+
use crate::picture::{Picture, PictureType, TOMBSTONE_PICTURE};
1010
use crate::tag::item::{ItemKey, ItemValue, TagItem};
1111
use crate::tag::{Tag, TagType};
12-
use crate::traits::{Accessor, TagExt};
12+
use crate::traits::{Accessor, SplitAndRejoinTag, TagExt};
1313
use crate::util::text::TextEncoding;
1414

1515
use std::borrow::Cow;
@@ -533,8 +533,8 @@ impl TagExt for ID3v2Tag {
533533
}
534534
}
535535

536-
impl From<ID3v2Tag> for Tag {
537-
fn from(input: ID3v2Tag) -> Self {
536+
impl SplitAndRejoinTag for ID3v2Tag {
537+
fn split_tag(&mut self) -> Tag {
538538
fn split_pair(
539539
content: &str,
540540
tag: &mut Tag,
@@ -555,13 +555,13 @@ impl From<ID3v2Tag> for Tag {
555555
Some(())
556556
}
557557

558-
let mut tag = Self::new(TagType::ID3v2);
558+
let mut tag = Tag::new(TagType::ID3v2);
559559

560-
for frame in input.frames {
561-
let id = frame.id;
560+
self.frames.retain_mut(|frame| {
561+
let id = &frame.id;
562562

563563
// The text pairs need some special treatment
564-
match (id.as_str(), frame.value) {
564+
match (id.as_str(), &mut frame.value) {
565565
("TRCK", FrameValue::Text { value: content, .. })
566566
if split_pair(
567567
&content,
@@ -571,13 +571,13 @@ impl From<ID3v2Tag> for Tag {
571571
)
572572
.is_some() =>
573573
{
574-
continue
574+
false // Frame consumed
575575
},
576576
("TPOS", FrameValue::Text { value: content, .. })
577577
if split_pair(&content, &mut tag, ItemKey::DiscNumber, ItemKey::DiscTotal)
578578
.is_some() =>
579579
{
580-
continue
580+
false // Frame consumed
581581
},
582582
("MVIN", FrameValue::Text { value: content, .. })
583583
if split_pair(
@@ -588,7 +588,7 @@ impl From<ID3v2Tag> for Tag {
588588
)
589589
.is_some() =>
590590
{
591-
continue
591+
false // Frame consumed
592592
},
593593
// Store TXXX/WXXX frames by their descriptions, rather than their IDs
594594
(
@@ -606,6 +606,7 @@ impl From<ID3v2Tag> for Tag {
606606
ItemValue::Text(c.to_string()),
607607
));
608608
}
609+
false // Frame consumed
609610
},
610611
(
611612
"WXXX",
@@ -622,6 +623,7 @@ impl From<ID3v2Tag> for Tag {
622623
ItemValue::Locator(c.to_string()),
623624
));
624625
}
626+
false // Frame consumed
625627
},
626628
(id, value) => {
627629
let item_key = ItemKey::from_key(TagType::ID3v2, id);
@@ -642,22 +644,22 @@ impl From<ID3v2Tag> for Tag {
642644
description,
643645
..
644646
}) => {
645-
if description == EMPTY_CONTENT_DESCRIPTOR {
647+
if *description == EMPTY_CONTENT_DESCRIPTOR {
646648
for c in content.split(V4_MULTI_VALUE_SEPARATOR) {
647649
tag.items.push(TagItem::new(
648650
item_key.clone(),
649651
ItemValue::Text(c.to_string()),
650652
));
651653
}
654+
return false; // Frame consumed
652655
}
653656
// ...else do not convert text frames with a non-empty content
654657
// descriptor that would otherwise unintentionally be modified
655658
// and corrupted by the incomplete conversion into a [`TagItem`].
656659
// TODO: How to convert these frames consistently and safely
657660
// such that the content descriptor is preserved during read/write
658661
// round trips?
659-
660-
continue;
662+
return true; // Keep frame
661663
},
662664
FrameValue::Text { value: content, .. } => {
663665
for c in content.split(V4_MULTI_VALUE_SEPARATOR) {
@@ -666,34 +668,34 @@ impl From<ID3v2Tag> for Tag {
666668
ItemValue::Text(c.to_string()),
667669
));
668670
}
669-
670-
continue;
671+
return false; // Frame consumed
671672
},
672673
FrameValue::URL(content)
673-
| FrameValue::UserURL(EncodedTextFrame { content, .. }) => ItemValue::Locator(content),
674+
| FrameValue::UserURL(EncodedTextFrame { content, .. }) => {
675+
ItemValue::Locator(std::mem::take(content))
676+
},
674677
FrameValue::Picture { picture, .. } => {
675-
tag.push_picture(picture);
676-
continue;
678+
tag.push_picture(std::mem::replace(picture, TOMBSTONE_PICTURE));
679+
return false; // Frame consumed
677680
},
678681
FrameValue::Popularimeter(popularimeter) => {
679682
ItemValue::Binary(popularimeter.as_bytes())
680683
},
681-
FrameValue::Binary(binary) => ItemValue::Binary(binary),
684+
FrameValue::Binary(binary) => ItemValue::Binary(std::mem::take(binary)),
682685
};
683686

684687
tag.items.push(TagItem::new(item_key, item_value));
688+
false // Frame consumed
685689
},
686690
}
687-
}
691+
});
688692

689693
tag
690694
}
691-
}
692695

693-
impl From<Tag> for ID3v2Tag {
694-
fn from(mut input: Tag) -> Self {
695-
fn join_items(input: &mut Tag, key: &ItemKey) -> String {
696-
let mut iter = input.take_strings(key);
696+
fn rejoin_tag(&mut self, mut tag: Tag) {
697+
fn join_items(tag: &mut Tag, key: &ItemKey) -> String {
698+
let mut iter = tag.take_strings(key);
697699

698700
match iter.next() {
699701
None => String::new(),
@@ -710,25 +712,22 @@ impl From<Tag> for ID3v2Tag {
710712
}
711713
}
712714

713-
let mut id3v2_tag = ID3v2Tag {
714-
frames: Vec::with_capacity(input.item_count() as usize),
715-
..ID3v2Tag::default()
716-
};
715+
self.frames.reserve(tag.item_count() as usize);
717716

718-
let artists = join_items(&mut input, &ItemKey::TrackArtist);
719-
id3v2_tag.set_artist(artists);
717+
let artists = join_items(&mut tag, &ItemKey::TrackArtist);
718+
self.set_artist(artists);
720719

721-
for item in input.items {
720+
for item in tag.items {
722721
let frame: Frame<'_> = match item.into() {
723722
Some(frame) => frame,
724723
None => continue,
725724
};
726725

727-
id3v2_tag.insert(frame);
726+
self.insert(frame);
728727
}
729728

730-
for picture in input.pictures {
731-
id3v2_tag.frames.push(Frame {
729+
for picture in tag.pictures {
730+
self.frames.push(Frame {
732731
id: FrameID::Valid(Cow::Borrowed("APIC")),
733732
value: FrameValue::Picture {
734733
encoding: TextEncoding::UTF8,
@@ -737,7 +736,19 @@ impl From<Tag> for ID3v2Tag {
737736
flags: FrameFlags::default(),
738737
})
739738
}
739+
}
740+
}
741+
742+
impl From<ID3v2Tag> for Tag {
743+
fn from(mut input: ID3v2Tag) -> Self {
744+
input.split_tag()
745+
}
746+
}
740747

748+
impl From<Tag> for ID3v2Tag {
749+
fn from(input: Tag) -> Self {
750+
let mut id3v2_tag = ID3v2Tag::default();
751+
id3v2_tag.rejoin_tag(input);
741752
id3v2_tag
742753
}
743754
}

0 commit comments

Comments
 (0)