fix(local): encode the disc cover url, and guard the first frame

Two defects in the multi-disc divider, both found by opening the owner's
own box set rather than by reading the diff.

1. THE '#' ATE THE PATH. The url was built by concatenating "file://"
   with the absolute path. This box's folders are named
   "Disc 07 - TV Series Soundtrack #05", and in a URL '#' opens a
   FRAGMENT — so the path truncated at "Soundtrack " and every disc
   logged "QML Image: Cannot open". The tree already had the right tool:
   `artwork_qt::file_url` escapes '#', '?' and '%' for exactly this, and
   the url is now built there instead of by hand in QML.

2. `root.doc` IS NULL ON THE FIRST FRAME, so `root.doc.discs` threw and
   left `discRows` undefined, which then threw again on every read of
   `.length` — 26 cascaded TypeErrors per open.

Verified on the real library after the fix: the dividers read "Disc 1 —
TV Series Soundtrack #01" and "Disc 2 — TV Series Soundtrack #02", each
with its OWN cover (mean pixel difference 92.9 between the two
thumbnails, i.e. genuinely different images), and the page opens with
zero QML complaints where it logged 38 before.

cc
This commit is contained in:
vicrodh committed 2026-08-22 05:41:33 -06:00
1 parent e5e19b9aa2
commit 9208513c51
2 files changed
+16 -8

No files matched your search

+5 -6
View File
@@ -247,7 +247,7 @@ Rectangle {
}
/// The disc rows the document publishes for a MULTI-disc album
/// (local_album_actions.rs::disc_rows). Empty on a single-disc album.
readonly property var discRows: root.doc.discs || []
readonly property var discRows: (root.doc && root.doc.discs) || []
function discInfo(n) {
for (var i = 0; i < root.discRows.length; i++)
if ((root.discRows[i].disc || 0) === n)
@@ -629,13 +629,12 @@ Rectangle {
radius: theme.radiusSm
anchors.verticalCenter: parent.verticalCenter
visible: root.discArtDistinct && source !== ""
// Already a percent-encoded file:// url —
// artwork_qt::file_url built it, because these
// folder names contain '#'.
source: {
var d = root.discInfo(root.discHeader(trackBlock.index))
var c = d ? (d.cover || "") : ""
// A bare absolute path — the local art
// channel hands back file:// urls, this one
// comes straight off the filesystem.
return c === "" ? "" : (c.indexOf("file:") === 0 ? c : "file://" + c)
return d ? (d.cover || "") : ""
}
}
Text {
+11 -2
View File
@@ -100,12 +100,21 @@ pub struct DiscRow {
pub cover: String,
}
/// This disc's own folder cover, ignoring the album root's.
/// This disc's own folder cover as a `file://` URL, ignoring the album root's.
///
/// Encoded through `artwork_qt::file_url`, which is not optional: this box's
/// disc folders are named "Disc 07 - TV Series Soundtrack #05", and a raw
/// concatenation gives QML `file:///…Soundtrack #05/cover.jpg`, where the `#`
/// opens a URL FRAGMENT and the path silently truncates at "Soundtrack ".
/// Caught on the owner's own library, 2026-08-22 — every disc logged
/// "QML Image: Cannot open".
fn disc_cover(t: &LocalTrack) -> String {
let Some(dir) = std::path::Path::new(&t.file_path).parent() else {
return String::new();
};
MetadataExtractor::folder_artwork_in_dir(dir).unwrap_or_default()
MetadataExtractor::folder_artwork_in_dir(dir)
.map(|p| crate::artwork_qt::file_url(&p))
.unwrap_or_default()
}
/// `{album:{…}, tracks:[…], discs:[…]}` — the `localAlbumJson` document.