e1d544f59f
Adds an embedded-art layer to the album cover chain between the sidecar lookup and the external provider chain. Reads ID3v2 APIC, FLAC METADATA_BLOCK_PICTURE, and MP4 covr atoms via the dhowden/tag library (already a dependency for MBID extraction). Whatever Picard or Lidarr embedded into the file at tag time becomes available without any network call. Diagnostic context: file managers (GNOME Files, Dolphin, etc.) generate audio-file thumbnails by reading exactly this picture block. An album that "has no art" in Minstrel but renders a thumbnail in the file manager is the smoking-gun signal that embedded art is present and we were ignoring it. Adds 'embedded' to the chain so those albums get covered without any external provider hit. Chain after this change: sidecar → embedded → MBCAA → TheAudioDB → Deezer → Last.fm → none Embedded beats every external provider for accuracy because it's the canonical art the operator/Picard chose at tag time, not a guess from a remote catalog. Refactors the post-fetch write logic into Enricher.writeAlbumArt so the embedded layer and the provider chain share the same sidecar-then-managed-cache fallback semantics. Behavior identical to before for the provider path.
46 lines
1.4 KiB
Go
46 lines
1.4 KiB
Go
package coverart
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/dhowden/tag"
|
|
)
|
|
|
|
// ErrNoEmbeddedArt indicates the audio file has no embedded picture.
|
|
// Distinct from "tag read failed" so the enricher can quietly fall
|
|
// through to the provider chain without logging at warn/error level.
|
|
var ErrNoEmbeddedArt = errors.New("coverart: no embedded album art")
|
|
|
|
// ExtractEmbeddedAlbumArt reads the embedded picture from an audio
|
|
// file's tag block: ID3v2 APIC frames (MP3), FLAC
|
|
// METADATA_BLOCK_PICTURE blocks, MP4 covr atoms (M4A/M4B/AAC), and OGG
|
|
// METADATA_BLOCK_PICTURE base64 in vorbis comments. Whichever format
|
|
// dhowden/tag recognises.
|
|
//
|
|
// Returns ErrNoEmbeddedArt when the tag block parses but contains no
|
|
// usable picture; returns a wrapped error for I/O or tag-parse
|
|
// failures so callers can distinguish "no art" from "couldn't read".
|
|
//
|
|
// Picard- and Lidarr-tagged libraries typically carry the canonical
|
|
// art in this block already — extracting it beats every external API
|
|
// guess for accuracy and avoids a network round-trip.
|
|
func ExtractEmbeddedAlbumArt(path string) ([]byte, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open: %w", err)
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
meta, err := tag.ReadFrom(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tag read: %w", err)
|
|
}
|
|
pic := meta.Picture()
|
|
if pic == nil || len(pic.Data) == 0 {
|
|
return nil, ErrNoEmbeddedArt
|
|
}
|
|
return pic.Data, nil
|
|
}
|