Skip to content

Conversation

htkhiem
Copy link
Owner

@htkhiem htkhiem commented Jul 4, 2025

This branch makes Euphonica prioritise embedded covers for all song widgets before falling back to in-folder cover images. Album widgets such as cells in the Album View will prefer folder-level covers, but will fall back to a random song's embedded art if that's not available.

This involved a near-complete rewrite of the metadata cache logic. The cache folder is now simpler, with artist avatars and album covers stored in the same images folder. Image filenames are no longer dependent on tag contents; they're now pseudorandom UUIDs, with the actual tag-to-filename mapping kept in a new table in the existing SQLite metadata DB.

This branch makes Euphonica prioritise embedded art (before falling back
fo in-folder cover images).
@htkhiem htkhiem changed the title Embedded cover art support Cache revamp (again) Jul 4, 2025
@htkhiem htkhiem changed the title Cache revamp (again) v0.95.0: cache revamp (again) & bugfixes Jul 5, 2025
@htkhiem
Copy link
Owner Author

htkhiem commented Jul 5, 2025

An update: to support embedded cover arts efficiently I'm heading this way

image

This should allow albums to fall back to a random track's embedded art, and a track to use embedded art by default, falling back to its folder's cover image if any is found.

By having a new table to keep track of URI -> filename mappings, we'll also solve the filename-too-long problem and simplify the cache somewhat in the process.

@sonicv6
Copy link
Contributor

sonicv6 commented Jul 5, 2025

I would personally look at embedded track art prior to metadata providers, I don't think I have a single item in my library with unique per-track cover art, but almost everything has the album cover embedded in the tracks. Also, you can call MPD albumart with a track URI and it will do the same thing as using a folder, no need to specifically resolve the folder URI.

@htkhiem
Copy link
Owner Author

htkhiem commented Jul 6, 2025

I would personally look at embedded track art prior to metadata providers, I don't think I have a single item in my library with unique per-track cover art, but almost everything has the album cover embedded in the tracks.

Actually same here, but I know of a few albums with one unique cover art per track, such as Coldplay's Music of the Spheres. The above flow prioritises embedded track art only for UI elements that explicitly represent a single track, for example a QueueRow. AlbumCells and the like should still prefer looking for folder-wide art, or we risk displaying a random per-track art in the above Coldplay example.

Also, you can call MPD albumart with a track URI and it will do the same thing as using a folder, no need to specifically resolve the folder URI.

I'm aware of this, but that's not why I've been truncating the filename from the URI before looking for folder art. Rather, it's an optimisation trick to allow us to fetch a common album art at most once for all tracks in that album. Say for example, there are 10 songs in an album, put in the same folder with a cover.png. If we truncate the filename before fetching the cover.png, all 10 songs would have the same "folder URI" and the above cover.png would be fetched just once by one of the songs (specifically, by the first row widget initialised in a song ListView). The other 9 songs will be able to reuse that same cover (provided they don't have their own embedded art) as their folder URIs all point to the same image locally. This both cuts down on network and storage usage.

Some trivia: this is of course not how MPD's albumart command was meant to be used, and in my testing I've found that, at least for v0.23.15, if I were to truncate the filename, I'd have to retain at least a forward slash, else MPD wouldn't return anything.

@sonicv6
Copy link
Contributor

sonicv6 commented Jul 6, 2025

Fair enough, this flow makes sense then. I just have an over the top aversion to making an external request if it can be avoided, but I suppose this avoids the worst-case more often.

@htkhiem
Copy link
Owner Author

htkhiem commented Jul 6, 2025

Fair enough, this flow makes sense then. I just have an over the top aversion to making an external request if it can be avoided, but I suppose this avoids the worst-case more often.

We could optimise some metadata provider calls away by inserting an "empty" mapping into our Sqlite table for cases we have failed to fetch any image at all. This allows us to prevent the code from re-attempting the whole flow every time a song with that album tag comes up, which is doomed to fail anyway. Textual metadata already have this implemented, just not images.

An edge case would be when additional metadata providers become available or the user enable more of them. We should then provide an option to clear these null entries out & let the whole flow run again.

All in all, yet more complexity :(

@sonicv6
Copy link
Contributor

sonicv6 commented Jul 6, 2025

Once you start, it never ends

@htkhiem
Copy link
Owner Author

htkhiem commented Jul 6, 2025

Turns out even the above flow can be inefficient in case an album has both a cover.png and embedded track art. In the Queue View, the song rows will first look for embedded art, which will succeed since there indeed are. Since that succeeded, we'd log them as having embedded art (even though the embedded version is just the exact same as the cover.png for all songs), resulting in them being stored repeatedly on disk and in memory.

I might have to leave it to the user to decide on the priority.

  • If embedded over folder, the above will happen (more network bandwidth just once, then more disk & memory usage afterwards)
  • If folder over embedded, any cover.png will override embedded covers (if any), affecting cases with per-track art (though honestly this is an edge case).

Or maybe we'll just default to cover.png and add a note in the README saying if one wants to use per-track art for a given album then they must remove the cover.png file first.

And re-export the placeholder thumbnail to be higher res (aligns with
current thumbnail res).

BUG: why are SignalListItemFactory::teardown closures complaining that
the item isn't the widget we think it is?
@sonicv6
Copy link
Contributor

sonicv6 commented Jul 6, 2025

You could compare images by hash when caching and if the image already exists just point to it.

@htkhiem
Copy link
Owner Author

htkhiem commented Jul 6, 2025

You could compare images by hash when caching and if the image already exists just point to it.

Looks like I can't avoid hashing then. That won't prevent multiple downloads from MPD but can avoid multiple duplicate textures in memory & disk.

We can either use simple cryptographic hashing (can only prevent perfect matches, so a few different pixels due to compression will throw it off), or perceptual hashing. The latter is more effective but will require comparing an incoming image to the rest of the mapping table (and rule out the use of an index). Maybe we'll just go with the former as most embedded art are the exact same image.

@sonicv6
Copy link
Contributor

sonicv6 commented Jul 7, 2025

If there's a difference in the image, even if its subtle, I'd always want it to be displayed anyway. I agree with avoiding a separate copy of the image being in memory for everything shown on screen, as well as the inflated disk space required for the cache, but the difference between one or two copies of a megabyte image shouldn't really be an issue and I wouldn't bother trying to control for it.

@htkhiem
Copy link
Owner Author

htkhiem commented Jul 7, 2025

If there's a difference in the image, even if its subtle, I'd always want it to be displayed anyway.

I meant several-pixel differences caused by compression, but yeah this is probably overkill.

I think the always-prefer-cover-file approach will work. Should the user want to use a unique embedded cover for each track (compilation albums, fancy editions, etc) then they simply need to ensure there's no cover file in the folder.

htkhiem added 2 commits July 7, 2025 22:00
Finally fixing all the "finalizing queuerow but there are children left" warnings.
Will not deduplicate (since this is probably an error on the library's side).
@sonicv6
Copy link
Contributor

sonicv6 commented Jul 7, 2025

Yeah, that should be fine. it's an edge case anyway.

@htkhiem
Copy link
Owner Author

htkhiem commented Jul 8, 2025

image

Should be the final version. The only change is that track widgets should also look for a folder-level cover first before attempting to call MPD's readpicture. This way we can avoid fetching duplicate "per-track" covers for each song in an album especially if we also have a cover file in the folder.

Note that this check is only among the local files. The full folder fetching logic won't be triggered until after we have failed with readpicture.

@Scupake
Copy link

Scupake commented Jul 8, 2025

I'm not sure if this is the intended behavior. But the cover art seems to override the embedded art of each individual song. For me personally, ideally, you'd have cover.jpg for the album cover, and then each song will have its own embedded art, if it doesn't have its own cover art, then it falls back to cover.jpg.

Currently on this branch, every song uses cover.jpg even if it has its own embedded art.

@sonicv6
Copy link
Contributor

sonicv6 commented Jul 8, 2025

If there's a difference in the image, even if its subtle, I'd always want it to be displayed anyway.

I meant several-pixel differences caused by compression, but yeah this is probably overkill.

I think the always-prefer-cover-file approach will work. Should the user want to use a unique embedded cover for each track (compilation albums, fancy editions, etc) then they simply need to ensure there's no cover file in the folder.

@Scupake

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants