extracted cover art handling from library package

This commit is contained in:
2026-02-25 15:31:05 -05:00
parent 5689c1be42
commit baa4644ece
11 changed files with 320 additions and 114 deletions
+6 -20
View File
@@ -15,8 +15,8 @@ import (
"golang.org/x/image/draw"
"yellowjacket/backend/coverart"
"yellowjacket/backend/metadata"
"yellowjacket/backend/system"
)
// thumbnailTier defines a single size tier for generated cover art thumbnails.
@@ -80,16 +80,14 @@ func (l *Library) saveCoverArt(
saveStart := time.Now()
// Get the data directory for storing cover art.
dataDir, err := system.GetUserDataDirPath()
// Get the covers directory for storing cover art.
coverDir, err := coverart.CoversDir()
if err != nil {
return "", fmt.Errorf(
"could not get user data directory: %w", err,
"could not resolve covers directory: %w", err,
)
}
coverDir := filepath.Join(dataDir, "covers")
// Ensure directory exists.
if err := os.MkdirAll(coverDir, 0o755); err != nil {
return "", fmt.Errorf(
@@ -324,15 +322,13 @@ func encodeAndSaveImage(
// _thumb files to _md, and generates any missing sized variants for each
// original cover art file.
func (l *Library) generateMissingSizedVariants() error {
dataDir, err := system.GetUserDataDirPath()
coverDir, err := coverart.CoversDir()
if err != nil {
return fmt.Errorf(
"could not get user data directory: %w", err,
"could not resolve covers directory: %w", err,
)
}
coverDir := filepath.Join(dataDir, "covers")
entries, err := os.ReadDir(coverDir)
if err != nil {
return fmt.Errorf(
@@ -483,16 +479,6 @@ func (l *Library) migrateLegacyThumbs(
return migrated
}
// SizedFilename derives a sized-variant filename from an original cover art
// filename and a size suffix.
// For example, SizedFilename("a1b2c3d4.jpg", "_sm") returns "a1b2c3d4_sm.jpg".
func SizedFilename(originalFilename, suffix string) string {
ext := filepath.Ext(originalFilename)
name := strings.TrimSuffix(originalFilename, ext)
return name + suffix + ".jpg"
}
// extensionFromMIME returns a file extension for common image MIME types.
func extensionFromMIME(mimeType string) string {
switch mimeType {
-46
View File
@@ -1,46 +0,0 @@
package library
import (
"fmt"
"net/http"
"path/filepath"
"yellowjacket/backend/system"
)
// CoverArtHandler serves cover art images via HTTP.
type CoverArtHandler struct {
coversDir string
}
// NewCoverArtHandler creates a handler that serves cover art from the user data directory.
func NewCoverArtHandler() (*CoverArtHandler, error) {
dataDir, err := system.GetUserDataDirPath()
if err != nil {
return nil, fmt.Errorf("could not get user data directory: %w", err)
}
return &CoverArtHandler{
coversDir: filepath.Join(dataDir, "covers"),
}, nil
}
// ServeHTTP handles requests for cover art images.
func (h *CoverArtHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Extract filename from path like "/covers/abc123.jpg"
filename := filepath.Base(r.URL.Path)
// Prevent directory traversal
if filename == "." || filename == ".." {
http.NotFound(w, r)
return
}
// Filenames are content-hashed (SHA-256), so they are immutable.
// Set aggressive cache headers to avoid redundant re-fetches.
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
filePath := filepath.Join(h.coversDir, filename)
http.ServeFile(w, r, filePath)
}
+12 -17
View File
@@ -4,9 +4,10 @@ import (
"database/sql"
"errors"
"fmt"
"path/filepath"
"strconv"
"strings"
"yellowjacket/backend/coverart"
)
// Sentinel errors for library queries.
@@ -258,14 +259,11 @@ func (l *Library) GetAllAlbums() ([]Album, error) {
// Convert filesystem path to URL path for the asset handler.
if row.CoverArtPath != "" {
base := filepath.Base(row.CoverArtPath)
album.CoverArtPath = "/covers/" + base
album.CoverArtSmall = "/covers/" +
SizedFilename(base, "_sm")
album.CoverArtMedium = "/covers/" +
SizedFilename(base, "_md")
album.CoverArtLarge = "/covers/" +
SizedFilename(base, "_lg")
urls := coverart.ResolveURLs(row.CoverArtPath)
album.CoverArtPath = urls.Original
album.CoverArtSmall = urls.Small
album.CoverArtMedium = urls.Medium
album.CoverArtLarge = urls.Large
}
albums = append(albums, album)
@@ -345,14 +343,11 @@ func (l *Library) GetAlbumsByArtist(
// Convert filesystem path to URL path for the asset handler.
if row.CoverArtPath != "" {
base := filepath.Base(row.CoverArtPath)
album.CoverArtPath = "/covers/" + base
album.CoverArtSmall = "/covers/" +
SizedFilename(base, "_sm")
album.CoverArtMedium = "/covers/" +
SizedFilename(base, "_md")
album.CoverArtLarge = "/covers/" +
SizedFilename(base, "_lg")
urls := coverart.ResolveURLs(row.CoverArtPath)
album.CoverArtPath = urls.Original
album.CoverArtSmall = urls.Small
album.CoverArtMedium = urls.Medium
album.CoverArtLarge = urls.Large
}
albums = append(albums, album)
+3 -5
View File
@@ -6,7 +6,7 @@ import (
"path/filepath"
"time"
"yellowjacket/backend/system"
"yellowjacket/backend/coverart"
)
// FullRescan clears the queue and player, wipes all library data
@@ -182,15 +182,13 @@ func (l *Library) clearLibraryTables() error {
// clearCoverArtFiles removes all files from the covers directory.
func (l *Library) clearCoverArtFiles() error {
dataDir, err := system.GetUserDataDirPath()
coverDir, err := coverart.CoversDir()
if err != nil {
return fmt.Errorf(
"could not get user data directory: %w", err,
"could not resolve covers directory: %w", err,
)
}
coverDir := filepath.Join(dataDir, "covers")
entries, err := os.ReadDir(coverDir)
if err != nil {
if os.IsNotExist(err) {