Compare commits
2 commits
1ff3ca9ca2
...
8932231136
| Author | SHA1 | Date | |
|---|---|---|---|
| 8932231136 | |||
| af43b2bacc |
2 changed files with 68 additions and 24 deletions
13
config.yaml
13
config.yaml
|
|
@ -64,6 +64,9 @@ beetslabels:
|
|||
# in the future? concat: ["label:a", "label:b"]
|
||||
query: "label:effortless"
|
||||
sort: "effortless + exercise"
|
||||
- name: everyday
|
||||
query: "label:everyday"
|
||||
sort: "everyday"
|
||||
|
||||
smartplaylist:
|
||||
relative_to: ~/Music/0beets_playlists
|
||||
|
|
@ -71,23 +74,23 @@ smartplaylist:
|
|||
forward_slash: no
|
||||
playlists:
|
||||
- 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"
|
||||
query: ['plfocus:1']
|
||||
- name: "warmth.m3u"
|
||||
query: ['plwarmth:1']
|
||||
- name: "relax.m3u"
|
||||
query: ['plrelax:1']
|
||||
- name: "everyday.m3u"
|
||||
query: ['^pleveryday::^$ pleveryday-']
|
||||
- name: "word.m3u"
|
||||
query: ['plword:1']
|
||||
- name: "control.m3u"
|
||||
query: ['plcontrol:1']
|
||||
- name: "precision.m3u"
|
||||
query: ['plprecision:1']
|
||||
- name: "exercise.m3u"
|
||||
query: ['sorted_playlist:exercise'] # ['^plexercise::^$ plexercise-']
|
||||
- name: "chiptune.m3u"
|
||||
query: ['genre:chiptune', 'genre:8-bit', 'plchiptune:1']
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from beets.ui.commands.modify import print_and_modify
|
|||
from beets.dbcore import FieldQuery
|
||||
from beets.dbcore.query import SlowFieldSort
|
||||
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
|
@ -218,13 +219,20 @@ def edit_labels(lib, opts, args):
|
|||
finally:
|
||||
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
|
||||
# Format: "Artist - Title:\n - key: value\n - key2: value2\n\n"
|
||||
changes = []
|
||||
i = 0
|
||||
item_idx = 0
|
||||
|
||||
while i < len(lines) and item_idx < len(items_list):
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
|
||||
# Skip blank lines
|
||||
|
|
@ -234,18 +242,17 @@ def edit_labels(lib, opts, args):
|
|||
|
||||
# Check if this is an item header (ends with :)
|
||||
if line.endswith(':'):
|
||||
item = items_list[item_idx]
|
||||
item_idx += 1
|
||||
item = item_by_header.get(line)
|
||||
i += 1
|
||||
|
||||
# Now read all label lines for this item
|
||||
new_labels = {}
|
||||
while i < len(lines):
|
||||
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('- '):
|
||||
# Parse: " - key: value"
|
||||
label_content = label_line.strip()[2:].strip() # Remove " - "
|
||||
# Parse: "- key: value"
|
||||
label_content = label_line.strip()[2:].strip() # Remove "- "
|
||||
if ':' in label_content:
|
||||
key, val = label_content.split(':', 1)
|
||||
key = key.strip()
|
||||
|
|
@ -253,6 +260,7 @@ def edit_labels(lib, opts, args):
|
|||
try:
|
||||
new_labels[key] = int(val)
|
||||
except ValueError:
|
||||
if item:
|
||||
print_(f"Warning: Invalid value for label '{key}' in item '{item.title}', skipping")
|
||||
else:
|
||||
new_labels[label_content.strip()] = 1
|
||||
|
|
@ -261,6 +269,10 @@ def edit_labels(lib, opts, args):
|
|||
# Not a label line, done with this item
|
||||
break
|
||||
|
||||
if item is None:
|
||||
print_(f"Warning: Header '{line}' not found in query results, skipping")
|
||||
continue
|
||||
|
||||
# Compare with old labels
|
||||
old_labels = {}
|
||||
if LABELS_FIELD_NAME in item and item[LABELS_FIELD_NAME]:
|
||||
|
|
@ -272,6 +284,24 @@ def edit_labels(lib, opts, args):
|
|||
# Unexpected format, skip
|
||||
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:
|
||||
print_("No changes to make.")
|
||||
return
|
||||
|
|
@ -321,19 +351,30 @@ class HasLabelQuery(FieldQuery):
|
|||
super().__init__(LABELS_FIELD_NAME, pattern, False)
|
||||
|
||||
@classmethod
|
||||
def value_match(self, pattern, labels):
|
||||
if labels is not None:
|
||||
label = pattern
|
||||
value = None
|
||||
if "." in pattern:
|
||||
label, value = pattern.split(".")
|
||||
|
||||
if value is None:
|
||||
return label in labels
|
||||
else:
|
||||
return label in labels and str(labels[label]) == value
|
||||
def value_match(cls, pattern, labels):
|
||||
if not labels:
|
||||
return False
|
||||
|
||||
match = re.match(r'^([^.]+)(?:\.(gt|lt|eq)\.(\d+))?$', pattern)
|
||||
if not match:
|
||||
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):
|
||||
def __init__(self, field, ascending=True, case_insensitive=True):
|
||||
super().__init__(field, ascending, case_insensitive)
|
||||
|
|
|
|||
Loading…
Reference in a new issue