-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Add PlaylistQuery plugin #2380
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a cool idea! Here are a few comments. It also looks like Travis has some style suggestions:
https://travis-ci.org/beetbox/beets/jobs/190643595#L851-L853
item_types = {'playlist': PlaylistType()} | ||
|
||
def __init__(self): | ||
super(PlaylistQueryPlugin, self).__init__() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll likely want to set defaults for the plugin's configuration here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the documentation could benefit from a small section on how to specify config defaults for plugins, it is currently missing so I had to look at other plugins for examples.
super(PlaylistQueryPlugin, self).__init__() | ||
self.register_listener('library_opened', self.library_opened) | ||
|
||
PlaylistQuery.playlist_dir = self.config['playlist_dir'].as_filename() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using global state to keep track of this could be a little problematic for testing—the right thing to do here might be to look up the configuration option directly in the query class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will this work with access needed to lib
? The Query objects don't get any context, so the best place I thought to put it was in the library_opened
event on PlaylistQueryPlugin, which in turn requires some sort of global state passed to PlaylistQuery for instances to use. Unless there's an API that I haven't seen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yes, that is a problem. We haven't had a case where a query needed a reference to the database before—maybe we need to add that capability?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment a Query object is constructed with the field and pattern, which makes it very clean. I think it is unwise to expand that in the default case, since most of the time it will go unused. However, I can see potential for the Builder pattern here - have a Plugin object instantiate Query objects that it controls.
So currently, construct_query_part()
in dbcore.queryparse directly constructs the selected query_class
. Instead, it should know which Plugin object corresponds to each query class, then calls on that object to construct the desired class. The default implementation in BeetsPlugin would do what construct_query_part()
does at the moment (i.e. check if we have a FieldQuery, and construct in two different ways) but can be overridden in custom plugins to do more complex things, passing in other information if desired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure; good idea. Another relatively simple way would be to use a closure—that is, the plugin would just set self.item_types
to reference a class that can access lib
from the outer context.
I suppose one way to judge whether we've done it "right" is to ask whether the plugin would break if beets were ever to support multiple libraries for some reason—if there weren't a single, global Library
object but queries would work on whatever library they're querying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of a good way to avoid using global state somewhere, without rewriting how the entire mechanism for registering queries works. Closures wouldn't work easily, since the PlaylistQuery class is defined inside PlaylistType, of which an instance belongs to PlaylistQueryPlugin, so the PlaylistQuery class inside the (now global) PlaylistType instance needs to be changed on library_opened
events, taking you right back to mutable global state.
I don't think these two configuration variables as global state is necessarily a problem, though. If you consider PlaylistQueryPlugin and PlaylistQuery to be separate units of code, then each can be unit tested independently, where the global state isn't such a big deal.
if relative_to == 'playlist': | ||
PlaylistQuery.relative_path = PlaylistQuery.playlist_dir | ||
else: | ||
PlaylistQuery.relative_path = lib.directory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a difference between setting playlist_dir
to be the same as directory
vs. setting relative_to
to base
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes: playlist_dir
affects where playlists are stored (and referenced from, so a playlist:foobar
looks up $playlist_dir/foobar.m3u
) while relative_to
affects how relative paths inside the M3U file are handled. There is no formal M3U spec (https://en.wikipedia.org/wiki/M3U#File_format) so paths may be relative to the playlist file (relative_to = playlist
) or relative to the base music directory (ncmpcpp does this, relative_to = base
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, that makes sense! Thanks for clarifying.
playlist_dir = None | ||
|
||
def __init__(self, field, pattern, fast=True): | ||
super(PlaylistQuery, self).__init__(field, pattern, fast) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want fast=False
here unless you implement a clause
method (i.e., a pure-SQL way of executing the query).
This is a nifty idea. I'm intrigued by the low-complexity approach of using a directory full of m3u files as the playlist store. Eventually, for the full set of #123 features, we probably want to keep playlists in the database itself. But for now, I'm curious about using this approach to prototype some of what we'd do eventually with that full-fledged implementation. |
@Xenopathic Any progress here? |
I'd really like to see this feature merged. |
I'm no longer using Beets, so I'm going to close this PR. Anyone else is welcome to take my work so far and complete it, since my work is licensed under the same MIT license that Beets is. |
Adds M3U playlist support as a query to beets and thus partially resolves issue beetbox#123. The implementation is heavily based on beetbox#2380 by Robin McCorkell. It supports referencing playlists by absolute path: $ beet ls playlist:/path/to/someplaylist.m3u It also supports referencing playlists by name. The playlist is then seached in the playlist_dir and the ".m3u" extension is appended to the name: $ beet ls playlist:anotherplaylist The configuration for the plugin looks like this: playlist: relative_to: library playlist_dir: /path/to/playlists The relative_to option specifies how relative paths in playlists are handled. By default, paths are relative to the "library" directory. It also possible to make them relative to the "playlist" or set the option or set it to a fixed path.
Adds M3U playlist support as a query to beets and thus partially resolves issue beetbox#123. The implementation is heavily based on beetbox#2380 by Robin McCorkell. It supports referencing playlists by absolute path: $ beet ls playlist:/path/to/someplaylist.m3u It also supports referencing playlists by name. The playlist is then seached in the playlist_dir and the ".m3u" extension is appended to the name: $ beet ls playlist:anotherplaylist The configuration for the plugin looks like this: playlist: relative_to: library playlist_dir: /path/to/playlists The relative_to option specifies how relative paths in playlists are handled. By default, paths are relative to the "library" directory. It also possible to make them relative to the "playlist" or set the option or set it to a fixed path.
Adds M3U playlist support as a query to beets and thus partially resolves issue beetbox#123. The implementation is heavily based on beetbox#2380 by Robin McCorkell. It supports referencing playlists by absolute path: $ beet ls playlist:/path/to/someplaylist.m3u It also supports referencing playlists by name. The playlist is then seached in the playlist_dir and the ".m3u" extension is appended to the name: $ beet ls playlist:anotherplaylist The configuration for the plugin looks like this: playlist: relative_to: library playlist_dir: /path/to/playlists The relative_to option specifies how relative paths in playlists are handled. By default, paths are relative to the "library" directory. It also possible to make them relative to the "playlist" or set the option or set it to a fixed path.
Adds M3U playlist support as a query to beets and thus partially resolves issue beetbox#123. The implementation is heavily based on beetbox#2380 by Robin McCorkell. It supports referencing playlists by absolute path: $ beet ls playlist:/path/to/someplaylist.m3u It also supports referencing playlists by name. The playlist is then seached in the playlist_dir and the ".m3u" extension is appended to the name: $ beet ls playlist:anotherplaylist The configuration for the plugin looks like this: playlist: relative_to: library playlist_dir: /path/to/playlists The relative_to option specifies how relative paths in playlists are handled. By default, paths are relative to the "library" directory. It also possible to make them relative to the "playlist" or set the option or set it to a fixed path.
Adds M3U playlist support as a query to beets and thus partially resolves issue beetbox#123. The implementation is heavily based on beetbox#2380 by Robin McCorkell. It supports referencing playlists by absolute path: $ beet ls playlist:/path/to/someplaylist.m3u It also supports referencing playlists by name. The playlist is then seached in the playlist_dir and the ".m3u" extension is appended to the name: $ beet ls playlist:anotherplaylist The configuration for the plugin looks like this: playlist: relative_to: library playlist_dir: /path/to/playlists The relative_to option specifies how relative paths in playlists are handled. By default, paths are relative to the "library" directory. It also possible to make them relative to the "playlist" or set the option or set it to a fixed path.
This plugin provides support for queries on M3U playlist files, giving some of the features requested in #123. In my workflow, this allows me to create and manage playlists with a music client (e.g. ncmpcpp) then leverage Beets to transform those tracks, e.g. exporting and converting them into a separate directory for consumption on another device.
It uses a flexible field type definition to add the
playlist:...
query pattern. I considered using prefixes, but that would make for strange behaviour when combined with a field selector. This approach adds a dynamic 'field' to items that allow for matching.Two configuration parameters are present:
playlist_dir
is required, and defines the location of the playlist directory.relative_to
defines how relative paths in M3U files are specified, as relative to the base music directory or relative to the playlist. It defaults to paths being relative to the base directory, since that is what ncmpcpp does.TODO:
I wanted to get this PR open so people can start commenting on my code