Skip to content

Releases: sydlexius/canticle

v1.38.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 20:31
Immutable release. Only release title and notes can be modified.
fc30877

Canticle v1.38.0

Highlights

This release brings the Musixmatch lane back to life and gives tracks that no provider has lyrics for a status of their own.

Musixmatch works again. Lookups had been returning the same unrelated lyric for every track. They now return the right lyrics again.

"Nothing found anywhere" is no longer "done". A track that exhausts its re-check budget with every provider missing is now retired to a new unavailable status instead of done, with its own dashboard tile, chart series and Reports row. queue recheck --retired revives these tracks.

What's changed

Added

  • An unavailable queue status for tracks retired after every provider repeatedly found nothing, shown on the dashboard, in Reports and in Recent Outcomes (#477, #938, #941, #945)
  • The client identity a Musixmatch token was minted for is recorded with the token, written together in one transaction, so a later identity change re-mints cleanly (#934, #943)
  • scan reconcile-paths repairs queue rows an earlier relink left pointing at a directory that no longer exists (#921, #940)
  • A dashboard line reporting the last scan reconcile-lrc --yes pass: when it ran and how many files it rewrote (#929, #931)
  • A one-time serve-startup check that reports stacked .lrc files and names the command that fixes them, without rewriting anything (#925)

Fixed

  • Musixmatch requests use the Android client identity, and a degenerate (all-zero) token is rejected and never saved (#934, #939)
  • A stored all-zero token from an earlier build is discarded at startup and a fresh one minted (#939)
  • A track with no title, ISRC or Spotify ID is skipped before any request is made, and a stable Musixmatch 4xx is treated as a miss rather than retried forever (#479, #933)
  • A file moved and relinked by prune keeps a valid output destination, instead of failing every write and re-fetching on each retry (#921, #935)
  • The dashboard tile tooltips describe what Failed, Deferred and Unavailable actually mean (#945)
  • make doctor no longer reports correctly wired git hooks as broken in a worktree or with an absolute hooks path (#918)

Documentation

  • A page on LRC normalization: why stacked timestamps render as literal text in simple players, what scan reconcile-lrc rewrites, and what the .lrc.orig backup is, linked from the dashboard (#930, #932)

Maintenance

  • Go toolchain 1.27.1 and golangci-lint v2.13.2 (#927)

Upgrade notes

Re-enable Musixmatch if you turned it off. An install that stored the all-zero token recovers on its own: the token is discarded at startup and a fresh one minted. An install still holding a real token from before the retirement keeps it, and it is replaced only if Musixmatch explicitly asks for a renewal. If lookups keep failing after upgrading, replace the token in Settings or via MUSIXMATCH_TOKEN.

Queue counts shift. Migration 049 moves every exhausted miss from done to unavailable. On /metrics and /api/v1/status the done count drops by that amount and a new unavailable value appears, so an external dashboard keyed on done will change. queue clear --done no longer removes these tracks.

Stale output paths. If queue rows fail with "output dir does not exist" after files were moved, run canticle scan reconcile-paths (a dry run), then again with --yes.

Retired tracks are not revived automatically when the provider set changes; run queue recheck --retired (#944).

No detector-sidecar changes in this release.

Install

docker pull ghcr.io/sydlexius/canticle:1.38.0

Full changelog: v1.37.1...v1.38.0

v1.37.1

Choose a tag to compare

@github-actions github-actions released this 08 Sep 03:19
Immutable release. Only release title and notes can be modified.
v1.37.1
e8f58da

Canticle v1.37.1

Highlights

A patch release fixing one defect in the InnerTube lane: a track whose lyrics came back without timings failed outright, and the lyrics were discarded.

YouTube Music returns two response shapes and only one carries timings. Such a response now produces a .txt. Not .lrc -- the timings are genuinely absent for those tracks, and --upgrade promotes the file if that changes.

What's changed

Fixed

  • An InnerTube response carrying lyrics without timings is saved as unsynced text instead of failing the fetch (#917)
  • An explicit null timing range is treated as malformed rather than as plain text (#917)
  • A track's synced-lyrics flag is derived from the result actually returned (#917)

Upgrade notes

Affected tracks were queued for retry, not abandoned, and are picked up again after upgrading. canticle queue retry <id> clears the backoff wait.

If InnerTube is ordered ahead of another provider, an unsynced result now settles the track instead of falling through, so it can settle as .txt where a later provider had supplied .lrc. Being reconsidered in #915.

No detector-sidecar changes in this release; 1.37.1 is identical in substance to 1.37.0.

Install

docker pull ghcr.io/sydlexius/canticle:1.37.1

Full changelog: v1.37.0...v1.37.1

v1.37.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 00:48
Immutable release. Only release title and notes can be modified.
7fd4238

Canticle v1.37.0

Highlights

This release adds a third lyrics provider: YouTube Music's InnerTube API. It needs no token and returns timed cues, so a hit gives you a synced .lrc.

Off by default. Set it as your primary provider, or pick it as a fallback. It reaches material the other providers miss but matches less of a general library, so it earns a fallback slot rather than the default. Enabling it triggers a library-wide re-fetch on the next start.

Attribution follows the track. InnerTube multiplexes between upstream licensors per track, so [source:] stays the provider and a new [upstream:] tag carries the licensor that served each result.

What's changed

Added

  • A YouTube Music InnerTube provider lane: transport, the search/next/browse call chain, candidate ranking, and timed-cue decoding (#867, #871, #873, #874, #876, #877, #880, #882, #884, #886, #889)
  • Per-result upstream licensor recorded in provenance as [upstream:], distinct from the lane in [source:] (#859, #907)
  • A dedicated providers.innertube_cooldown_seconds key, so the lane paces independently of the Musixmatch cooldown (#858, #909)
  • Per-request pacing on the lane, counted per outbound request rather than per lookup (#885)
  • Detection of a provider lane that has stopped discriminating between candidates (#844)

Fixed

  • Candidate matching rejects siblings that differ only by a release year, disagreeing guest credits, or a reordered artist list (#879, #881, #893, #899)
  • The search-result gate widened for release packaging and dashed upload titles, without admitting unrelated matches (#892, #906)
  • The live gateway's musicShelfRenderer shape is parsed correctly (#894, #895)
  • Redirects are pinned to the caller's origin (#876)
  • Musixmatch parses subtitle_body and rejects non-corresponding matches (#840)
  • Settings rejects a detector-ordering and providers-mode combination that conflicts, instead of saving it and behaving unpredictably later (#846)
  • The provider order picker can actually express a reorder, so the order you set is the order you get (#843)

Documentation

  • The InnerTube lane, its configuration surface, and its per-track attribution model are documented, along with a reading of both upstream licensors' published terms (#860, #864)

Security

  • golang.org/x/crypto bumped to clear two SSH denial-of-service advisories (#834)
  • mkdocs-material bumped in the docs lockfile for GHSA-xvg9-69gf-fjrf (#835)
  • util-linux floor pinned in the runtime image to remediate a batch of advisories (#866)

v1.36.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 22:50
Immutable release. Only release title and notes can be modified.
d8868e4

Canticle v1.36.0

Highlights

This release makes the periodic library scan actually run on a schedule you choose, and fixes the reason it was not running at all.

The scan now fires on the wall clock. Its timer was previously anchored to process start, so a container restarted on any regular cadence reset the countdown before the interval elapsed and the periodic scan never fired. The only scan that ran was the one at startup. Scans are now scheduled against the clock, so a nightly restart no longer cancels them.

You can say when. The new [server.scan_schedule] section replaces a raw interval with a frequency and a time: set frequency to hourly, daily, weekly or off, at to an HH:MM local time, and day for a weekly scan. It is editable from Settings in the web UI. Point it at a quiet hour and the library walk happens then, instead of whenever the process last came up. scan_on_start is off by default -- the filesystem watcher already catches changes, so a restart no longer costs a full walk.

Provider attribution now names the provider that actually served a track. The worker labeled its primary lane from a constant rather than from the provider it was given, so an install whose primary is not Musixmatch recorded and displayed every result under the wrong name. New results are attributed correctly; results recorded before this release are not rewritten, and the affected window is documented in the user guide.

What's changed

Added

  • A [server.scan_schedule] configuration section that schedules the library scan by frequency and wall-clock time, superseding scan_interval_seconds (#816, #817)
  • Settings can save the scan schedule, with the surface documented in the user guide (#819)

Fixed

  • The periodic library scan is scheduled on the wall clock instead of from process start, so a restart no longer cancels it before it ever fires (#818, #726)
  • The primary provider lane is named from the fetcher it was given rather than a hardcoded constant, so results are credited to the provider that served them (#826)
  • A queue item cleared by a release records the cause instead of discarding it (#820)
  • scan purge-provenance refuses to delete a sidecar whose [source:] tag names a different provider than the database does, rather than deleting on disputed provenance (#827)

Documentation

  • The provider lane-attribution window is recorded against both the metrics counters and the Provider effectiveness report, including which instrument to prefer for that period (#828)

Security

  • expat pinned in the container image to clear two HIGH severity advisories (#826)

v1.35.1

Choose a tag to compare

@github-actions github-actions released this 29 Aug 04:08
Immutable release. Only release title and notes can be modified.
2a7aff5

Canticle v1.35.1

Highlights

A patch release fixing one defect: on a typical container install, the timing sweep introduced in v1.35.0 could not actually remediate anything.

Remediation now works when the quarantine directory and your music are on different volumes. Moving a file used a single rename, which cannot cross a filesystem boundary. The quarantine directory lives beside the database, and on the standard container layout that is a different volume from the media library, so every demote and quarantine failed. Canticle now falls back to a copy when a move crosses volumes.

Nothing was ever damaged by this. A failed action left the file exactly where it was and the track was queued to be retried, so the only consequence was that the sweep reported work it had not done. If you enabled timing_validation on v1.35.0 and saw failed actions in the log, upgrading is all that is needed.

What's changed

Fixed

  • A demote, quarantine, or purge that crosses a filesystem boundary now succeeds instead of failing (#810)

Upgrade notes

If you enabled the timing sweep on v1.35.0 and turned its actions off after seeing failures, you can set on_mis_synced and on_categorical back to their acting values after upgrading.

Files affected while the defect was present were left untouched and their tracks were never marked as done, so the sweep picks them up again on its next cycle. No manual recovery is needed.

The copy path keeps the same guarantees the move had: an existing file is never overwritten, the copy is flushed to disk before the original is removed, and a failed copy leaves the original in place. A move within one volume still uses the original rename.

No migrations run in this release, and no configuration changes are required.

No detector-sidecar changes in this release.

Install

docker pull ghcr.io/sydlexius/canticle:1.35.1

Full changelog: v1.35.0...v1.35.1

v1.35.0

Choose a tag to compare

@github-actions github-actions released this 29 Aug 02:43
Immutable release. Only release title and notes can be modified.
ff1c515

Canticle v1.35.0

Highlights

This release finishes lyric timing validation: Canticle now finds and fixes synced lyrics that do not line up with the audio they sit beside, on its own.

Serve mode re-judges the lyrics you already have. Until now, timing was only checked when a lyric was first fetched, so every .lrc written before that check existed went unexamined unless you ran canticle revalidate by hand. Set timing_validation.enabled and timing_validation.revalidate_existing, and a background sweep works through that backlog a batch at a time, then idles.

It is designed to leave your disks alone. The sweep never re-scans the library: it works from a list of tracks whose lyrics have never been judged, and a judged file is recorded and never looked at again. On a caught-up install a cycle costs one database query and touches no files at all.

You can watch before you let it act. Set both actions to off for an observability-only mode: every verdict is still recorded and visible on /metrics and in the new Review queue report, and nothing on disk is touched.

What's changed

Added

  • A serve-mode sweep that re-judges existing synced lyrics and remediates the ones whose timing does not fit (#443, #437)
  • The [timing_validation] configuration section, reachable from config get/set/list (#443)
  • Timing verdicts on /metrics and a Review queue report in the web UI (#629)

Fixed

  • A relinked track returns to the queue as pending rather than failed, so it is actually retried (#789)
  • The audio-detector lane reports why it failed instead of collapsing every cause into one error (#790)

Changed

  • Resolving a lyric's companion audio no longer lists its directory, which keeps a large library's disks quieter (#691)
  • Product name capitalized consistently in user-facing text, and enforced going forward (#788)

Security

  • OpenSSL pinned to clear a batch of HIGH severity advisories in the container image (#754)

Upgrade notes

The sweep is off by default and stays off until you turn it on. Both timing_validation.enabled and timing_validation.revalidate_existing must be set: the first says the feature is live, the second says an unattended pass may touch lyrics written before this release. Nothing changes on an existing install until both are true.

It moves and deletes files, so read the actions before enabling it. on_mis_synced defaults to demote, which keeps the words as a .txt beside the audio and sets the .lrc aside; on_categorical defaults to quarantine, which sets the file aside without keeping anything. Quarantined files are moved under a quarantine directory next to the database and can be moved back. purge deletes outright and is never a default. Setting either to off records the verdict and touches nothing.

Judging is one-way. A lyric that has been judged is not re-examined, and editing an .lrc by hand does not re-queue it. Use canticle revalidate to re-check a file that already carries a verdict.

Draining a backlog is gradual by design. Each cycle judges 100 sidecars by default (timing_validation.revalidate_batch) and reads a duration for any audio file it has not measured before, once per file. A large backlog takes several cycles rather than one burst, which is what keeps a parked disk array from waking for the whole job.

canticle revalidate is unchanged, still dry-run by default, and previews exactly what the sweep would do.

No migrations run in this release, and no configuration changes are required.

No detector-sidecar changes in this release.

Install

docker pull ghcr.io/sydlexius/canticle:1.35.0

Full changelog: v1.34.0...v1.35.0

v1.34.0

Choose a tag to compare

@github-actions github-actions released this 22 Aug 21:08
Immutable release. Only release title and notes can be modified.
de06b36

Canticle v1.34.0

Highlights

This release recovers tracks that a library reorganization made Canticle forget.

A track whose file moved is re-attached instead of retired. When a folder is renamed, the audio file and its lyric sidecar move together, so the file arrives at its new path already settled and the scan skipped it before ever indexing it -- while the queue row still pointed at the old path and was retired as permanently unresolvable. Three changes close the loop: the scan now indexes a settled file it has never seen, the prune sweep consults an exact-title tier before retiring an identity-less row, and both share one resolver.

Retirement is no longer terminal. A row retired this way is reconsidered once its file turns up again, so previously lost tracks recover on their own.

What's changed

Fixed

  • A moved track is re-attached to its new path rather than retired as unresolvable (#740, #786)
  • The scan indexes a settled file missing from its index, so a relocated file becomes visible to recovery (#786)
  • Two tools no longer report a remedy that would not have worked (#779)

Added

  • A shared 1:1 name-similarity resolver, used by both realign and prune (#781)

Upgrade notes

One migration runs on first start: it adds an index to the scan-results table. It adds no data and rewrites nothing.

Recovery takes two scans, not one, and it is not instant. The first scan indexes relocated files; the sweep that re-attaches them runs on a later one. The sweep runs at startup, so in practice recovery lands on the restart after the upgrade rather than during it. Seeing no change immediately after upgrading is expected.

Re-attachment is deliberately conservative: it matches on an exact title within one library, and declines rather than guessing when several files could match. A track it declines stays exactly as it was.

No configuration changes are required.

No detector-sidecar changes in this release.

Install

docker pull ghcr.io/sydlexius/canticle:1.34.0

Full changelog: v1.33.1...v1.34.0

v1.33.1

Choose a tag to compare

@github-actions github-actions released this 17 Aug 00:36
Immutable release. Only release title and notes can be modified.
v1.33.1
0e9afcc

canticle v1.33.1

Highlights

This release fixes two places where the dashboard told you the wrong thing.

The instrumental-detector icon no longer renders enormous. On some page loads the detector glyph appeared roughly ten times its intended size, while the provider mark beside it stayed correct. The difference was that the provider mark carried explicit dimensions and the drawn-in-house glyphs did not, so whenever the stylesheet had not taken effect at the moment the page painted, those icons fell back to a browser default of about 150 pixels. All five in-house icons now carry their own dimensions, which sets a floor regardless of stylesheet timing; the styled result is unchanged.

A track rejected for its language now records why. The language guard has always produced a precise verdict, naming the measurement and the threshold it exceeded, and that verdict went only to a log line -- so a rejected track showed up in the dashboard as settled with nothing written and no explanation. The reason is now stored with the outcome and shown in a new Detail column in Recent outcomes. The same column also explains a result the timing guard refused, which previously reported only as unknown while its reason sat unread in the database.

What's changed

Fixed

  • Rejected tracks record and display the reason they were rejected, instead of settling unexplained (#773)
  • In-house icons render at their intended size even when the stylesheet has not applied yet (#776)
  • The Up-next panel calls a waiting track's delay "retry backoff" rather than "cooldown", which named a different mechanism entirely (#776)

Upgrade notes

One migration runs on first start: it adds a nullable column to the work queue for the outcome reason. It adds no data and rewrites nothing, so it completes immediately at any library size.

Reasons are recorded going forward, not backfilled. Tracks already rejected never stored their reason anywhere recoverable, so their Detail cell stays empty rather than showing an invented explanation. New rejections carry it from the moment you upgrade -- which is the argument for upgrading sooner rather than later, since every rejection in the meantime settles without a recoverable reason.

No configuration changes are required.

No detector-sidecar changes in this release; 1.33.1 is identical in substance to 1.33.0. The image is republished with the new version tag only, so there is no need to pull it.

Install

docker pull ghcr.io/sydlexius/canticle:1.33.1

Full changelog: v1.33.0...v1.33.1

v1.33.0

Choose a tag to compare

@github-actions github-actions released this 16 Aug 22:28
Immutable release. Only release title and notes can be modified.
26ea2b8

canticle v1.33.0

Highlights

This release adds word-level lyric output and finishes the petitlyrics error story, so the fallback lane behaves sensibly when a provider goes quiet.

Lyrics can now be written with per-word timing. When a provider supplies word-level data, canticle can emit Enhanced-LRC (A2) markers so a compatible player highlights each word rather than each line. It is off by default and enabled with output.word_sync: player support for A2 is uneven, and a player that does not understand the markers may show them as literal text, so this stays a deliberate choice rather than something a library inherits silently.

A quiet provider is no longer mistaken for a broken one. petitlyrics answers an unknown track with a normal, empty response, which is indistinguishable from a revoked credential -- so a long run of misses used to be reported as a provider outage. On a fallback lane that run is ordinary, because the primary provider has already served everything common. canticle now confirms a suspected outage by re-fetching a track the provider recently served: if that succeeds, the misses were about the material and the lane keeps working.

A settled track always records what it settled to. A lyric rejected by the language filter completed with nothing written and nothing recorded, so reports could not tell it apart from a track predating the field. Those rows now say so explicitly.

What's changed

Added

  • Enhanced-LRC (A2) word markers on synced output, opt-in via output.word_sync (#480, #759, #760)
  • The encrypted petitlyrics line-sync tier is decoded, so more tracks can settle as synced (#602, #763)
  • petitlyrics has its own request-pacing setting, independent of the primary provider (#535, #752)
  • The dashboard and reports show which provider served each result (#601, #766)
  • Musixmatch attribution renders across the web UI, as its API terms require (#600, #769)

Fixed

  • A suspected provider outage is confirmed by a liveness check before the lane is taken out of service (#767, #771)
  • A revoked petitlyrics application id is detected and reported instead of looking like a library-wide miss (#607, #749)
  • Language-filter rejections record why they settled with nothing written (#655, #770)
  • Synced lyrics whose timestamps all share one value are no longer accepted as genuinely synced (#673, #757)
  • Word markers are treated as markup, not content, when judging whether a cue overruns the audio (#758)
  • Dependency and toolchain updates, including a CVE floor for libssh (#761, #755)

Maintenance

  • A provider error sentinel that is not classified now fails the test suite instead of being silently treated as a transport failure (#748, #753)
  • Measurement harnesses for provider coverage and result trust, gated off by default (#614, #750, #762, #772)
  • A retrospective disk-read report for diagnosing library-disk wake-ups (#756)

Upgrade notes

No migration runs, and no configuration changes are required.

Word-level output is off by default and stays off after upgrading. Set output.word_sync = true to enable it. Before doing so, check that your player renders A2 markers: some strip them, some display them as literal text alongside the lyric. Line-level output is unchanged either way.

Existing settled tracks are not reclassified. The language-filter fix records the reason from this version onward; rows settled by an earlier build stay as they were, since nothing on disk records why they settled.

No detector-sidecar changes in this release.

Install

docker pull ghcr.io/sydlexius/canticle:1.33.0

Full changelog: v1.32.5...v1.33.0

v1.32.5

Choose a tag to compare

@github-actions github-actions released this 11 Aug 03:45
Immutable release. Only release title and notes can be modified.
v1.32.5
910e136

canticle v1.32.5

Highlights

This release removes a requirement that made bootstrapping the web UI cost more than it should have.

Creating your admin account no longer requires handing out a standing bypass. The first-run setup page used to be reachable only from a trusted network, so getting to it meant adding your browser's network to the trusted list -- and anything on that list skips the sign-in check on every page of the dashboard, indefinitely. Bootstrapping therefore meant granting your whole network unauthenticated access to settings, the stored API token, and webhook key creation, and that grant stayed switched on unless you remembered to remove it. The setup page is now open until the first account exists and closes permanently the moment it does, so no trusted-network entry is needed to get started. On an exposed network you can skip the window entirely by creating the account at startup with MXLRC_WEBAUTH_ADMIN_USER and MXLRC_WEBAUTH_ADMIN_PASSWORD.

What's changed

Fixed

  • First-run setup no longer requires a permanent trusted-network bypass to reach (#461)
  • A dropped connection while fetching a build dependency is retried instead of failing the run (#745)

Maintenance

  • The dashboard image in the documentation is generated from a synthetic library, so it shows no real library contents (#746)
  • SQLite driver updated to 1.56.0 (#741)
  • Build workflow actions updated (#742, #743, #744)
  • The shared lint cache is cleaned when a worktree disappears, instead of reporting findings against files that are no longer there (#669)

Upgrade notes

No configuration changes are required, and no migration runs.

Existing installations are unaffected by the setup change. It applies only while no admin account exists; once one does -- which is true of every deployment already running -- the setup page stays closed exactly as before. If you previously added networks to [server.trusted_networks].cidrs (or MXLRC_TRUSTED_CIDRS) solely to complete first-run setup, you can now clear that setting: everyone signs in normally, and clearing it removes the standing bypass. Keep an entry only for something that genuinely cannot sign in, such as a Prometheus scraper reading /metrics.

No detector-sidecar changes in this release; 1.32.5 is identical in substance to 1.32.4. The image is republished with the new version tag only, so there is no need to pull it.

Install

docker pull ghcr.io/sydlexius/canticle:1.32.5

Full changelog: v1.32.4...v1.32.5