cover-grid album dropdown behavior fixes
This commit is contained in:
+274
-96
@@ -18,42 +18,75 @@ import (
|
||||
"yellowjacket/backend/system"
|
||||
)
|
||||
|
||||
const (
|
||||
// thumbnailMaxSize is the maximum width/height for generated thumbnails.
|
||||
thumbnailMaxSize = 200
|
||||
// thumbnailQuality is the JPEG encoding quality for thumbnails.
|
||||
thumbnailQuality = 80
|
||||
// thumbnailSuffix is appended to the content hash for thumbnail filenames.
|
||||
thumbnailSuffix = "_thumb"
|
||||
)
|
||||
// thumbnailTier defines a single size tier for generated cover art thumbnails.
|
||||
type thumbnailTier struct {
|
||||
// Suffix appended to the content hash (e.g. "_sm", "_md", "_lg").
|
||||
Suffix string
|
||||
// MaxSize is the maximum width or height in pixels.
|
||||
MaxSize int
|
||||
// Quality is the JPEG encoding quality (1-100).
|
||||
Quality int
|
||||
}
|
||||
|
||||
// thumbnailTiers lists all generated size variants, ordered smallest to largest.
|
||||
var thumbnailTiers = []thumbnailTier{
|
||||
{Suffix: "_sm", MaxSize: 100, Quality: 75},
|
||||
{Suffix: "_md", MaxSize: 200, Quality: 80},
|
||||
{Suffix: "_lg", MaxSize: 400, Quality: 85},
|
||||
}
|
||||
|
||||
// legacyThumbSuffix is the old single-thumbnail suffix used before the
|
||||
// multi-tier system. Kept for migration purposes only.
|
||||
const legacyThumbSuffix = "_thumb"
|
||||
|
||||
// isSizedVariant reports whether a filename contains any known size suffix
|
||||
// (current tiers or legacy).
|
||||
func isSizedVariant(name string) bool {
|
||||
if strings.Contains(name, legacyThumbSuffix) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, tier := range thumbnailTiers {
|
||||
if strings.Contains(name, tier.Suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// saveCoverArt saves embedded cover art to the cache directory.
|
||||
// Returns the file path where the art was saved, or empty string if no picture data.
|
||||
func (l *Library) saveCoverArt(pic *metadata.PictureData) (string, error) {
|
||||
func (l *Library) saveCoverArt(
|
||||
pic *metadata.PictureData,
|
||||
) (string, error) {
|
||||
if pic == nil || len(pic.Data) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get the data directory for storing cover art
|
||||
// Get the data directory for storing cover art.
|
||||
dataDir, err := system.GetUserDataDirPath()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not get user data directory: %w", err)
|
||||
return "", fmt.Errorf(
|
||||
"could not get user data directory: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
coverDir := filepath.Join(dataDir, "covers")
|
||||
|
||||
// Ensure directory exists
|
||||
// Ensure directory exists.
|
||||
if err := os.MkdirAll(coverDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("could not create covers directory: %w", err)
|
||||
return "", fmt.Errorf(
|
||||
"could not create covers directory: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
// Generate filename from content hash (deduplication)
|
||||
// Generate filename from content hash (deduplication).
|
||||
hash := sha256.Sum256(pic.Data)
|
||||
hashStr := hex.EncodeToString(hash[:8]) // First 8 bytes = 16 hex chars
|
||||
hashStr := hex.EncodeToString(hash[:8]) // First 8 bytes = 16 hex chars.
|
||||
|
||||
ext := pic.Ext
|
||||
if ext == "" {
|
||||
// Determine extension from MIME type
|
||||
ext = extensionFromMIME(pic.MIMEType)
|
||||
}
|
||||
|
||||
@@ -61,103 +94,157 @@ func (l *Library) saveCoverArt(pic *metadata.PictureData) (string, error) {
|
||||
filePath := filepath.Join(coverDir, filename)
|
||||
|
||||
// Skip if already exists (same content hash).
|
||||
// Missing thumbnails are handled by generateMissingThumbnails() at the end of a scan.
|
||||
// Missing sized variants are handled by
|
||||
// generateMissingSizedVariants() at the end of a scan.
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
l.logger.Debug("cover art already exists", "path", filePath)
|
||||
l.logger.Debug(
|
||||
"cover art already exists", "path", filePath,
|
||||
)
|
||||
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// Write file
|
||||
if err := os.WriteFile(filePath, pic.Data, 0o644); err != nil {
|
||||
return "", fmt.Errorf("could not write cover art: %w", err)
|
||||
// Write file.
|
||||
if err := os.WriteFile(
|
||||
filePath, pic.Data, 0o644,
|
||||
); err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"could not write cover art: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
l.logger.Debug("saved cover art", "path", filePath, "size", len(pic.Data))
|
||||
l.logger.Debug(
|
||||
"saved cover art",
|
||||
"path", filePath, "size", len(pic.Data),
|
||||
)
|
||||
|
||||
// Generate thumbnail alongside the original
|
||||
if err := l.generateThumbnail(pic.Data, coverDir, hashStr); err != nil {
|
||||
l.logger.Warn("could not generate thumbnail", "path", filePath, "err", err)
|
||||
// Generate all sized variants alongside the original.
|
||||
if err := l.generateSizedVariants(
|
||||
pic.Data, coverDir, hashStr,
|
||||
); err != nil {
|
||||
l.logger.Warn(
|
||||
"could not generate sized variants",
|
||||
"path", filePath, "err", err,
|
||||
)
|
||||
}
|
||||
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// generateThumbnail creates a downscaled JPEG thumbnail from cover art image data.
|
||||
// The thumbnail is saved as {hashStr}_thumb.jpg in the given directory.
|
||||
func (l *Library) generateThumbnail(imgData []byte, dir, hashStr string) error {
|
||||
thumbFilename := fmt.Sprintf("%s%s.jpg", hashStr, thumbnailSuffix)
|
||||
thumbPath := filepath.Join(dir, thumbFilename)
|
||||
|
||||
// Decode the source image
|
||||
// generateSizedVariants creates all thumbnail tiers for the given image data.
|
||||
// Each tier is saved as {hashStr}{suffix}.jpg in the given directory.
|
||||
func (l *Library) generateSizedVariants(
|
||||
imgData []byte,
|
||||
dir, hashStr string,
|
||||
) error {
|
||||
src, _, err := image.Decode(bytes.NewReader(imgData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not decode image for thumbnail: %w", err)
|
||||
return fmt.Errorf(
|
||||
"could not decode image for thumbnails: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate thumbnail dimensions preserving aspect ratio
|
||||
bounds := src.Bounds()
|
||||
srcW := bounds.Dx()
|
||||
srcH := bounds.Dy()
|
||||
|
||||
// Skip if image is already smaller than the thumbnail size
|
||||
if srcW <= thumbnailMaxSize && srcH <= thumbnailMaxSize {
|
||||
// Still save a JPEG copy for consistent serving
|
||||
return l.encodeAndSaveThumbnail(src, thumbPath, srcW, srcH)
|
||||
for _, tier := range thumbnailTiers {
|
||||
tierPath := filepath.Join(
|
||||
dir,
|
||||
fmt.Sprintf("%s%s.jpg", hashStr, tier.Suffix),
|
||||
)
|
||||
|
||||
w, h := fitDimensions(srcW, srcH, tier.MaxSize)
|
||||
|
||||
if err := encodeAndSaveImage(
|
||||
src, tierPath, w, h, tier.Quality,
|
||||
); err != nil {
|
||||
l.logger.Warn(
|
||||
"could not generate sized variant",
|
||||
"tier", tier.Suffix,
|
||||
"path", tierPath,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
l.logger.Debug(
|
||||
"saved sized variant",
|
||||
"tier", tier.Suffix,
|
||||
"path", tierPath,
|
||||
"dimensions", fmt.Sprintf("%dx%d", w, h),
|
||||
)
|
||||
}
|
||||
|
||||
// Scale down preserving aspect ratio
|
||||
thumbW, thumbH := thumbnailMaxSize, thumbnailMaxSize
|
||||
if srcW > srcH {
|
||||
thumbH = srcH * thumbnailMaxSize / srcW
|
||||
} else {
|
||||
thumbW = srcW * thumbnailMaxSize / srcH
|
||||
}
|
||||
|
||||
return l.encodeAndSaveThumbnail(src, thumbPath, thumbW, thumbH)
|
||||
}
|
||||
|
||||
// encodeAndSaveThumbnail scales the source image to the given dimensions and saves as JPEG.
|
||||
func (l *Library) encodeAndSaveThumbnail(src image.Image, path string, w, h int) error {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: thumbnailQuality}); err != nil {
|
||||
return fmt.Errorf("could not encode thumbnail: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil {
|
||||
return fmt.Errorf("could not write thumbnail: %w", err)
|
||||
}
|
||||
|
||||
l.logger.Debug(
|
||||
"saved thumbnail",
|
||||
"path",
|
||||
path,
|
||||
"size",
|
||||
buf.Len(),
|
||||
"dimensions",
|
||||
fmt.Sprintf("%dx%d", w, h),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateMissingThumbnails scans the covers directory and generates thumbnails
|
||||
// for any original cover art files that do not yet have a corresponding _thumb.jpg.
|
||||
func (l *Library) generateMissingThumbnails() error {
|
||||
// fitDimensions calculates the output dimensions that fit within maxSize
|
||||
// while preserving the aspect ratio. If the source is already smaller
|
||||
// than maxSize, the original dimensions are returned unchanged.
|
||||
func fitDimensions(srcW, srcH, maxSize int) (int, int) {
|
||||
if srcW <= maxSize && srcH <= maxSize {
|
||||
return srcW, srcH
|
||||
}
|
||||
|
||||
w, h := maxSize, maxSize
|
||||
if srcW > srcH {
|
||||
h = srcH * maxSize / srcW
|
||||
} else {
|
||||
w = srcW * maxSize / srcH
|
||||
}
|
||||
|
||||
return w, h
|
||||
}
|
||||
|
||||
// encodeAndSaveImage scales the source image to the given dimensions
|
||||
// and saves it as a JPEG with the specified quality.
|
||||
func encodeAndSaveImage(
|
||||
src image.Image,
|
||||
path string,
|
||||
w, h, quality int,
|
||||
) error {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
draw.CatmullRom.Scale(
|
||||
dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil,
|
||||
)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := jpeg.Encode(
|
||||
&buf, dst, &jpeg.Options{Quality: quality},
|
||||
); err != nil {
|
||||
return fmt.Errorf("could not encode image: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(
|
||||
path, buf.Bytes(), 0o644,
|
||||
); err != nil {
|
||||
return fmt.Errorf("could not write image: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateMissingSizedVariants scans the covers directory, migrates legacy
|
||||
// _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()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get user data directory: %w", err)
|
||||
return fmt.Errorf(
|
||||
"could not get user data directory: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
coverDir := filepath.Join(dataDir, "covers")
|
||||
|
||||
entries, err := os.ReadDir(coverDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read covers directory: %w", err)
|
||||
return fmt.Errorf(
|
||||
"could not read covers directory: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
// Build a set of existing filenames for quick lookup.
|
||||
@@ -169,38 +256,64 @@ func (l *Library) generateMissingThumbnails() error {
|
||||
}
|
||||
}
|
||||
|
||||
// First pass: migrate legacy _thumb files to _md.
|
||||
migrated := l.migrateLegacyThumbs(
|
||||
coverDir, existing,
|
||||
)
|
||||
|
||||
// Second pass: generate missing sized variants.
|
||||
var generated, skipped int
|
||||
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
|
||||
// Skip directories and thumbnails themselves.
|
||||
if entry.IsDir() || strings.Contains(name, thumbnailSuffix) {
|
||||
// Skip directories and any sized variants.
|
||||
if entry.IsDir() || isSizedVariant(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
thumbName := ThumbnailFilename(name)
|
||||
if _, exists := existing[thumbName]; exists {
|
||||
hashStr := strings.SplitN(name, ".", 2)[0]
|
||||
|
||||
// Check which tiers are missing.
|
||||
allPresent := true
|
||||
|
||||
for _, tier := range thumbnailTiers {
|
||||
tierName := fmt.Sprintf(
|
||||
"%s%s.jpg", hashStr, tier.Suffix,
|
||||
)
|
||||
if _, exists := existing[tierName]; !exists {
|
||||
allPresent = false
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allPresent {
|
||||
skipped++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract hash from filename (everything before the first dot).
|
||||
hashStr := strings.SplitN(name, ".", 2)[0]
|
||||
|
||||
imgData, err := os.ReadFile(filepath.Join(coverDir, name))
|
||||
// Read the original and generate missing tiers.
|
||||
imgData, err := os.ReadFile(
|
||||
filepath.Join(coverDir, name),
|
||||
)
|
||||
if err != nil {
|
||||
l.logger.Warn(
|
||||
"could not read cover art for thumbnail generation",
|
||||
"could not read cover art for variant generation",
|
||||
"file", name, "err", err,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := l.generateThumbnail(imgData, coverDir, hashStr); err != nil {
|
||||
l.logger.Warn("could not generate thumbnail", "file", name, "err", err)
|
||||
if err := l.generateSizedVariants(
|
||||
imgData, coverDir, hashStr,
|
||||
); err != nil {
|
||||
l.logger.Warn(
|
||||
"could not generate sized variants",
|
||||
"file", name, "err", err,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -208,18 +321,83 @@ func (l *Library) generateMissingThumbnails() error {
|
||||
generated++
|
||||
}
|
||||
|
||||
l.logger.Info("thumbnail generation complete", "generated", generated, "skipped", skipped)
|
||||
l.logger.Info(
|
||||
"sized variant generation complete",
|
||||
"generated", generated,
|
||||
"skipped", skipped,
|
||||
"migrated", migrated,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ThumbnailFilename derives the thumbnail filename from an original cover art filename.
|
||||
// For example, "a1b2c3d4.jpg" becomes "a1b2c3d4_thumb.jpg".
|
||||
func ThumbnailFilename(originalFilename string) string {
|
||||
// migrateLegacyThumbs renames _thumb.jpg files to _md.jpg.
|
||||
// Returns the number of files migrated.
|
||||
func (l *Library) migrateLegacyThumbs(
|
||||
coverDir string,
|
||||
existing map[string]struct{},
|
||||
) int {
|
||||
var migrated int
|
||||
|
||||
for name := range existing {
|
||||
if !strings.Contains(name, legacyThumbSuffix) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Derive the _md name from the legacy name.
|
||||
mdName := strings.Replace(
|
||||
name, legacyThumbSuffix, "_md", 1,
|
||||
)
|
||||
|
||||
oldPath := filepath.Join(coverDir, name)
|
||||
newPath := filepath.Join(coverDir, mdName)
|
||||
|
||||
// Only rename if _md doesn't already exist.
|
||||
if _, exists := existing[mdName]; exists {
|
||||
// Both exist; remove the legacy file.
|
||||
if err := os.Remove(oldPath); err != nil {
|
||||
l.logger.Warn(
|
||||
"could not remove legacy thumbnail",
|
||||
"file", name, "err", err,
|
||||
)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
l.logger.Warn(
|
||||
"could not migrate legacy thumbnail",
|
||||
"from", name, "to", mdName, "err", err,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Update the existing set so subsequent lookups
|
||||
// see the new name.
|
||||
delete(existing, name)
|
||||
existing[mdName] = struct{}{}
|
||||
|
||||
migrated++
|
||||
|
||||
l.logger.Debug(
|
||||
"migrated legacy thumbnail",
|
||||
"from", name, "to", mdName,
|
||||
)
|
||||
}
|
||||
|
||||
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 + thumbnailSuffix + ".jpg"
|
||||
return name + suffix + ".jpg"
|
||||
}
|
||||
|
||||
// extensionFromMIME returns a file extension for common image MIME types.
|
||||
@@ -236,6 +414,6 @@ func extensionFromMIME(mimeType string) string {
|
||||
case "image/bmp":
|
||||
return "bmp"
|
||||
default:
|
||||
return "jpg" // Default to jpg
|
||||
return "jpg" // Default to jpg.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,9 +321,13 @@ func (l *Library) Scan() error {
|
||||
return true
|
||||
})
|
||||
|
||||
// Generate thumbnails for any cover art that doesn't have one yet.
|
||||
if err := l.generateMissingThumbnails(); err != nil {
|
||||
l.logger.Warn("could not generate missing thumbnails", "err", err)
|
||||
// Generate sized variants for any cover art missing them,
|
||||
// and migrate legacy _thumb files.
|
||||
if err := l.generateMissingSizedVariants(); err != nil {
|
||||
l.logger.Warn(
|
||||
"could not generate missing sized variants",
|
||||
"err", err,
|
||||
)
|
||||
}
|
||||
|
||||
l.logger.Info(
|
||||
|
||||
@@ -25,12 +25,14 @@ type Track struct {
|
||||
|
||||
// Album represents an album for the cover grid display.
|
||||
type Album struct {
|
||||
ID int64
|
||||
Name string
|
||||
ArtistName string
|
||||
CoverArtPath string
|
||||
CoverArtThumbnailPath string
|
||||
Year int64
|
||||
ID int64
|
||||
Name string
|
||||
ArtistName string
|
||||
CoverArtPath string
|
||||
CoverArtSmall string
|
||||
CoverArtMedium string
|
||||
CoverArtLarge string
|
||||
Year int64
|
||||
}
|
||||
|
||||
// GetAllTracks returns an array of track structs of every file in the library.
|
||||
@@ -123,11 +125,16 @@ func (l *Library) GetAllAlbums() ([]Album, error) {
|
||||
album.Year = row.Year.Int64
|
||||
}
|
||||
|
||||
// Convert filesystem path to URL path for the asset handler
|
||||
// Convert filesystem path to URL path for the asset handler.
|
||||
if row.CoverArtPath != "" {
|
||||
base := filepath.Base(row.CoverArtPath)
|
||||
album.CoverArtPath = "/covers/" + base
|
||||
album.CoverArtThumbnailPath = "/covers/" + ThumbnailFilename(base)
|
||||
album.CoverArtSmall = "/covers/" +
|
||||
SizedFilename(base, "_sm")
|
||||
album.CoverArtMedium = "/covers/" +
|
||||
SizedFilename(base, "_md")
|
||||
album.CoverArtLarge = "/covers/" +
|
||||
SizedFilename(base, "_lg")
|
||||
}
|
||||
|
||||
albums = append(albums, album)
|
||||
|
||||
+21
-15
@@ -20,6 +20,7 @@ import (
|
||||
"yellowjacket/backend/database"
|
||||
"yellowjacket/backend/database/sql/sqlcgen"
|
||||
"yellowjacket/backend/events"
|
||||
"yellowjacket/backend/library"
|
||||
"yellowjacket/backend/metadata"
|
||||
)
|
||||
|
||||
@@ -598,12 +599,14 @@ func (p *Player) GetCurrentTrackInfo() (map[string]interface{}, error) {
|
||||
fileName := filepath.Base(p.currentFile.Name())
|
||||
filePath := p.currentFile.Name()
|
||||
|
||||
// Default values
|
||||
// Default values.
|
||||
title := fileName
|
||||
artist := ""
|
||||
album := ""
|
||||
coverArt := ""
|
||||
coverArtThumbnail := ""
|
||||
coverArtSmall := ""
|
||||
coverArtMedium := ""
|
||||
coverArtLarge := ""
|
||||
|
||||
// Try to get metadata from database
|
||||
if p.db != nil {
|
||||
@@ -619,11 +622,12 @@ func (p *Player) GetCurrentTrackInfo() (map[string]interface{}, error) {
|
||||
if meta.CoverArtPath != "" {
|
||||
base := filepath.Base(meta.CoverArtPath)
|
||||
coverArt = "/covers/" + base
|
||||
|
||||
// Derive thumbnail filename from original: hash.ext -> hash_thumb.jpg
|
||||
ext := filepath.Ext(base)
|
||||
name := base[:len(base)-len(ext)]
|
||||
coverArtThumbnail = "/covers/" + name + "_thumb.jpg"
|
||||
coverArtSmall = "/covers/" +
|
||||
library.SizedFilename(base, "_sm")
|
||||
coverArtMedium = "/covers/" +
|
||||
library.SizedFilename(base, "_md")
|
||||
coverArtLarge = "/covers/" +
|
||||
library.SizedFilename(base, "_lg")
|
||||
}
|
||||
} else {
|
||||
p.logger.Debug(
|
||||
@@ -634,14 +638,16 @@ func (p *Player) GetCurrentTrackInfo() (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"fileName": fileName,
|
||||
"filePath": filePath,
|
||||
"state": string(p.state),
|
||||
"title": title,
|
||||
"artist": artist,
|
||||
"album": album,
|
||||
"coverArt": coverArt,
|
||||
"coverArtThumbnail": coverArtThumbnail,
|
||||
"fileName": fileName,
|
||||
"filePath": filePath,
|
||||
"state": string(p.state),
|
||||
"title": title,
|
||||
"artist": artist,
|
||||
"album": album,
|
||||
"coverArt": coverArt,
|
||||
"coverArtSmall": coverArtSmall,
|
||||
"coverArtMedium": coverArtMedium,
|
||||
"coverArtLarge": coverArtLarge,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -29,15 +29,17 @@ type Summary struct {
|
||||
|
||||
// Track represents a track within a playlist, including its metadata.
|
||||
type Track struct {
|
||||
ID int64 `json:"ID"`
|
||||
Position int64 `json:"Position"`
|
||||
FilePath string `json:"FilePath"`
|
||||
Title string `json:"Title"`
|
||||
Artist string `json:"Artist"`
|
||||
Album string `json:"Album"`
|
||||
CoverArtPath string `json:"CoverArtPath"`
|
||||
CoverArtThumbnailPath string `json:"CoverArtThumbnailPath"`
|
||||
Duration string `json:"Duration"`
|
||||
ID int64 `json:"ID"`
|
||||
Position int64 `json:"Position"`
|
||||
FilePath string `json:"FilePath"`
|
||||
Title string `json:"Title"`
|
||||
Artist string `json:"Artist"`
|
||||
Album string `json:"Album"`
|
||||
CoverArtPath string `json:"CoverArtPath"`
|
||||
CoverArtSmall string `json:"CoverArtSmall"`
|
||||
CoverArtMedium string `json:"CoverArtMedium"`
|
||||
CoverArtLarge string `json:"CoverArtLarge"`
|
||||
Duration string `json:"Duration"`
|
||||
}
|
||||
|
||||
// WithTracks contains a playlist summary and all its tracks.
|
||||
@@ -213,8 +215,12 @@ func trackFromRow(
|
||||
if coverArtPath != "" {
|
||||
base := filepath.Base(coverArtPath)
|
||||
track.CoverArtPath = "/covers/" + base
|
||||
track.CoverArtThumbnailPath = "/covers/" +
|
||||
library.ThumbnailFilename(base)
|
||||
track.CoverArtSmall = "/covers/" +
|
||||
library.SizedFilename(base, "_sm")
|
||||
track.CoverArtMedium = "/covers/" +
|
||||
library.SizedFilename(base, "_md")
|
||||
track.CoverArtLarge = "/covers/" +
|
||||
library.SizedFilename(base, "_lg")
|
||||
}
|
||||
|
||||
return track
|
||||
|
||||
Reference in New Issue
Block a user