Search Deezer and MusicBrainz together; add preview clips
Search
- Albums and songs search Deezer and MusicBrainz at once and merge the
results: duplicates keep the Deezer copy, best word matches first.
Deezer's search misses albums it carries (Death Magnetic) and most game
soundtracks; MusicBrainz finds them
- MusicBrainz secondary types are shown ("Soundtrack", "Live")
- Settings: Deezer + MusicBrainz (default), Deezer only, MusicBrainz only
- Search results show which service they came from
Previews
- Songs and albums get a play button (cards, rows, detail view, editor,
and Deezer search results) for a 30-second Deezer clip; albums play
their most popular track
- Clips are looked up fresh (Deezer URLs expire), cached 10 minutes, and
streamed through Canon with Range support; hidden lists stay private
- Entries from MusicBrainz or added by hand are matched to Deezer on save
so they can preview too
- "Play previews" toggle in Settings → Profile
Tests cover merging and title/artist matching; `-tags live` runs checks
against the real services.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6d92b46130
commit
b09533cc00
20 files changed
+736
-19
No files matched your search
@@ -20,7 +20,8 @@ type ItemView struct {
|
||||
Source string `json:"source"`
|
||||
Description string `json:"description"`
|
||||
Note string `json:"note"`
|
||||
Badge string `json:"badge"` // e.g. theater production
|
||||
Badge string `json:"badge"` // e.g. theater production
|
||||
Preview bool `json:"preview"` // has a playable clip at /preview/{id}
|
||||
Meta []MetaKV `json:"meta"`
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ var extraFields = []struct{ Key, Label string }{
|
||||
{"label", "Label"},
|
||||
}
|
||||
|
||||
func itemView(it store.Item, showNotes bool) ItemView {
|
||||
func itemView(it store.Item, showNotes, previews bool) ItemView {
|
||||
v := ItemView{
|
||||
ID: it.ID,
|
||||
Rank: it.Rank,
|
||||
@@ -71,6 +72,7 @@ func itemView(it store.Item, showNotes bool) ItemView {
|
||||
if showNotes {
|
||||
v.Note = it.Note
|
||||
}
|
||||
v.Preview = previews && deezerRef(it) != ""
|
||||
for _, f := range extraFields {
|
||||
if val := it.Extra[f.Key]; val != "" {
|
||||
v.Meta = append(v.Meta, MetaKV{f.Label, val})
|
||||
@@ -122,7 +124,7 @@ func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) {
|
||||
sec := listSection{List: l, More: l.ItemCount - len(items)}
|
||||
sec.Category = listCategory(l)
|
||||
for _, it := range items {
|
||||
sec.Items = append(sec.Items, itemView(it, site.ShowNotes))
|
||||
sec.Items = append(sec.Items, itemView(it, site.ShowNotes, site.ShowPreviews && hasPreviews(l.Category)))
|
||||
}
|
||||
page.Sections = append(page.Sections, sec)
|
||||
page.TotalItems += l.ItemCount
|
||||
@@ -188,7 +190,7 @@ func (s *Server) handleList(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
for _, it := range items {
|
||||
p.Items = append(p.Items, itemView(it, site.ShowNotes))
|
||||
p.Items = append(p.Items, itemView(it, site.ShowNotes, site.ShowPreviews && hasPreviews(l.Category)))
|
||||
}
|
||||
if len(p.Items) > 0 && p.Items[0].Rank == 1 && p.Items[0].Image != "" {
|
||||
p.Hero = &p.Items[0]
|
||||
|
||||
Reference in new issue
Block a user