KristoforMaynard / music-tag

Simple interface to edit audio file metadata
MIT License
118 stars 19 forks source link

method like keys() or items() to iterate through tags? #32

Open RanTalbott opened 1 year ago

RanTalbott commented 1 year ago

I found out the hard way that the interface to the tags is only dict-like. :-(

I'm doing my first project with music-tag, and trying to debug a problem with setting tags. I tried doing an f.keys() to find out what keys were set, and a ValueError exception 'cuz it only kinda looks like a dict.

Is there a definitive list of all the possible tag keys somewhere that I could use to simulate keys() and/or items()? Would you be interested in getting a method that you could incorporate into music-tag?

The reason I'm messing with tags is that, for a while, I used a ripper that stored tags for oggs with lowercase names, so lots of apps don't see them. Do you happen to know, offhand, of a library for python, perl or c that would make it easy to access and replace the non-standard tags? I was hoping they'd show up in the "extras" in tinytags, but no luck.

Thanks,

Ran

ernestclyde commented 1 year ago

Hi @RanTalbott, I can't also find it on docs, simply use f.mfile this will show key and values

ernestclyde commented 1 year ago

Hi @RanTalbott, I can't also find it on docs, simply use f.mfile this will show key and values

this will show all the tags currently written in the file, it seems that the only specified tag in the doc is supported

KristoforMaynard commented 1 year ago

Adding these methods is easy enough, but I'm curious what sort of behavior would make sense. I'm thinking something like an iterator though the tags returned by f.info(). This will leave underlying tags untouched if they are not mapped in music-tag.

RanTalbott commented 1 year ago

Thanks, folks.

I did some more digging through the code. In file.py, there's a dict called _DEFAULT_TAG_MAP that's used to build some internal table. At the user API level, you can get a copy of the supported tags with f.tag_map.keys() but you have to be careful: there's one tag included, "artwork", that doesn't appear to be fully supported. It throws an exception like this one I got trying print(f.info()) in idle:

Traceback (most recent call last):
  File "/usr/lib/python3.8/idlelib/run.py", line 559, in runcode
    exec(code, self.locals)
  File "<pyshell#8>", line 1, in <module>
  File "/usr/local/lib/python3.8/dist-packages/music_tag/file.py", line 591, in info
    mdi = self.get(tag, None)
  File "/usr/local/lib/python3.8/dist-packages/music_tag/file.py", line 425, in get
    val = tmap.getter(self, norm_key)
  File "/usr/local/lib/python3.8/dist-packages/music_tag/vorbis.py", line 27, in get_pictures
    for p in afile.mfile.tags['metadata_block_picture']:
  File "/usr/local/lib/python3.8/dist-packages/mutagen/_vorbis.py", line 241, in __getitem__
    raise KeyError(key)
KeyError: 'metadata_block_picture'

Trying print(f.mfile()) produced:

Traceback (most recent call last):
  File "/usr/lib/python3.8/idlelib/run.py", line 559, in runcode
    exec(code, self.locals)
  File "<pyshell#9>", line 1, in <module>
TypeError: 'OggVorbis' object is not callable

I tried both of those in a program run from the command line, and got the same exceptions (with a slightly different traceback stack from running in idle). Note that the ogg file did NOT have the "artwork" tag set.

But, if I do this:

for i in f.tag_map.keys():
    try:
        # Don't try artwork, because it makes a mess
        if i != 'artwork': print(f"{i}: {f[i]}")
    except KeyError:
        print("    key " + i + " not supported")
        pass

it works fine, and I get an empty string, instead of KeyError (which I didn't actually need to check, after all, but I didn't know what the result was for "nothing there"), for any tag that isn't set in the file.

"in theory", your suggestions will eventually be "right", and my kluge will become "wrong", when the "artwork" tag is properly supported. But, for 0.4.3, my kluge "wins" ;-)

Thanks for the tips. And thanks, Kristofor, for all the hard work putting together music-tag: my tag fixer is only the secon python program I've written that's longer than 100 lines, and I might not have lived long enough to figure it out for myself ;-)

RanTalbott commented 1 year ago

I found a tool for adding cover art to ogg files here: [(https://alexcabal.com/adding-album-art-to-ogg-files-from-the-command-line)]

I used a different ogg file because it happened to have a cover.jpg file in its directory.

Before I added the cover art, I got the same exceptions as before. After, I got the same exception from f.mfile(), but different results from f.info():

print(f.info().keys())
Traceback (most recent call last):
  File "/usr/lib/python3.8/idlelib/run.py", line 559, in runcode
    exec(code, self.locals)
  File "<pyshell#16>", line 1, in <module>
AttributeError: 'str' object has no attribute 'keys'
>>> print(f.info())
tracknumber: 1
genre: Unknown
artwork: image/jpeg 500x489 de25d124b875f4ddb3133ae47055d929
#codec: Ogg Vorbis

So, info() returns a str, not a dict, and only the tags that are set in the ogg file.

For the curious: cover.jpg is 116253 bytes long, but it added 155749 to the ogg file. You might want to shrink it to a thumbnail (the one the ripper downloaded is 500x498) if you're going to tag the individual files.

I have over 8000 oggs from 700 CDs that I've ripped with at least 3 different apps over the last 20+ years. If you have changes to music-tag that you like to check, I can put together a collection of slightly-differently-tagged files to try them against.

Thanks again, Ran

KristoforMaynard commented 1 year ago

I just pushed a few commits related to this issue. Incidentally, I think I may have also fixed some of your usability issues regarding artwork in vorbis files. I don't have any music in ogg, so it's not as real-world tested as mp4 / mp3 formats.