The list can say how long each track is and how good (#567) - #914
Conversation
LIST_SHOW_AUDIO_INFO (off by default) adds duration and quality after the size on every MP3 and FLAC row, in the spelling other servers' lists use: "::INFO:: 10.3MB 4m31s 320/44.1/JS". A VBR average is "~245". audio_info.py reads both with the standard library: ID3v2 skipped, the first frame confirmed by its successor, Xing/Info/VBRI for the frame count, ID3v1 excluded from the audio; FLAC's STREAMINFO with the real bitrate. Anything unreadable keeps its size only; read() never raises. A SQLite cache (LIST_AUDIO_INFO_CACHE) keyed by the row and checked against size and mtime means only new or changed files are opened after the first rebuild. Each list prunes only its own rows, and only when its rebuild publishes. @find sends a row without its audio tail before it would cut the name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW
|
Live test result: this is far too slow on a real, non-trivial library — reporting the numbers before this ships. Tested live by the operator: Before (same library, same evening, without audio info): With That is a ~143x slowdown. Extrapolated to the full 64,136 files: roughly 1.5–2 hours for one rebuild. The operator aborted the run at that point (SIGTERM to Likely cause: What held up on the good side: the abort was clean. The subprocess was in Worth deciding before this ships, given real numbers rather than the CI machines' local disks:
No opinion yet on which of those is right - just the measured numbers, since CI's local disks would never have shown this. |
…limit (#567) Neo's live test on a real 64,136-file NFS library measured the first version at 9.8 files a second: two hours for one rebuild, with every search and request paused. On a network mount the time is round trips. Now, the way QuickList (OmenServe's list maker) does it: - files are only noted during the walk; the ones to read are read after it, LIST_AUDIO_INFO_THREADS (16) at a time; - each file is opened unbuffered and read through a window: one 16 KB request for an ordinary MP3 or FLAC, one more for cover art; the ID3v1 tag is no longer looked for; - the cache is loaded into memory and checked against the size the directory listing gave: no stat, no read for an unchanged file; - LIST_AUDIO_INFO_MINUTES (5) bounds the reading one rebuild does; the rest show their size and the next rebuild reads them. A stopped rebuild keeps what it read. The dashboard shows the reading as its own progress phase. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW
|
Thanks for the live numbers. CI's local disks would never have shown that. Reworked in 19139e3, modelled on how QuickList (OmenServe's list maker, mIRC-scripts/QuickList) does it. On a network mount the time goes on round trips, not bytes, and the first version paid 3–4 of them per file, one file after another. What changed
Speed: measured only synthetically. With 5 ms of injected latency per open and read, 400 files: 1 worker 40 files/s, 4 workers 372/s, 16 workers 1,418/s. Extrapolating from your 9.8 files/s, the first pass on your NFS library should be minutes rather than two hours, but that's an estimate. Could you or the operator re-run the same test? The rebuild's last line now reads Audio info: N read, M unchanged, K left for the next one, which gives the real rate directly. Tests: 40, up from 31. Among them: a barrier that only four concurrent reads can pass, the time limit with an injected clock, a stopped rebuild keeping what it read, and a reader that raises. Every new property was mutation-checked. Full suite 6547 OK. #917 is rebased on top. |
|
Re-tested with the rewrite (19139e3) live, as asked — three full Numbers:
62,699 audio files total on this library; 42 still show size-only after run 3 (presumably genuinely unreadable/corrupt - next run will attempt them again since they were never added to Cache confirmed correct across separate invocations, not just within one run: each run picked up exactly where the last left off (29,971 → 47,023 → 62,657), strictly additive - no file was re-read once it had a cached entry, and nothing was lost between runs (each is a separate Two questions, now that this has real numbers behind it:
🤖 Generated with Claude Code |
Neo's live re-test on the NFS library read at about 55-60 files a second with 16 workers, well below the synthetic estimate. On a network mount the ceiling is the server's; the rebuild's last line now ends "Read at N files a second, 16 at a time", so an operator can try another LIST_AUDIO_INFO_THREADS and compare. Measured on the real clock, not the budget's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW
|
Thanks, that's the test I couldn't run. It covers three rebuilds, persistence across separate subprocesses, and a real NFS server. A correction on the 42: they won't be retried. A file whose read fails still goes into 1. Headroom in 2. Moving the reading out of |
|
Update on Question 1 above (thread headroom): tested it directly. Deleted
Between those two I bumped Caveat, in fairness: run 2 immediately followed run 1 against the same library, so some of the gain could be the NFS client's or server's own cache having just been warmed by run 1's pass over the same directories, not only the thread count - I did not isolate the two (e.g. by re-running at 16 threads a second time to separate "warm cache" from "more threads"). Worth someone repeating with that controlled, but a change this large strongly suggests the thread count matters a lot on real hardware, not just synthetically. Given that, worth considering whether 🤖 Generated with Claude Code |
On Windows before Python 3.13, time.monotonic() ticks every ~15.6 ms. A short batch read inside one tick measured 0 s, so no rate was printed - the rate tests failed on the Windows 3.10 and 3.12 runners. perf_counter is high-resolution everywhere. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW
Neo's cold-cache test on the real NFS library: 16 threads read about 73 files a second, 64 about 236 - partly on a server cache warmed by the run before, so not fully isolated. The default goes to 32, harmless on a local disk; the range to 1-128; and the help says a network drive should try 64, comparing the rate the rebuild reports. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW
|
That settles it better than I could have. Proposed in 37cbde9, yours to take or change:
If you'd rather ship 64 as the default, it's the one number in |
Ninja-FSE's own call, made with the measured numbers from live testing in hand: this repository's proposal (37cbde9) shipped 32 as a cautious middle ground between the old default (16) and the measured 64-thread result, since that result was partly on a warmed cache and not fully isolated from the thread-count change alone. Asked directly, the operator preferred shipping the number actually measured rather than splitting the difference - a plain disk answers 64 concurrent requests as readily as 16, so there is little reason to leave the higher concurrency unused by default. Same value everywhere it appears: defaults.py, update_list.py's getattr() fallback (kept in sync with the real default, matching this codebase's own convention elsewhere), settings.conf.sample (regenerated in an isolated clone, not this checkout - it holds this operator's own live, gitignored settings), settings_help.py, docs/INSTALL.md, docs/UPDATES.md, and the es/fr translations. The two tests that pinned 32 (the clamp-range test's literal defaults.py/update_list.py check, and the two log-line assertions naming the thread count) now pin 64. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ninja-FSE's own call, made with the measured numbers from live testing in hand: this repository's proposal (37cbde9) shipped 32 as a cautious middle ground between the old default (16) and the measured 64-thread result, since that result was partly on a warmed cache and not fully isolated from the thread-count change alone. Asked directly, the operator preferred shipping the number actually measured rather than splitting the difference - a plain disk answers 64 concurrent requests as readily as 16, so there is little reason to leave the higher concurrency unused by default. Same value everywhere it appears: defaults.py, update_list.py's getattr() fallback (kept in sync with the real default, matching this codebase's own convention elsewhere), settings.conf.sample (regenerated in an isolated clone, not this checkout - it holds this operator's own live, gitignored settings), settings_help.py, docs/INSTALL.md, docs/UPDATES.md, and the es/fr translations. The two tests that pinned 32 (the clamp-range test's literal defaults.py/update_list.py check, and the two log-line assertions naming the thread count) now pin 64. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bd2825e to
4842704
Compare
…-version-exists' into tmp-914 # Conflicts: # docs/UPDATES.md
Closes #567.
Stacked on #913 (→ #906 → main). #906 moved the rebuild limits out of Your list, and this adds a field there. #906 had left the category at 14 of its 16-field cap, while on
mainit is full. This also touches the same settings maps andtests/support.pyas #913. I merged the whole stack (#906 + #913 + this) with today'smain, which includes #904/#905/#912, and the full suite passed: 6544 OK.What it does
LIST_SHOW_AUDIO_INFO(off by default, Your list) adds duration and quality after the size on every MP3 and FLAC row:How the issue's open questions were settled
list.pyand other bots already read it. Duration4m31s(minutes go past 59). ChannelsS/JS/DC/M/6ch. VBR average marked~245/44.1/JS: there was no spelling on record, so say if you want a different one.@find <artist> 320works.LIST_AUDIO_INFO_CACHE=./data/audio_info.db, beside the list index. Rows are keyed by folder and name and checked against size and mtime. Each list prunes only its own rows, and only when its rebuild publishes. A cache that can't open is reported, and the list is written with sizes only.Reader (
audio_info.py, stdlib only)read()never raises. Any file it can't read keeps only its size. AutoQ is unaffected: only file rows change, after the size, and the!rarrows are exactly as before.Tests
tests/test_the_list_says_how_long_and_how_good.py(31): every file is built byte by byte, so the expected numbers are the formats' own arithmetic. It covers:@findat the exact name length where the tail decides whether the name gets cut.I mutation-checked 8 properties: sync confirmation, ID3v1 exclusion, the ID3v2 skip, per-list prune, the size/mtime check, prune on publish, the search fallback, and ignoring a changed file. Each mutation fails a test. Full suite: 6538 OK on this branch.
Docs: both changelogs and INSTALL.md (Build the first list).
🤖 Generated with Claude Code
https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW