-
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
Playlist plugin #3145
Playlist plugin #3145
Conversation
One of the main problems is that the query is rather slow with large libraries. Since |
2ceb8ef
to
9887f1e
Compare
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.
Cool! This is a really neat prototype.
I think the issue you're running into with the query is a bit more fundamental than it might seem at first: field-qualified queries like foo:bar
are currently quite intrinsically tied to the existence of a field called foo
(either built-in or flexible). You're introducing a type for a "fake" field called playlist
here, which works but isn't really what the query system is expecting. Because PlaylistQuery
is a FieldQuery
, the system really wants to do a playlist-style query of some field (and that field, here, is always called playlist
).
I agree that the syntax playlist:foo
would be desirable for this plugin. So maybe it's worth digging a little bit into building special support for these "pseudo-fields" for invoking special queries with field-like syntax but without querying actual, underlying fields. For what it's worth, the random
plugin (which currently does not define a query) could use this too: a query term like random:5
could use the same mechanism.
beets/autotag/match.py
Outdated
@@ -345,7 +345,7 @@ def _sort_candidates(candidates): | |||
return sorted(candidates, key=lambda match: match.distance) | |||
|
|||
|
|||
def _add_candidate(items, results, info): | |||
def _add_candidate(items, results, info, force=False): |
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 addition of the force
parameter seems to be an unrelated change that got included in this PR by mistake?
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.
Yeah, sorry about that.
beetsplug/playlist.py
Outdated
config = beets.config['playlist'] | ||
|
||
# Get the full path to the playlist | ||
if os.path.isabs(beets.util.syspath(pattern)): |
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 might recommend using isfile
. That way, anything that refers to an actual file on disk will work, and if the file doesn't exist, we'll fall back on searching playlist_dir
.
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.
That would introduce a race condition in case the file is deleted between the isfile
and the open
call. With proper error handling this wouldn't be too bad, but I restructured the code a bit so that isfile
is not needed at all.
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.
Looks perfect. 👍
beetsplug/playlist.py
Outdated
relative_to = beets.util.bytestring_path(relative_to) | ||
|
||
self.paths = [] | ||
with open(beets.util.syspath(playlist_path), 'rb') as f: |
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.
It might be nice to have some error handling here in case the playlist does not exist (either as a plain filename or in the centralized playlist 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.
Done. It would be nice to print some error message to the console, but I don't know how. I don't really want to assign the self._log
attribute of PlaylistPlugin
to the PlaylistQuery
class, but I don't see another way to do this.
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.
Good point. Threading through the plugin-specific log object here seems too hard. Maybe we can work something out as part of the more general "pseudo-field" query infrastructure.
beetsplug/playlist.py
Outdated
# Playlist is empty | ||
return '0', () | ||
clause = 'BYTELOWER(path) IN ({0})'.format( | ||
', '.join('BYTELOWER(?)' for path in self.paths)) |
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 note that this query is always case-insensitive. That's probably fine, but it's probably worth noting, at least in the docstring for this class. (See the built-in PathQuery
if you're curious—it can either be case-sensitive or case-insensitive, depending on the filesystem.)
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.
Well, the problem is that Playlists can contain multiple entries from different filesystems. I should probably make this case sensitive - case-insensitivity is more a convenience feature for FAT/NTFS users and not actually needed.
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.
Also a good point! Case-sensitive sounds fine; we can revisit this if it gets inconvenient.
- **relative_to**: Interpret paths in the playlist files relative to a base | ||
directory. It is also possible to set it to ``playlist`` to use the | ||
playlist's parent directory as base directory. | ||
Default: ``library`` |
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.
It's probably a good idea to mention explicitly that there are three options here: playlist
, library
, or a path.
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.
Done.
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.
Implement the col_clause method for faster, sqlite-based querying. This will only make a difference if the "fast" kwarg is set to True.
83ef4dc
to
d52dcdd
Compare
For some reason some testcase on Windows test keep throwing errors: Might be related to spaces in the path, but I'm not sure and I can't fix this since I don't have Windows. The other tests should work fine now. |
Looks like spaces in the path were indeed the problem. Should work now. The performance issue could be fixed via #3149. If that PR is merged, we only need to override the @classmethod
def check_fast(self, key, model_cls):
return issubclass(model_cls, beets.library.Item) |
Is anything else missing? Should I squash the commits? |
Awesome! This looks perfect. Thank you for tracking down the Windows problem even without access to a Windows machine—that was heroic. ✨ No need for a squash from my perspective. I'll merge this now. 🚀 |
As discussed here: #3145 (review) This would replace the need for #3149.
Adds M3U playlist support as a query to beets and thus partially
resolves issue #123. The implementation is heavily based on #2380 by
Robin McCorkell.
It supports referencing playlists by absolute path:
It also supports referencing playlists by name. The playlist is then
seached in the
playlist_dir
and the.m3u
extension is appended to thename:
The configuration for the plugin looks like this:
The
relative_to
option specifies how relative paths in playlists arehandled. By default, paths are relative to the
library
directory. Italso possible to make them relative to the
playlist
or set the optionor set it to a fixed path.