feat: extract embedded album artwork during scans
Add a dedicated artwork cache directory and save embedded album art from audio tags during library scans. Prefer embedded artwork for album cover resolution while keeping sidecar image files as the fallback path.
This commit is contained in:
@@ -2,6 +2,7 @@ APP_ENV=development
|
||||
SERVER_HOST=0.0.0.0
|
||||
SERVER_PORT=4040
|
||||
DATABASE_PATH=./data/app.db
|
||||
ARTWORK_CACHE_DIR=./data/artwork
|
||||
MEDIA_ROOT=./media
|
||||
CORS_ORIGINS=http://localhost:5173
|
||||
DEFAULT_ADMIN_USERNAME=demo
|
||||
|
||||
@@ -33,7 +33,7 @@ func main() {
|
||||
log.Fatalf("database seed failed: %v", err)
|
||||
}
|
||||
|
||||
scanService := scanner.NewService(database, cfg.MediaRoot)
|
||||
scanService := scanner.NewService(database, cfg.MediaRoot, cfg.ArtworkCacheDir)
|
||||
if scanService.HasMediaFiles() {
|
||||
if result, err := scanService.Scan(ctx); err != nil {
|
||||
log.Printf("startup scan failed: %v", err)
|
||||
|
||||
@@ -7,6 +7,7 @@ type Config struct {
|
||||
ServerHost string
|
||||
ServerPort string
|
||||
DatabasePath string
|
||||
ArtworkCacheDir string
|
||||
MediaRoot string
|
||||
CORSOrigins string
|
||||
DefaultAdminUsername string
|
||||
@@ -19,6 +20,7 @@ func Load() Config {
|
||||
ServerHost: getenv("SERVER_HOST", "0.0.0.0"),
|
||||
ServerPort: getenv("SERVER_PORT", "4040"),
|
||||
DatabasePath: getenv("DATABASE_PATH", "./data/app.db"),
|
||||
ArtworkCacheDir: getenv("ARTWORK_CACHE_DIR", "./data/artwork"),
|
||||
MediaRoot: getenv("MEDIA_ROOT", "./media"),
|
||||
CORSOrigins: getenv("CORS_ORIGINS", "http://localhost:5173"),
|
||||
DefaultAdminUsername: getenv("DEFAULT_ADMIN_USERNAME", "demo"),
|
||||
|
||||
@@ -38,6 +38,7 @@ type Result struct {
|
||||
type Service struct {
|
||||
db *sql.DB
|
||||
mediaRoot string
|
||||
artworkRoot string
|
||||
mu sync.RWMutex
|
||||
status Status
|
||||
}
|
||||
@@ -78,8 +79,8 @@ type scannedTrack struct {
|
||||
ContentType string
|
||||
}
|
||||
|
||||
func NewService(db *sql.DB, mediaRoot string) *Service {
|
||||
return &Service{db: db, mediaRoot: mediaRoot}
|
||||
func NewService(db *sql.DB, mediaRoot, artworkRoot string) *Service {
|
||||
return &Service{db: db, mediaRoot: mediaRoot, artworkRoot: artworkRoot}
|
||||
}
|
||||
|
||||
func (s *Service) HasMediaFiles() bool {
|
||||
@@ -327,7 +328,13 @@ func (s *Service) scanFile(path string) (scannedItem, error) {
|
||||
artistID := hashID("artist", artistName)
|
||||
albumID := hashID("album", artistName, albumTitle, fmt.Sprintf("%d", year))
|
||||
trackID := hashID("track", path)
|
||||
coverArt := findCoverArt(filepath.Dir(path))
|
||||
coverArt := ""
|
||||
if err == nil {
|
||||
coverArt = s.extractEmbeddedCoverArt(metadata, albumID)
|
||||
}
|
||||
if coverArt == "" {
|
||||
coverArt = findCoverArt(filepath.Dir(path))
|
||||
}
|
||||
|
||||
return scannedItem{
|
||||
artist: scannedArtist{
|
||||
@@ -417,6 +424,44 @@ func findCoverArt(dir string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Service) extractEmbeddedCoverArt(metadata tag.Metadata, albumID string) string {
|
||||
if metadata == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
picture := metadata.Picture()
|
||||
if picture == nil || len(picture.Data) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if s.artworkRoot == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(s.artworkRoot, 0o755); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
filename := albumID + coverExtension(picture.MIMEType)
|
||||
path := filepath.Join(s.artworkRoot, filename)
|
||||
if err := os.WriteFile(path, picture.Data, 0o644); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func coverExtension(mimeType string) string {
|
||||
switch strings.ToLower(mimeType) {
|
||||
case "image/png":
|
||||
return ".png"
|
||||
case "image/webp":
|
||||
return ".webp"
|
||||
default:
|
||||
return ".jpg"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) tryMarkStarted() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user