mirror of
https://github.com/navidrome/navidrome.git
synced 2026-01-05 21:38:27 -05:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a37beac753 | ||
|
|
8a31e80b7a | ||
|
|
ce11a2f3be | ||
|
|
5a95feeedc |
@@ -31,6 +31,7 @@ type mediaStream interface {
|
||||
Name() string
|
||||
ModTime() time.Time
|
||||
Close() error
|
||||
Duration() int
|
||||
}
|
||||
|
||||
type mediaStreamer struct {
|
||||
@@ -108,6 +109,10 @@ func (m *rawMediaStream) ModTime() time.Time {
|
||||
return m.mf.UpdatedAt
|
||||
}
|
||||
|
||||
func (m *rawMediaStream) Duration() int {
|
||||
return m.mf.Duration
|
||||
}
|
||||
|
||||
func (m *rawMediaStream) Close() error {
|
||||
log.Trace(m.ctx, "Closing file", "id", m.mf.ID, "path", m.mf.Path)
|
||||
return m.file.Close()
|
||||
@@ -186,6 +191,10 @@ func (m *transcodedMediaStream) ModTime() time.Time {
|
||||
return m.mf.UpdatedAt
|
||||
}
|
||||
|
||||
func (m *transcodedMediaStream) Duration() int {
|
||||
return m.mf.Duration
|
||||
}
|
||||
|
||||
func (m *transcodedMediaStream) Close() error {
|
||||
log.Trace(m.ctx, "Closing stream", "id", m.mf.ID, "path", m.mf.Path)
|
||||
err := m.pipe.Close()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/deluan/navidrome/log"
|
||||
"github.com/deluan/navidrome/model"
|
||||
"github.com/deluan/rest"
|
||||
"github.com/dhowden/tag/mbz"
|
||||
)
|
||||
|
||||
type albumRepository struct {
|
||||
@@ -36,7 +37,7 @@ func (r *albumRepository) Put(a *model.Album) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.index(a.ID, a.Name)
|
||||
return r.index(a.ID, a.Name, a.Artist, mbz.AlbumArtist)
|
||||
}
|
||||
|
||||
func (r *albumRepository) selectAlbum(options ...model.QueryOptions) SelectBuilder {
|
||||
|
||||
@@ -41,7 +41,7 @@ func (r mediaFileRepository) Put(m *model.MediaFile) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.index(m.ID, m.Title)
|
||||
return r.index(m.ID, m.Title, m.Album, m.Artist, m.AlbumArtist)
|
||||
}
|
||||
|
||||
func (r mediaFileRepository) selectMediaFile(options ...model.QueryOptions) SelectBuilder {
|
||||
|
||||
@@ -10,13 +10,16 @@ import (
|
||||
|
||||
const searchTable = "search"
|
||||
|
||||
func (r sqlRepository) index(id string, text string) error {
|
||||
sanitizedText := strings.TrimSpace(sanitize.Accents(strings.ToLower(text)))
|
||||
func (r sqlRepository) index(id string, text ...string) error {
|
||||
sanitizedText := strings.Builder{}
|
||||
for _, txt := range text {
|
||||
sanitizedText.WriteString(strings.TrimSpace(sanitize.Accents(strings.ToLower(txt))) + " ")
|
||||
}
|
||||
|
||||
values := map[string]interface{}{
|
||||
"id": id,
|
||||
"item_type": r.tableName,
|
||||
"full_text": sanitizedText,
|
||||
"full_text": strings.TrimSpace(sanitizedText.String()),
|
||||
}
|
||||
update := Update(searchTable).Where(Eq{"id": id}).SetMap(values)
|
||||
count, err := r.executeSQL(update)
|
||||
@@ -33,7 +36,7 @@ func (r sqlRepository) index(id string, text string) error {
|
||||
|
||||
func (r sqlRepository) doSearch(q string, offset, size int, results interface{}, orderBys ...string) error {
|
||||
q = strings.TrimSpace(sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*"))))
|
||||
if len(q) <= 2 {
|
||||
if len(q) < 2 {
|
||||
return nil
|
||||
}
|
||||
sq := Select("*").From(r.tableName)
|
||||
|
||||
@@ -73,6 +73,8 @@ func (api *Router) routes() http.Handler {
|
||||
H(reqParams, "getArtist", c.GetArtist)
|
||||
H(reqParams, "getAlbum", c.GetAlbum)
|
||||
H(reqParams, "getSong", c.GetSong)
|
||||
H(reqParams, "getArtistInfo", c.GetArtistInfo)
|
||||
H(reqParams, "getArtistInfo2", c.GetArtistInfo2)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
c := initAlbumListController(api)
|
||||
|
||||
@@ -165,6 +165,30 @@ func (c *BrowsingController) GetGenres(w http.ResponseWriter, r *http.Request) (
|
||||
return response, nil
|
||||
}
|
||||
|
||||
const noImageAvailableUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/No_image_available.svg/1024px-No_image_available.svg.png"
|
||||
|
||||
// TODO Integrate with Last.FM
|
||||
func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||
response := NewResponse()
|
||||
response.ArtistInfo = &responses.ArtistInfo{}
|
||||
response.ArtistInfo.Biography = "Biography not available"
|
||||
response.ArtistInfo.SmallImageUrl = noImageAvailableUrl
|
||||
response.ArtistInfo.MediumImageUrl = noImageAvailableUrl
|
||||
response.ArtistInfo.LargeImageUrl = noImageAvailableUrl
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// TODO Integrate with Last.FM
|
||||
func (c *BrowsingController) GetArtistInfo2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||
response := NewResponse()
|
||||
response.ArtistInfo2 = &responses.ArtistInfo2{}
|
||||
response.ArtistInfo2.Biography = "Biography not available"
|
||||
response.ArtistInfo2.SmallImageUrl = noImageAvailableUrl
|
||||
response.ArtistInfo2.MediumImageUrl = noImageAvailableUrl
|
||||
response.ArtistInfo2.LargeImageUrl = noImageAvailableUrl
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *BrowsingController) buildDirectory(d *engine.DirectoryInfo) *responses.Directory {
|
||||
dir := &responses.Directory{
|
||||
Id: d.Id,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","artistInfo":{"biography":"Black Sabbath is an English \u003ca target='_blank' href=\"http://www.last.fm/tag/heavy%20metal\" class=\"bbcode_tag\" rel=\"tag\"\u003eheavy metal\u003c/a\u003e band","musicBrainzId":"5182c1d9-c7d2-4dad-afa0-ccfeada921a8","lastFmUrl":"http://www.last.fm/music/Black+Sabbath","smallImageUrl":"http://userserve-ak.last.fm/serve/64/27904353.jpg","mediumImageUrl":"http://userserve-ak.last.fm/serve/126/27904353.jpg","largeImageUrl":"http://userserve-ak.last.fm/serve/_/27904353/Black+Sabbath+sabbath+1970.jpg","similarArtist":[{"id":"22","name":"Accept"},{"id":"101","name":"Bruce Dickinson"},{"id":"26","name":"Aerosmith"}]}}
|
||||
@@ -0,0 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><artistInfo><biography>Black Sabbath is an English <a target='_blank' href="http://www.last.fm/tag/heavy%20metal" class="bbcode_tag" rel="tag">heavy metal</a> band</biography><musicBrainzId>5182c1d9-c7d2-4dad-afa0-ccfeada921a8</musicBrainzId><lastFmUrl>http://www.last.fm/music/Black+Sabbath</lastFmUrl><smallImageUrl>http://userserve-ak.last.fm/serve/64/27904353.jpg</smallImageUrl><mediumImageUrl>http://userserve-ak.last.fm/serve/126/27904353.jpg</mediumImageUrl><largeImageUrl>http://userserve-ak.last.fm/serve/_/27904353/Black+Sabbath+sabbath+1970.jpg</largeImageUrl><similarArtist id="22" name="Accept"></similarArtist><similarArtist id="101" name="Bruce Dickinson"></similarArtist><similarArtist id="26" name="Aerosmith"></similarArtist></artistInfo></subsonic-response>
|
||||
@@ -0,0 +1 @@
|
||||
{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","artistInfo":{}}
|
||||
@@ -0,0 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0"><artistInfo></artistInfo></subsonic-response>
|
||||
@@ -34,6 +34,9 @@ type Subsonic struct {
|
||||
Artist *Indexes `xml:"artists,omitempty" json:"artists,omitempty"`
|
||||
ArtistWithAlbumsID3 *ArtistWithAlbumsID3 `xml:"artist,omitempty" json:"artist,omitempty"`
|
||||
AlbumWithSongsID3 *AlbumWithSongsID3 `xml:"album,omitempty" json:"album,omitempty"`
|
||||
|
||||
ArtistInfo *ArtistInfo `xml:"artistInfo,omitempty" json:"artistInfo,omitempty"`
|
||||
ArtistInfo2 *ArtistInfo2 `xml:"artistInfo2,omitempty" json:"artistInfo2,omitempty"`
|
||||
}
|
||||
|
||||
type JsonWrapper struct {
|
||||
@@ -272,3 +275,22 @@ type Genre struct {
|
||||
type Genres struct {
|
||||
Genre []Genre `xml:"genre,omitempty" json:"genre,omitempty"`
|
||||
}
|
||||
|
||||
type ArtistInfoBase struct {
|
||||
Biography string `xml:"biography,omitempty" json:"biography,omitempty"`
|
||||
MusicBrainzID string `xml:"musicBrainzId,omitempty" json:"musicBrainzId,omitempty"`
|
||||
LastFmUrl string `xml:"lastFmUrl,omitempty" json:"lastFmUrl,omitempty"`
|
||||
SmallImageUrl string `xml:"smallImageUrl,omitempty" json:"smallImageUrl,omitempty"`
|
||||
MediumImageUrl string `xml:"mediumImageUrl,omitempty" json:"mediumImageUrl,omitempty"`
|
||||
LargeImageUrl string `xml:"largeImageUrl,omitempty" json:"largeImageUrl,omitempty"`
|
||||
}
|
||||
|
||||
type ArtistInfo struct {
|
||||
ArtistInfoBase
|
||||
SimilarArtist []Artist `xml:"similarArtist,omitempty" json:"similarArtist,omitempty"`
|
||||
}
|
||||
|
||||
type ArtistInfo2 struct {
|
||||
ArtistInfoBase
|
||||
SimilarArtist []ArtistID3 `xml:"similarArtist,omitempty" json:"similarArtist,omitempty"`
|
||||
}
|
||||
|
||||
@@ -282,4 +282,42 @@ var _ = Describe("Responses", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ArtistInfo", func() {
|
||||
BeforeEach(func() {
|
||||
response.ArtistInfo = &ArtistInfo{}
|
||||
})
|
||||
|
||||
Context("without data", func() {
|
||||
It("should match .XML", func() {
|
||||
Expect(xml.Marshal(response)).To(MatchSnapshot())
|
||||
})
|
||||
It("should match .JSON", func() {
|
||||
Expect(json.Marshal(response)).To(MatchSnapshot())
|
||||
})
|
||||
})
|
||||
|
||||
Context("with data", func() {
|
||||
BeforeEach(func() {
|
||||
response.ArtistInfo.Biography = `Black Sabbath is an English <a target='_blank' href="http://www.last.fm/tag/heavy%20metal" class="bbcode_tag" rel="tag">heavy metal</a> band`
|
||||
response.ArtistInfo.MusicBrainzID = "5182c1d9-c7d2-4dad-afa0-ccfeada921a8"
|
||||
response.ArtistInfo.LastFmUrl = "http://www.last.fm/music/Black+Sabbath"
|
||||
response.ArtistInfo.SmallImageUrl = "http://userserve-ak.last.fm/serve/64/27904353.jpg"
|
||||
response.ArtistInfo.MediumImageUrl = "http://userserve-ak.last.fm/serve/126/27904353.jpg"
|
||||
response.ArtistInfo.LargeImageUrl = "http://userserve-ak.last.fm/serve/_/27904353/Black+Sabbath+sabbath+1970.jpg"
|
||||
response.ArtistInfo.SimilarArtist = []Artist{
|
||||
{Id: "22", Name: "Accept"},
|
||||
{Id: "101", Name: "Bruce Dickinson"},
|
||||
{Id: "26", Name: "Aerosmith"},
|
||||
}
|
||||
})
|
||||
It("should match .XML", func() {
|
||||
Expect(xml.Marshal(response)).To(MatchSnapshot())
|
||||
})
|
||||
It("should match .JSON", func() {
|
||||
Expect(json.Marshal(response)).To(MatchSnapshot())
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package subsonic
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/deluan/navidrome/engine"
|
||||
"github.com/deluan/navidrome/server/subsonic/responses"
|
||||
@@ -31,6 +32,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp
|
||||
|
||||
// Override Content-Type detected by http.FileServer
|
||||
w.Header().Set("Content-Type", ms.ContentType())
|
||||
w.Header().Set("X-Content-Duration", strconv.Itoa(ms.Duration()))
|
||||
http.ServeContent(w, r, ms.Name(), ms.ModTime(), ms)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user