024493f2a7
Closes Scribe #614, #615, server half of #616 surfaced by the 2026-06-04 divergent-provider audit. - streamURL helper now used everywhere /api/tracks/{id}/stream is built (was inline concat in playlists.go and cast_token.go); add streamURLWithExt for the .ext cast variant. - audioContentType in media.go is the canonical file_format -> MIME lookup; mimeForFormat in cast_token.go is now a thin wrapper that overrides the unknown-format fallback to audio/mpeg (Sonos rejects octet-stream). Adds mpeg/vorbis/wave aliases. Subsonic's contentTypeForFormat stays frozen per docs. - coverart.ResolveAlbumPath extracted; api and subsonic both delegate to it.
60 lines
2.1 KiB
Go
60 lines
2.1 KiB
Go
// Package coverart finds and fetches album cover artwork.
|
|
//
|
|
// Two-layer model: a directory-walking sidecar lookup (cover.jpg / folder.jpg
|
|
// next to the audio files) and a MusicBrainz Cover Art Archive (MBCAA)
|
|
// fallback for albums with an MBID but no sidecar. The Enricher orchestrates
|
|
// the two; the API and Subsonic packages reuse the sidecar helper directly.
|
|
package coverart
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
)
|
|
|
|
// SidecarNames is the lookup order for cover art living next to audio files.
|
|
// Matches common library conventions: cover.* wins over folder.*; JPEG wins
|
|
// over PNG when both are present.
|
|
var SidecarNames = []string{
|
|
"cover.jpg", "cover.jpeg", "cover.png",
|
|
"folder.jpg", "folder.jpeg", "folder.png",
|
|
}
|
|
|
|
// FindSidecar returns the absolute path to a sidecar cover image in
|
|
// albumDir, or "" when none of the conventional names are present as a
|
|
// regular file. Directories named like sidecar files are skipped.
|
|
func FindSidecar(albumDir string) string {
|
|
for _, name := range SidecarNames {
|
|
candidate := filepath.Join(albumDir, name)
|
|
info, err := os.Stat(candidate)
|
|
if err != nil || info.IsDir() {
|
|
continue
|
|
}
|
|
return candidate
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ResolveAlbumPath returns the on-disk path to an album's cover image,
|
|
// preferring the explicit album.cover_art_path when set and the file
|
|
// exists, falling back to a sidecar (cover.jpg / folder.jpg) next to the
|
|
// first track in the album's directory. "" means no art was found.
|
|
//
|
|
// Shared by internal/api/media.go (browser endpoint) and
|
|
// internal/subsonic/stream.go (Subsonic endpoint); they were byte-identical
|
|
// duplicates before extraction.
|
|
func ResolveAlbumPath(ctx context.Context, q *dbq.Queries, album dbq.Album) string {
|
|
if album.CoverArtPath != nil && *album.CoverArtPath != "" {
|
|
if _, err := os.Stat(*album.CoverArtPath); err == nil {
|
|
return *album.CoverArtPath
|
|
}
|
|
}
|
|
tracks, err := q.ListTracksByAlbum(ctx, dbq.ListTracksByAlbumParams{AlbumID: album.ID})
|
|
if err != nil || len(tracks) == 0 {
|
|
return ""
|
|
}
|
|
return FindSidecar(filepath.Dir(tracks[0].FilePath))
|
|
}
|