dobrovv
dobrovv

Reputation: 33

Is it possible to add ID3 tags to m4a files using mutagen?

I run into a problem while writing a script to tag my music library with mutagen. The following code works fine when dealing with mp3 files but corrupts the m4a files.

def set_video_tags(video, filepath):
    try:
        tags = ID3(filepath, v2_version=3)
    except ID3NoHeaderError:
        tags = ID3()

    tags.add(TXXX(3, desc='desc:custom_tag',text= video.custom_text))
    tags.save(filepath, v2_version=3)

After processing the m4a files, tags can be read back using mutagen but aren't detected by any other players and the audio doesn't playback.

I tried to delete MP4 tags from the files before calling this function but it didn't help.

What am I doing wrong?

Upvotes: 3

Views: 9667

Answers (1)

zwer
zwer

Reputation: 25789

As I was saying in my comment, MP4 and MP3 are different containers and they use a completely different structures to store the metadata. While, technically, you can encode ID3 fields within MP4 metadata, usage of such would heavily depend on your tagger/player because there are a lot of ways to do it and everybody think they have the best way consequently pushing their own structures.

What is ID3 for MP3 containers, XMP is for MP4/M4A containers (and for a lot of other containers like JPEG, PDF and even MP3 if you want to) - except that XMP is an actual standard while ID3 was an afterthought (it literally was just an added trailing structure at the end of MP3 files) that everybody went along with and its features were mainly dictated by massively popular players of the time like WinAmp, foobar2000 and the likes. In contrast, XMP is essentially an atom-feed like structure (which is why you'll hear mp4 tags being referred as 'atoms' even tho they are not referring to the same thing) which along with its metadata carries also the information of what that metadata presents so even players that first encounter it could, in theory, discern relevant from irrelevant metadata.

Of course, in practice this has also turned into a player/tagger war so a lot of them use their own custom 'extensions' for tagging - currently iTunes holds a massive sway over which fields should be used and how, and other players kind-of play ball - so is mutagen. Instead of a single XMP structure, iTunes spreads the tags as non-video/audio frames (the actual 'atoms') in the MP4 structure itself and some 'tags' have to be weirdly named (binary names and such) so they don't interfere with the format itself. While there is some advantage to this approach (changing the metadata with streaming, very useful for live events) it makes the tagging needlessly complicated and, again, non-standard.

Anyway, your problem occurs because you're trying to write an ID3 structure to an MP4 container - when writing ID3 tags mutagen doesn't parse the whole file to discern if the underlying file supports ID3 and where/how to write it, instead it assumes its been fed a regular MP3 file and writes down the at a wrong place, in a wrong format, at best just adding some garbage at the end of your file (earlier, non-streamable ID3 versions) or, at worst, corrupting your M4A container as a consequence. Same goes when you read the file back - it can read the ID3 structure it wrote previously at a predictable place, it doesn't care what the rest of the data is.

So, as I was saying, and as it is shown in the documentation, use mutagen.mp4.MP4 (or rather the underlying mutagen.mp4.MP4Tags) when dealing with MP4/M4A containers as those are created to work with MP4 containers. For example, to change the desc tag as you're attempting:

from mutagen.mp4 import MP4

def get_description(filename):
    return MP4(filename).tags.get("desc", [None])[-1]

def set_description(filename, description):
    tags = MP4(filename).tags
    tags["desc"] = description
    tags.save(filename)

NOTE: I'm using only the last desc entry in the get_description() function as multiple entries per 'tag' are supported so existing tags return as lists. You obviously wouldn't be using the above in a production setting.

You can test it with:

print("Current description: {}".format(get_description("test.m4a")))
# Current description: None

set_description("test.m4a", "Test description")  # let's add some description
print("Current description: {}".format(get_description("test.m4a")))
# Current description: Test description

# You can also modify the description once set:
set_description("test.m4a", get_description("test.m4a") + " update")
print("Current description: {}".format(get_description("test.m4a")))
# Current description: Test description update

# etc.

For a full set of 'supported' (or rather iTunes, hmmm, encouraged) keys you can check the mutagen.mp4.MP4Tag docs. Of course, you can invent your own keys using a free form structure (i.e. ----:foo:bar) but don't expect any other player to recognize them.

Upvotes: 16

Related Questions