Ranges not working in `beet ls` with album fields in item/track context

Greetings,

I’ve run into an odd behavior, and I’m wondering if it’s behaving as expected and maybe I’m missing something, or if it’s an actual bug.

I’ve enabled the ‘missing’ plugin, and am doing searches based on albums which are missing tracks (albumtotal - len(items)). Here are the queries i’ve tested, and the behaviors:

  • beet ls -a missing:1.. - returns albums missing at least one track, as expected
  • beet ls -a missing:1 - returns albums missing exactly one track, as expected
  • beet ls missing:1 - returns tracks in albums missing exactly one track, as expected
  • beet ls missing:1.. - returns nothing. This is where I’m confused. Only the range fails, despite the album field type being set to integer. It seems like something gets confused when the query falls back to the album field from item context. I’ve tried using the unmerged pull request to allow for album fields in the queries in paths, which works, but ran into this same behavior. I was going to report it to them, but then discovered ls exhibits the same behavior. I can’t set a separate path format for when missing:1...

My actual goal is to be able to separate tracks where I only have a single track in the album, even when not flagged as a singleton, to ease navigation when on my digital audio player (ideally using beets-alternatives to set that up), but at the moment I’m failing to be able to do so, since albumtotal, missing, and an attempt at creating an ‘existing’ field (just len(items)) run into first the inability to use album fields as path format queries, and then this issue.

On a related note, I have a minimal ‘existing’ plugin which just returns len(album.items()) in an album field, and has the type set to types.INTEGER. If I do beet ls -a existing:1 it gives the albums where I have one track, but if I do beet ls existing:1, it returns tracks in albums with 1 track, but also tracks in albums with 10 tracks, and tracks in albums with 21 tracks. It ignored the type and did a text search, but only when in item/track context.

Fair warning that I’ve only been using this project for a day, so please forgive any incorrect terms, and please let me know if I’ve made any flawed assumptions here.

I’d appreciate any thoughts or input on this. Thanks!

-kergoth

That’s strange! I don’t have an immediate explanation. I do note that you occasionally use = instead of : in your examples, but I assume that’s a transcription error. These are some tricky edge cases that could probably use further digging.

Indeed, the = thing was a transcription error, thanks. And good to know it probably wasn’t something immediately obviously wrong on my end, I wasn’t at all certain of that :slight_smile:

I just tried this on my own library and I find that the non-album query never matches. beet ls -a missing:1 returns results but beet ls missing:1 does not. I’m not sure it’s actually related to ranges specifically.

The missing plugin provides an album-level template field called missing but no item-level field. As far as I know these aren’t propagated to items automatically for queries. I tried a quick hacky change to beets’ internals to implement this propagation:

diff --git a/beets/library.py b/beets/library.py
index 16db1e97..71b6db22 100644
--- a/beets/library.py
+++ b/beets/library.py
@@ -526,7 +526,17 @@ class Item(LibModel):
 
     @classmethod
     def _getters(cls):
-        getters = plugins.item_field_getters()
+        def atoi(f, ag):
+            def ig(i):
+                a = i.get_album()
+                if a:
+                    return ag(a)
+                else:
+                    return cls._type(f).null
+            return ig
+        getters = {f: atoi(f, g)
+                   for f, g in plugins.album_field_getters().items()}
+        getters.update(plugins.item_field_getters())
         getters['singleton'] = lambda i: i.album_id is None
         getters['filesize'] = Item.try_filesize  # In bytes.
         return getters
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index 327db6b0..c3adc72d 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -1145,7 +1145,10 @@ def _setup(options, lib=None):
         plugins.send("library_opened", lib=lib)
 
     # Add types and queries defined by plugins.
-    library.Item._types.update(plugins.types(library.Item))
+    at = plugins.types(library.Album)
+    at.update(library.Item._types)
+    at.update(plugins.types(library.Item))
+    library.Item._types = at
     library.Album._types.update(plugins.types(library.Album))
     library.Item._queries.update(plugins.named_queries(library.Item))
     library.Album._queries.update(plugins.named_queries(library.Album))

After making this change beet ls missing:1 returns the items from albums with 1 missing track (and beet ls missing:1.. works as expected). It’s pretty slow though since it has to lookup the album for each item in the entire library.

1 Like

Thanks for the help with this. I was testing a pull request that also implemented the fallback, but did so in a way that introduced the bugs and problems I was running into, so false alarm. I’ll report back to the dev that opened the request, since this is specific to that code. Thanks again!