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"]
|
# 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']
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue