Creating a plugin that can write a custom tag

I am relative new to beets. I use both Musicbrainz and Discogs as a meta source.
For Plex/PlexAmp I need to use tag called releasetype to get sorting for singles, eps, albums working.

In the beets library/database I do see in both albums and items tables contain an albumtype and albumtypes but a lot are empty.

So I thought to write a beets plugin that sets a releasetype tag to all the items within an album.
I was thinking to build functionality:

  1. Add releasetype tag on beet import.
  2. Add releasetype tag command incase you want to do it after import: beet releasetype.
  3. Add 'releasetypetag command based on album namebeet releasetype -a ‘Album name’`.

Can someone tell me how write a custom tag to an item? I thought I need to use MediaFile for this but it looks like I cannot add custom tags only predefined.

Then the second question is this the right approach? Or should I use the library/database fields albumtype and albumtypes.

Thanks in advance.

What I have so far:

from beets.plugins import BeetsPlugin
from beets.ui import Subcommand
from mediafile import MediaFile
import inquirer


class ReleaseTypePlugin(BeetsPlugin):
    def __init__(self):
        super(ReleaseTypePlugin, self).__init__()

        self.config.add({
            'album_length_threshold': 1800,  # 30 minutes in seconds
            'ep_length_threshold': 1800,     # 30 minutes in seconds
            'single_track_limit': 3,
            'ep_track_limit': 6
        })

    def commands(self):
        cmd = Subcommand(
            'releasetype', help='Assign release type')
        cmd.parser.add_option(
            '--album', '-a', help='Album name')
        cmd.func = self.release_type
        return [cmd]

    def release_type(self, lib, opts, args):
        if opts.album:
            album_name = opts.album
            album = lib.albums(album_name)[0]
            if album:
                release_type = self.determine_release_type(album)
                self.set_release_type(album, release_type)
            else:
                print(f"No album found with name: {album_name}")
        else:
            for album in lib.albums():
                release_type = self.determine_release_type(album)
                self.set_release_type(album, release_type)

        self._log.info('Release type set for album: %s' % album)

    def determine_release_type(self, album):
        album_length_treshold = self.config['album_length_threshold'].get(int)
        ep_length_threshold = self.config['ep_length_threshold'].get(int)
        single_track_limit = self.config['single_track_limit'].get(int)
        ep_track_limit = self.config['ep_track_limit'].get(int)

        total_tracks = len(album.items())
        total_length = sum(item.length for item in album.items())

        if total_tracks <= single_track_limit:
            release_type = 'single'
        elif total_tracks <= ep_track_limit and total_length <= ep_length_threshold:
            release_type = 'ep'
        elif total_tracks >= ep_track_limit and total_length > album_length_treshold:
            release_type = 'album'

            secondary_release_type = self.determine_secondary_release_type()
            if secondary_release_type:
                release_type = f'{release_type}; {secondary_release_type}'
        else:
            release_type = None

        return release_type

    def determine_secondary_release_type(self):
        questions = [
            inquirer.List(
                'secondary_release_type',
                message='Please select a secondary release type:',
                choices=['', 'Compilation', 'Demo',
                         'DJ-mix', 'Live', 'Remix', 'Soundtrack']
            )
        ]
        answers = inquirer.prompt(questions)
        secondary_release_type = answers.get('secondary_release_type').lower()
        if secondary_release_type:
            return secondary_release_type
        return None

    def set_release_type(self, album, release_type):
        for item in album.items():
            mf = MediaFile(item.path)
            mf.mgfile['releasetype'] = release_type
            mf.save()

Hi! If you’re interested in having your plugin populate a specific on-disk metadata tag for your music files, consider adding a MediaField:
https://beets.readthedocs.io/en/stable/dev/plugins.html#extend-mediafile

I have updated my plugin using mediaField.

Is there anything else you would recommend me todo?
Like updating the albumtype and albumtypes in the beets database? Not sure where this is used for. Or can I add the releasetype to the Beets library/database, this way I can check if the releasetype exist and skip the ones already processed and if needed add another command -f incase you want to force a full run.

from beets.plugins import BeetsPlugin
from beets.ui import Subcommand
from mediafile import MediaField, MP3DescStorageStyle, StorageStyle
import inquirer


class ReleaseTypePlugin(BeetsPlugin):
    def __init__(self):
        super(ReleaseTypePlugin, self).__init__()

        field = MediaField(
            MP3DescStorageStyle(u'releasetype'),
            StorageStyle(u'releasetype')
        )
        self.add_media_field('releasetype', field)

        self.config.add({
            'album_length_threshold': 1800,  # 30 minutes in seconds
            'ep_length_threshold': 1800,     # 30 minutes in seconds
            'single_track_limit': 3,
            'ep_track_limit': 6
        })

    def commands(self):
        cmd = Subcommand(
            'releasetype', help='Assign release type')
        cmd.parser.add_option(
            '--album', '-a', help='Album name')
        cmd.func = self.release_type
        return [cmd]

    def release_type(self, lib, opts, args):
        album = None
        album_name = None

        if opts.album:
            album_name = opts.album
            album_query = f'album:"{album_name}"'
            matching_albums = lib.albums(album_query)
            if matching_albums:
                album = matching_albums[0]
                release_type = self.determine_release_type(album)
                if release_type == 'album':
                    release_type = self.determine_secondary_release_type(
                        release_type, album) or release_type
                self.set_release_type(album, release_type)

                self._log.info('Release type set for album: %s (%s)' %
                               (album, release_type))
            else:
                print(f"No album found with name: {album_name}")
        else:
            for album in lib.albums():
                release_type = self.determine_release_type(album)
                if release_type == 'album':
                    release_type = self.determine_secondary_release_type(
                        release_type, album) or release_type
                self.set_release_type(album, release_type)

                self._log.info('Release type set for album: %s (%s)' %
                               (album, release_type))

    def determine_release_type(self, album):
        album_length_treshold = self.config['album_length_threshold'].get(int)
        ep_length_threshold = self.config['ep_length_threshold'].get(int)
        single_track_limit = self.config['single_track_limit'].get(int)
        ep_track_limit = self.config['ep_track_limit'].get(int)

        total_tracks = len(album.items())
        total_length = sum(item.length for item in album.items())

        if total_tracks <= single_track_limit:
            release_type = 'single'
        elif total_tracks <= ep_track_limit and total_length <= ep_length_threshold:
            release_type = 'ep'
        elif total_tracks >= ep_track_limit and total_length > album_length_treshold:
            release_type = 'album'
        else:
            release_type = None

        return release_type

    def determine_secondary_release_type(self, release_type, album):
        questions = [
            inquirer.List(
                'secondary_release_type',
                message=f'Please select a secondary release type for album "{album.albumartist} - {album.album}"',
                choices=['', 'Compilation', 'Demo',
                         'DJ-mix', 'Live', 'Remix', 'Soundtrack']
            )
        ]
        answers = inquirer.prompt(questions)
        secondary_release_type = answers.get('secondary_release_type').lower()
        if secondary_release_type:
            return f'{release_type}; {secondary_release_type}'
        return None

    def set_release_type(self, album, release_type):
        for item in album.items():
            item['releasetype'] = release_type
            item.write()