Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support writing ID3v2 version 3. #62

Open
sagudev opened this issue Aug 11, 2022 · 10 comments
Open

Support writing ID3v2 version 3. #62

sagudev opened this issue Aug 11, 2022 · 10 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@sagudev
Copy link
Contributor

sagudev commented Aug 11, 2022

Currently all ID3v2 tags are upgraded to version 4, but this is not always desired.

@sagudev sagudev added the enhancement New feature or request label Aug 11, 2022
@Serial-ATA
Copy link
Owner

Hm, not sure how to best go about this. Maybe ID3v2Tag::set_version?

@sagudev
Copy link
Contributor Author

sagudev commented Aug 11, 2022

Maybe create something like ID3v2Tag::upgrade for 3 -> 4 and ID3v2Tag::downgrade for 4 -> 3 and on reading instead of force upgrading all to version 4 something like:
2 -> 3
3 -> 3
4 -> 4

@Serial-ATA
Copy link
Owner

Allowing two different versions of the tag would complicate things quite a bit, even TagLib upgrades all tags to 2.4. TagLib lets you change the version with overloading, so maybe a new trait is the way to go, maybe ID3v2Writable?

@sagudev
Copy link
Contributor Author

sagudev commented Aug 12, 2022

If I understand correctly this would meant that Lofty would store ID3v2.4 internally and than decide what version to use on saving? ID3v2.3 file would then on reading be upgraded (as it is now) to ID3v2.4 and on writing be downgraded back to ID3v2.3. Is transforming ID3v2.3 to version 4 lossless or lossy? Looking at Wikipedia it loos like more or less lossless with only few fields having some difficult mappings.

@Serial-ATA
Copy link
Owner

The conversion isn't completely lossless, some frames have a 1:1 mapping and others have expanded in 2.4. The discarded frames aren't that important though IMO, I doubt most apps even look at them.

@Serial-ATA Serial-ATA added this to the 0.10.0 milestone Oct 15, 2022
@FriederHannenheim
Copy link
Contributor

I really would like for this to be implemented. Sadly the support for id3v2.4 is pretty bad and so I need to use v2.3. Lofty is a genius library, the only one that allows writing many different tag formats so easily so I would really like to use it.

@Serial-ATA
Copy link
Owner

The only blocker here is how to best expose the option to downgrade the tag.

I may just end up going with the suggestion above:

Maybe create something like ID3v2Tag::upgrade for 3 -> 4 and ID3v2Tag::downgrade for 4 -> 3 and on reading instead of force upgrading all to version 4 something like: 2 -> 3 3 -> 3 4 -> 4

@Serial-ATA Serial-ATA removed this from the 0.10.0 milestone Feb 20, 2023
@FriederHannenheim
Copy link
Contributor

Yeah I think that's a good solution

@Serial-ATA
Copy link
Owner

Serial-ATA commented Jul 11, 2023

I've been looking into this and I think it'd introduce way more complexity than needed. The current behavior of upgrading to version 4 will stay so there aren't a bunch of unnecessary version checks.

The best solution would probably be through a setting in WriteOptions once that gets implemented (#228). Probably use_id3v23.

There can be an almost entirely lossless v3 -> v4 conversion (with the exception of the RVAD and EQUA (?) frames). The couple of frames that can't be upgraded can be stored with FrameId::Outdated, and ignored on write unless allowed through WriteOptions::use_id3v23.

There's a list of ID3v2.4 frames that can be discarded when writing ID3v2.3:

   ASPI Audio seek point index [F:4.30]
   EQU2 Equalisation (2) [F:4.12]
   RVA2 Relative volume adjustment (2) [F:4.11]
   SEEK Seek frame [F:4.29]
   SIGN Signature frame [F:4.28]
   TDEN Encoding time [F:4.2.5]
   TDOR Original release time [F:4.2.5]
   TDRC Recording time [F:4.2.5]
   TDRL Release time [F:4.2.5]
   TDTG Tagging time [F:4.2.5]
   TIPL Involved people list [F:4.2.2]
   TMCL Musician credits list [F:4.2.2]
   TMOO Mood [F:4.2.3]
   TPRO Produced notice [F:4.2.4]
   TSOA Album sort order [F:4.2.5]
   TSOP Performer sort order [F:4.2.5]
   TSOT Title sort order [F:4.2.5]
   TSST Set subtitle [F:4.2.1]

Writing an ID3v2.4 tag as ID3v2.3 would be as "simple" as:

  1. Filtering out the above IDs
  2. Splitting dates into multiple frames (Will be much easier after Parse ID3v2 timestamps #233)
    1. TDOR has its year split into TORY (Original release year)
    2. TDRC is split into three components: Year, month/day, and hour/minute, going into TYER (yyyy), TDAT (DDMM), and TIME (HHMM) respectively.
  3. TCON needs to be split and any number (<= 255) or the strings "RX" or "CR" need to be wrapped in parentheses. (Trivial after ID3v2: Handle genre IDs in TCON frames #286)
  4. All frames encoded TextEncoding::{UTF8, UTF16BE} need to be changed to TextEncoding::UTF16
  5. FrameFlags need to be written with their ID3v2.3 mappings:
    pub(crate) fn parse_flags(flags: u16, v4: bool) -> FrameFlags {
    FrameFlags {
    tag_alter_preservation: if v4 {
    flags & 0x4000 == 0x4000
    } else {
    flags & 0x8000 == 0x8000
    },
    file_alter_preservation: if v4 {
    flags & 0x2000 == 0x2000
    } else {
    flags & 0x4000 == 0x4000
    },
    read_only: if v4 {
    flags & 0x1000 == 0x1000
    } else {
    flags & 0x2000 == 0x2000
    },
    grouping_identity: ((v4 && flags & 0x0040 == 0x0040) || (flags & 0x0020 == 0x0020))
    .then_some(0),
    compression: if v4 {
    flags & 0x0008 == 0x0008
    } else {
    flags & 0x0080 == 0x0080
    },
    encryption: ((v4 && flags & 0x0004 == 0x0004) || flags & 0x0040 == 0x0040).then_some(0),
    unsynchronisation: if v4 { flags & 0x0002 == 0x0002 } else { false },
    data_length_indicator: (v4 && flags & 0x0001 == 0x0001).then_some(0),
    }
    }

@Serial-ATA Serial-ATA added the help wanted Extra attention is needed label Jul 11, 2023
@Serial-ATA Serial-ATA added the blocked Blocked on something else, cannot be worked on yet label Jan 3, 2024
@Serial-ATA Serial-ATA removed the blocked Blocked on something else, cannot be worked on yet label May 3, 2024
@Serial-ATA
Copy link
Owner

Everything's in place for this to be worked on now. I'll probably work on this after the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants