Compare commits

...

2 commits

Author SHA1 Message Date
8932231136 Change up this syntax a little 2026-06-06 09:26:48 -05:00
af43b2bacc Okay, this is an interface I am in love with 2026-06-06 09:26:38 -05:00
2 changed files with 68 additions and 24 deletions

View file

@ -64,6 +64,9 @@ beetslabels:
# in the future? concat: ["label:a", "label:b"] # in the future? concat: ["label:a", "label:b"]
query: "label:effortless" query: "label:effortless"
sort: "effortless + exercise" sort: "effortless + exercise"
- name: everyday
query: "label:everyday"
sort: "everyday"
smartplaylist: smartplaylist:
relative_to: ~/Music/0beets_playlists relative_to: ~/Music/0beets_playlists
@ -71,23 +74,23 @@ smartplaylist:
forward_slash: no forward_slash: no
playlists: playlists:
- name: "favorites.m3u" - name: "favorites.m3u"
query: ['plfavorites:1'] query: ['label:favorites']
- name: "everyday.m3u"
query: ['sorted_playlist:everyday']
- name: "exercise.m3u"
query: ['sorted_playlist:exercise'] # ['^plexercise::^$ plexercise-']
- name: "focus.m3u" - name: "focus.m3u"
query: ['plfocus:1'] query: ['plfocus:1']
- name: "warmth.m3u" - name: "warmth.m3u"
query: ['plwarmth:1'] query: ['plwarmth:1']
- name: "relax.m3u" - name: "relax.m3u"
query: ['plrelax:1'] query: ['plrelax:1']
- name: "everyday.m3u"
query: ['^pleveryday::^$ pleveryday-']
- name: "word.m3u" - name: "word.m3u"
query: ['plword:1'] query: ['plword:1']
- name: "control.m3u" - name: "control.m3u"
query: ['plcontrol:1'] query: ['plcontrol:1']
- name: "precision.m3u" - name: "precision.m3u"
query: ['plprecision:1'] query: ['plprecision:1']
- name: "exercise.m3u"
query: ['sorted_playlist:exercise'] # ['^plexercise::^$ plexercise-']
- name: "chiptune.m3u" - name: "chiptune.m3u"
query: ['genre:chiptune', 'genre:8-bit', 'plchiptune:1'] query: ['genre:chiptune', 'genre:8-bit', 'plchiptune:1']

View file

@ -4,6 +4,7 @@ from beets.ui.commands.modify import print_and_modify
from beets.dbcore import FieldQuery from beets.dbcore import FieldQuery
from beets.dbcore.query import SlowFieldSort from beets.dbcore.query import SlowFieldSort
import re
import json import json
import os import os
import subprocess import subprocess
@ -218,13 +219,20 @@ def edit_labels(lib, opts, args):
finally: finally:
os.remove(temp_file.name) os.remove(temp_file.name)
# Build a lookup from header string to item
def item_header(item):
if item.artist:
return f"{item.artist} - {item.title}:"
return f"{item.title}:"
item_by_header = {item_header(item): item for item in items_list}
# Parse the edited labels and apply changes # Parse the edited labels and apply changes
# Format: "Artist - Title:\n - key: value\n - key2: value2\n\n" # Format: "Artist - Title:\n - key: value\n - key2: value2\n\n"
changes = [] changes = []
i = 0 i = 0
item_idx = 0
while i < len(lines) and item_idx < len(items_list): while i < len(lines):
line = lines[i].strip() line = lines[i].strip()
# Skip blank lines # Skip blank lines
@ -234,18 +242,17 @@ def edit_labels(lib, opts, args):
# Check if this is an item header (ends with :) # Check if this is an item header (ends with :)
if line.endswith(':'): if line.endswith(':'):
item = items_list[item_idx] item = item_by_header.get(line)
item_idx += 1
i += 1 i += 1
# Now read all label lines for this item # Now read all label lines for this item
new_labels = {} new_labels = {}
while i < len(lines): while i < len(lines):
label_line = lines[i] label_line = lines[i]
# Check if this is a label line (starts with " - ") # Check if this is a label line (starts with "- ")
if label_line.startswith('- '): if label_line.startswith('- '):
# Parse: " - key: value" # Parse: "- key: value"
label_content = label_line.strip()[2:].strip() # Remove " - " label_content = label_line.strip()[2:].strip() # Remove "- "
if ':' in label_content: if ':' in label_content:
key, val = label_content.split(':', 1) key, val = label_content.split(':', 1)
key = key.strip() key = key.strip()
@ -253,7 +260,8 @@ def edit_labels(lib, opts, args):
try: try:
new_labels[key] = int(val) new_labels[key] = int(val)
except ValueError: except ValueError:
print_(f"Warning: Invalid value for label '{key}' in item '{item.title}', skipping") if item:
print_(f"Warning: Invalid value for label '{key}' in item '{item.title}', skipping")
else: else:
new_labels[label_content.strip()] = 1 new_labels[label_content.strip()] = 1
i += 1 i += 1
@ -261,6 +269,10 @@ def edit_labels(lib, opts, args):
# Not a label line, done with this item # Not a label line, done with this item
break break
if item is None:
print_(f"Warning: Header '{line}' not found in query results, skipping")
continue
# Compare with old labels # Compare with old labels
old_labels = {} old_labels = {}
if LABELS_FIELD_NAME in item and item[LABELS_FIELD_NAME]: if LABELS_FIELD_NAME in item and item[LABELS_FIELD_NAME]:
@ -272,6 +284,24 @@ def edit_labels(lib, opts, args):
# Unexpected format, skip # Unexpected format, skip
i += 1 i += 1
# Items removed from the file get their labels cleared
headers_in_file = set()
i = 0
while i < len(lines):
line = lines[i].strip()
if line.endswith(':') and line in item_by_header:
headers_in_file.add(line)
i += 1
for item in items_list:
header = item_header(item)
if header not in headers_in_file:
old_labels = {}
if LABELS_FIELD_NAME in item and item[LABELS_FIELD_NAME]:
old_labels = item[LABELS_FIELD_NAME]
if old_labels:
changes.append((item, {}))
if not changes: if not changes:
print_("No changes to make.") print_("No changes to make.")
return return
@ -321,18 +351,29 @@ class HasLabelQuery(FieldQuery):
super().__init__(LABELS_FIELD_NAME, pattern, False) super().__init__(LABELS_FIELD_NAME, pattern, False)
@classmethod @classmethod
def value_match(self, pattern, labels): def value_match(cls, pattern, labels):
if labels is not None: if not labels:
label = pattern return False
value = None
if "." in pattern:
label, value = pattern.split(".")
if value is None: match = re.match(r'^([^.]+)(?:\.(gt|lt|eq)\.(\d+))?$', pattern)
return label in labels if not match:
else: return False
return label in labels and str(labels[label]) == value
return False label, op, val = match.group(1), match.group(2), match.group(3)
if label not in labels:
return False
if op is None:
return True
value = labels[label]
num = int(val)
if op == 'eq':
return value == num
elif op == 'gt':
return value > num
elif op == 'lt':
return value < num
class LabelValueSort(SlowFieldSort): class LabelValueSort(SlowFieldSort):
def __init__(self, field, ascending=True, case_insensitive=True): def __init__(self, field, ascending=True, case_insensitive=True):