Adding custom fields to items

244 views
Skip to first unread message

Arnaud Castaner

unread,
Feb 19, 2016, 4:40:41 AM2/19/16
to beets
Hi,

I'd like beets to be able to access custom fields from items. For instance, I'd like to add a "savedir" field to the scope of items (to use in the paths when importing). Following the "Writing Plugins" examples I came up with this (sorry if the code is bad, never did Python before, I'm more of a C# guy) :

from beets import plugins, mediafile

class DirectoryField(plugins.BeetsPlugin):
        def __init__(self):
            field = mediafile.MediaField(
                mediafile.MP3DescStorageStyle(u'savedir'),
                mediafile.StorageStyle(u'savedir')
            )
            self.add_media_field('savedir', field)

DirectoryField()


This generate the following error:

  File "c:\python27\lib\site-packages\beets\mediafile.py", line 1519, in add_field
    u'property "{0}" already exists on MediaField'.format(name))
ValueError: property "savedir" already exists on MediaField


I have tried several different values besides "savedir" so I'm fairly sure this is a generic error message? The full stacktrace is this:

PS C:\Users\ArnaudLocal> beet
Traceback (most recent call last):
  File "C:\Python27\Scripts\beet-script.py", line 9, in <module>
    load_entry_point('beets==1.3.17', 'console_scripts', 'beet')()
  File "c:\python27\lib\site-packages\beets\ui\__init__.py", line 1236, in main
    _raw_main(args)
  File "c:\python27\lib\site-packages\beets\ui\__init__.py", line 1222, in _raw_main
    subcommands, plugins, lib = _setup(options, lib)
  File "c:\python27\lib\site-packages\beets\ui\__init__.py", line 1101, in _setup
    plugins = _load_plugins(config)
  File "c:\python27\lib\site-packages\beets\ui\__init__.py", line 1087, in _load_plugins
    plugins.send("pluginload")
  File "c:\python27\lib\site-packages\beets\plugins.py", line 458, in send
    for handler in event_handlers()[event]:
  File "c:\python27\lib\site-packages\beets\plugins.py", line 441, in event_handlers
    for plugin in find_plugins():
  File "c:\python27\lib\site-packages\beets\plugins.py", line 289, in find_plugins
    _instances[cls] = cls()
  File "c:\python27\lib\site-packages\beetsplug\directoryfield.py", line 9, in __init__
    self.add_media_field('savedir', field)
  File "c:\python27\lib\site-packages\beets\plugins.py", line 190, in add_media_field
    mediafile.MediaFile.add_field(name, descriptor)
  File "c:\python27\lib\site-packages\beets\mediafile.py", line 1519, in add_field
    u'property "{0}" already exists on MediaField'.format(name))
ValueError: property "savedir" already exists on MediaField
PS C:\Users\ArnaudLocal>


Any pointer would be appreciated!

Regards,

A

Adrian Sampson

unread,
Feb 19, 2016, 10:43:27 AM2/19/16
to beets...@googlegroups.com
Hi! The problem is this extra instantiation:

> On Feb 19, 2016, at 1:40 AM, Arnaud Castaner <fur...@gmail.com> wrote:
>
> DirectoryField()

Beets will construct your plugin; you don't need to.

And, BTW, just to be clear: no plugin is necessary if you just want to use this field in beets' database. The plugin dance is only required if you also want to write the data to files' tags.

A

Arnaud Castaner

unread,
Feb 19, 2016, 11:38:34 AM2/19/16
to beets...@googlegroups.com
Ah thanks, I'll try that!

I don't need to use this field in the database (not at first though), I want to be able to use it in the path: section of the configuration, like so:

paths:
default: $savedir/$albumartist/$albumartist - $year - $album/$disc_and_track. $title
item_fields:
disc_and_track: u'%02i-%02i' % (disc, track) if
disctotal > 1 else u'%02i' % (track)

It seems $savedir isn't in the scope, so I reckoned I need to witre a plugin to add that field?



-----Message d'origine-----
De : beets...@googlegroups.com [mailto:beets...@googlegroups.com] De la part de Adrian Sampson
Envoyé : vendredi 19 février 2016 16:43
À : beets...@googlegroups.com
Objet : Re: [beets] Adding custom fields to items
--
You received this message because you are subscribed to a topic in the Google Groups "beets" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/beets-users/Q3lG4CYFZ2s/unsubscribe.
To unsubscribe from this group and all its topics, send an email to beets-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Adrian Sampson

unread,
Feb 19, 2016, 11:50:37 AM2/19/16
to beets...@googlegroups.com
You can use custom fields in path formats (again, no plugin required).

There’s an overview in the docs here:
http://docs.beets.io/en/v1.3.17/guides/advanced.html#store-any-data-you-like

Specifically, you can just use `beet modify savedir=…` and then $savedir will be available in your path formats. (That, for what it’s worth, is what I meant by “in the database.”)
> You received this message because you are subscribed to the Google Groups "beets" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to beets-users...@googlegroups.com.

Arnaud Castaner

unread,
Feb 19, 2016, 12:55:34 PM2/19/16
to beets...@googlegroups.com

Ah, I see. Ideally I'd like to do this at import though, one command to type instead of two. I have _a lot_ of media to import, already sorted by genre (using directory names). I can't import thousands of album and then set their directory artist by artist. So doing something like below would be perfect:

beet import --set-import-variable=savedir:metal /somePath/

And then being able to use the "savedir" variable in the path configuration. The only alternative I found for that is to add that extra field to the items scope during import.

Would something like this make sense?

Adrian Sampson

unread,
Feb 19, 2016, 1:03:46 PM2/19/16
to beets...@googlegroups.com
Yes! This request describes exactly that: https://github.com/beetbox/beets/issues/1881

One other idea you might consider, if you’re down with plugin hacking, would be a custom plugin that just sets the `savedir` field based on the album’s import path. In Python, that would look something like this:

item.savedir = os.path.basename(os.path.dirname(os.path.dirname(item.path)))

or something like that. No MediaFile monkeying required.

Arnaud Castaner

unread,
Feb 19, 2016, 2:32:25 PM2/19/16
to beets...@googlegroups.com

I like that feature request J Hopefully somebody will implement it soon.

 

In the meantime, I’ll explore your suggestion using the album’s import path, probably easier than what I started with.

 

Thanks for taking the time to answer my admittedly stupid questions!

 

 

De : beets...@googlegroups.com [mailto:beets...@googlegroups.com] De la part de Adrian Sampson
Envoyé : vendredi 19 février 2016 19:04

Arnaud Castaner

unread,
Feb 21, 2016, 1:19:12 AM2/21/16
to beets...@googlegroups.com

So I have tried the suggestion below (“store any data you like”) but I’m still stuck.

 

I have edited (from foobar2000) a bunch of albums and added the “savedir” field to the tracks. The value of that field is “metal”

 

I created a path section like so in config.yaml:

 

paths:

    default: _sort/$albumartist/$albumartist - $year - $album/$disc_and_track. $title

    savedir:metal: metal_hard-rock/$albumartist/$albumartist - $year - $album/$disc_and_track. $title

 

When I import an album initially, it gets put under “_sort” – as I understand this is because beets, upon import, can read only the standard fields. But once they are imported, all the fields are stored in the database and beets can read them, correct?

 

So with that in mind I tried to move them. If I do “beet mv” there’s no file to move. The documentation says “you can query on this field in your path formats to sort this music differently. Put something like this in your configuration file ». But doesn’t give any example of command that would move the files using the new path section.

 

I really wish we could specify which paths section to use upon import (ie: beets import –path=savedir:metal /<some dir>) !

 

De : beets...@googlegroups.com [mailto:beets...@googlegroups.com] De la part de Adrian Sampson
Envoyé : vendredi 19 février 2016 19:04

Adrian Sampson

unread,
Feb 21, 2016, 1:33:48 AM2/21/16
to beets...@googlegroups.com
You'll need to use `beet modify` to set the field, not foobar. Those custom fields are stored in beets' database, which foobar doesn't know about.

Arnaud Castaner

unread,
Feb 24, 2016, 12:56:51 PM2/24/16
to beets...@googlegroups.com

So just to be clear, the idea is to import once, which will use the default path, then use beet to modify the savedir field and when that happens beets will also move the files to the appropriate path I've setup? It'll be time consuming but it should work?

Also, if you implement the feature request to set explicit fields at import, then beet will use the appropriate path upon the initial import instead of having to do two steps as described above?

Adrian Sampson

unread,
Feb 24, 2016, 1:03:38 PM2/24/16
to beets...@googlegroups.com
Yes and yes.
Reply all
Reply to author
Forward
0 new messages