v0.32.0: folder tree in Rabbit Sync Settings, quieter art pass, tink, VLC-style Preferences

- Rabbit Sync Settings shows the sidebar's folder tree. A folder has its own
  tristate checkbox; a fully ticked folder is stored as device_sync.folder_ids
  and follows the folder, so playlists added to it later sync too.
- The album-art pass compares covers against the sizes the planner's device
  scan already read (plan.art_sizes) instead of an MTP stat per album, caches
  "no art" as a .none marker, and only unlinks covers that are really there.
  The summary reports covers actually updated rather than every cover.
- A quiet tink when a Rabbit sync, export or URL import finishes, with an
  on/off switch (sound_effects, on by default).
- Preferences is now General / Appearance / Integrations, a list on the left
  and the page on the right, opened big enough for the tallest page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 00:45:42 -07:00
co-authored by Claude Opus 5.5
parent 4a019d4e27
commit ba795e3c69
13 changed files with 699 additions and 80 deletions
+41
View File
@@ -0,0 +1,41 @@
"""Render lintunes/sounds/tink.wav — the quiet "done" chime.
A bell-ish 1.6 kHz partial with a softer inharmonic overtone, a 3 ms attack
(no click) and a fast exponential decay. Run once; the WAV is committed.
python3 scripts/make_tink.py
"""
import wave
from pathlib import Path
import numpy as np
RATE = 44100
LENGTH = 0.35 # seconds; the decay is inaudible well before this
PEAK_DBFS = -18.0
def render() -> np.ndarray:
t = np.arange(int(RATE * LENGTH)) / RATE
tone = (np.sin(2 * np.pi * 1600 * t)
+ 0.35 * np.sin(2 * np.pi * 1600 * 2.76 * t) * np.exp(-t * 40))
envelope = np.exp(-t * 18) * np.minimum(t / 0.003, 1.0)
signal = tone * envelope
signal /= np.abs(signal).max()
return signal * 10 ** (PEAK_DBFS / 20)
def main():
out = Path(__file__).resolve().parent.parent / "lintunes" / "sounds" / "tink.wav"
out.parent.mkdir(parents=True, exist_ok=True)
pcm = (render() * 32767).astype("<i2")
with wave.open(str(out), "wb") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(RATE)
f.writeframes(pcm.tobytes())
print(out)
if __name__ == "__main__":
main()