refactor(server): unify stream URL builders + MIME tables + cover-path helper
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.
This commit is contained in:
+21
-25
@@ -22,46 +22,42 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
// resolveAlbumCoverPath returns the filesystem path to the album's cover art.
|
||||
// It prefers an explicit cover_art_path (set by the scanner in a future
|
||||
// milestone) and falls back to a sidecar next to the first track in the
|
||||
// album's directory. "" means no art was found.
|
||||
// resolveAlbumCoverPath delegates to coverart.ResolveAlbumPath; kept as a
|
||||
// local alias so the call sites in this file read naturally.
|
||||
func resolveAlbumCoverPath(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 coverart.FindSidecar(filepath.Dir(tracks[0].FilePath))
|
||||
return coverart.ResolveAlbumPath(ctx, q, album)
|
||||
}
|
||||
|
||||
// audioContentType maps the short file_format recorded on tracks (mp3, flac,
|
||||
// ogg, opus, m4a, aac, wav) to a MIME type for the Content-Type header.
|
||||
// Unknown formats fall back to octet-stream so the browser downloads them
|
||||
// rather than attempting to decode.
|
||||
// This is the canonical table; both the browser stream endpoint and the
|
||||
// UPnP cast token URL builder consult it. Unknown formats fall back to
|
||||
// octet-stream so the browser downloads them rather than attempting to
|
||||
// decode -- cast_token.go applies its own audio/mpeg fallback for Sonos.
|
||||
//
|
||||
// Aliases (mpeg/vorbis/wave) cover historical / alternate format spellings
|
||||
// that have shown up in track rows. The trim+lowercase normalization makes
|
||||
// the lookup permissive to whatever a scanner happened to write.
|
||||
//
|
||||
// Divergences from internal/subsonic/types.go's contentTypeForFormat are
|
||||
// intentional: opus→audio/ogg (library .opus files are Ogg-encapsulated, so
|
||||
// this matches real library contents), aac→audio/aac (raw AAC is ADTS, not
|
||||
// MP4, so audio/mp4 would mislead codec sniffers), and there is no "oga" case
|
||||
// (we don't record that format). Don't "fix" these to match subsonic.
|
||||
// intentional: opus/vorbis→audio/ogg (library .opus / .ogg files are
|
||||
// Ogg-encapsulated, so this matches real library contents), aac→audio/aac
|
||||
// (raw AAC is ADTS, not MP4, so audio/mp4 would mislead codec sniffers),
|
||||
// and there is no "oga" case (we don't record that format). Subsonic is a
|
||||
// frozen client contract -- don't "fix" these to match it.
|
||||
func audioContentType(format string) string {
|
||||
switch strings.ToLower(format) {
|
||||
case "mp3":
|
||||
switch strings.ToLower(strings.TrimSpace(format)) {
|
||||
case "mp3", "mpeg":
|
||||
return "audio/mpeg"
|
||||
case "flac":
|
||||
return "audio/flac"
|
||||
case "ogg", "opus":
|
||||
case "ogg", "opus", "vorbis":
|
||||
return "audio/ogg"
|
||||
case "m4a":
|
||||
case "m4a", "mp4":
|
||||
return "audio/mp4"
|
||||
case "aac":
|
||||
return "audio/aac"
|
||||
case "wav":
|
||||
case "wav", "wave":
|
||||
return "audio/wav"
|
||||
}
|
||||
return "application/octet-stream"
|
||||
|
||||
Reference in New Issue
Block a user