What are my options for storing "play_count" in MediaFile?

Hi,

I am storing a “play_count” flex attribute in the db for one of my plugins. I would also like to optionally write this attribute to the MediaFile.

I tried this:

item.try_write(tags={"play_count": play_count})

but no avail.
Any tips?

I think I got it:

I registered a listener for the “after_write” event with this method:

def write_play_count_to_mediafile(item: Item, path: str):
    play_count_fld = MediaField(MP3DescStorageStyle(desc="play_count"))
    mf = MediaFile(path)
    mf.add_field("play_count", play_count_fld)
    setattr(mf, "play_count", str(item.get("play_count", 0)))
    mf.save()

and it works… Incredible!!! :star_struck:

So, I change my question: Which event would you use for reading and adding the same attribute to the item on import? The item_imported event or would it be better to work with a non-blocking pipeline stage?

Yo! For that, you’ll need to add a field to MediaFile that describes the mapping to actual metadata tags:
https://beets.readthedocs.io/en/stable/dev/plugins.html#extend-mediafile

Aha! Oops, looks like I was too slow. :smiley: :man_facepalming:

If I were you, I’d add the MediaField when your plugin loads up. Then it’s permanently available to all stages of beets. Then you don’t need an explicit save—anything that writes tags will automatically get the play_count value from the database.

If you don’t want that (if you don’t want play_count to be in the database, for example), probably a pipeline stage. That way, the latency of the file operation will be overlapped with other actions. But it’s likely not to be too slow, so it won’t matter too much.

1 Like

Thanks for the tip! Maybe I will read the manual :open_book: next time before asking questions already answered there ;), :blush: sorry.

1 Like

Supercool! Extending the MediaFile works great. Just one question (or two). I don’t understand why the guide says to initialise the MediaField this way:

field = mediafile.MediaField(
            mediafile.MP3DescStorageStyle(u'foo'),
            mediafile.StorageStyle(u'foo')
        )

Why to I need the second ‘generic’ style in the constructor?

I also added the out_type to so my code looks like this:

# Add `play_count` field support
        fld_name = u'play_count'
        field = mediafile.MediaField(
            mediafile.MP3DescStorageStyle(fld_name),
            mediafile.StorageStyle(fld_name),
            out_type=int
        )
        self.add_media_field(fld_name, field)

With this I hoped to be able to remove the following from my plugin

    @property
    def item_types(self):
        return {'play_count': types.INTEGER}

but without it I get the following error when trying to to numeric operations on the field:

    play_count = item.get("play_count", 0) + 1
TypeError: can only concatenate str (not "int") to str

So the out_type=int on the MediaField has nothing to do with the type of the field on the library Item?

Great; glad this is working!

The latter style is for file formats that use key/value tag data—FLAC, Ogg Vorbis, etc. They need a different mapping from ID3 tags, which use a complicated “frame” system, and other more structured formats like MPEG-4 and Windows Media.

Indeed—MediaFile is mostly decoupled from beets itself, so the types it declares are different from the beets type system. In particular, MediaFile out_type conversions are plain Python types, while beets types are much richer (they can express padding, date formats, etc.).

Thanks a lot for the info.