feat(subsonic): add support for podcast episodes in both playlists and play queues

This commit is contained in:
Brian Doherty
2022-11-11 11:29:27 -06:00
committed by sentriz
parent ae5bc2e149
commit aecee3d2d8
6 changed files with 184 additions and 69 deletions

View File

@@ -13,15 +13,16 @@ import (
"github.com/jinzhu/gorm"
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
)
var (
errPlaylistNoMatch = errors.New("couldn't match track")
)
func playlistParseLine(c *Controller, absPath string) (int, error) {
func playlistParseLine(c *Controller, absPath string) (*specid.ID, error) {
if strings.HasPrefix(absPath, "#") || strings.TrimSpace(absPath) == "" {
return 0, nil
return nil, nil
}
var track db.Track
query := c.DB.Raw(`
@@ -30,14 +31,23 @@ func playlistParseLine(c *Controller, absPath string) (int, error) {
WHERE (albums.root_dir || ? || albums.left_path || albums.right_path || ? || tracks.filename)=?`,
string(os.PathSeparator), string(os.PathSeparator), absPath)
err := query.First(&track).Error
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
return 0, fmt.Errorf("%v: %w", err, errPlaylistNoMatch)
case err != nil:
return 0, fmt.Errorf("while matching: %w", err)
default:
return track.ID, nil
if err == nil {
return &specid.ID{Type: specid.Track, Value: track.ID}, nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("while matching: %w", err)
}
var pe db.PodcastEpisode
err = c.DB.Where("path=?", absPath).First(&pe).Error
if err == nil {
return &specid.ID{Type: specid.PodcastEpisode, Value: pe.ID}, nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("while matching: %w", err)
}
return nil, fmt.Errorf("%v: %w", err, errPlaylistNoMatch)
}
func playlistCheckContentType(contentType string) bool {
@@ -65,7 +75,7 @@ func playlistParseUpload(c *Controller, userID int, header *multipart.FileHeader
if !playlistCheckContentType(contentType) {
return []string{fmt.Sprintf("invalid content-type %q", contentType)}, false
}
var trackIDs []int
var trackIDs []specid.ID
var errors []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
@@ -74,8 +84,8 @@ func playlistParseUpload(c *Controller, userID int, header *multipart.FileHeader
// trim length of error to not overflow cookie flash
errors = append(errors, fmt.Sprintf("%.100s", err.Error()))
}
if trackID != 0 {
trackIDs = append(trackIDs, trackID)
if trackID.Value != 0 {
trackIDs = append(trackIDs, *trackID)
}
}
if err := scanner.Err(); err != nil {

View File

@@ -167,16 +167,31 @@ func (c *Controller) ServeGetPlayQueue(r *http.Request) *spec.Response {
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, id := range trackIDs {
track := db.Track{}
c.DB.
Where("id=?", id).
Preload("Album").
Preload("TrackStar", "user_id=?", user.ID).
Preload("TrackRating", "user_id=?", user.ID).
Find(&track)
sub.PlayQueue.List[i] = spec.NewTCTrackByFolder(&track, track.Album)
sub.PlayQueue.List[i].TranscodedContentType = transcodeMIME
sub.PlayQueue.List[i].TranscodedSuffix = transcodeSuffix
switch id.Type {
case specid.Track:
track := db.Track{}
c.DB.
Where("id=?", id.Value).
Preload("Album").
Preload("TrackStar", "user_id=?", user.ID).
Preload("TrackRating", "user_id=?", user.ID).
Find(&track)
sub.PlayQueue.List[i] = spec.NewTCTrackByFolder(&track, track.Album)
sub.PlayQueue.List[i].TranscodedContentType = transcodeMIME
sub.PlayQueue.List[i].TranscodedSuffix = transcodeSuffix
case specid.PodcastEpisode:
pe := db.PodcastEpisode{}
c.DB.
Where("id=?", id.Value).
Find(&pe)
p := db.Podcast{}
c.DB.
Where("id=?", pe.PodcastID).
Find(&p)
sub.PlayQueue.List[i] = spec.NewTCPodcastEpisode(&pe, &p)
sub.PlayQueue.List[i].TranscodedContentType = transcodeMIME
sub.PlayQueue.List[i].TranscodedSuffix = transcodeSuffix
}
}
return sub
}
@@ -187,11 +202,10 @@ func (c *Controller) ServeSavePlayQueue(r *http.Request) *spec.Response {
if err != nil {
return spec.NewError(10, "please provide some `id` parameters")
}
// TODO: support other play queue entries other than tracks
trackIDs := make([]int, 0, len(tracks))
trackIDs := make([]specid.ID, 0, len(tracks))
for _, id := range tracks {
if id.Type == specid.Track {
trackIDs = append(trackIDs, id.Value)
if (id.Type == specid.Track) || (id.Type == specid.PodcastEpisode) {
trackIDs = append(trackIDs, id)
}
}
if len(trackIDs) == 0 {
@@ -201,7 +215,7 @@ func (c *Controller) ServeSavePlayQueue(r *http.Request) *spec.Response {
var queue db.PlayQueue
c.DB.Where("user_id=?", user.ID).First(&queue)
queue.UserID = user.ID
queue.Current = params.GetOrID("current", specid.ID{}).Value
queue.Current = params.GetOrID("current", specid.ID{}).String()
queue.Position = params.GetOrInt("position", 0)
queue.ChangedBy = params.GetOr("c", "") // must exist, middleware checks
queue.SetItems(trackIDs)

View File

@@ -11,6 +11,7 @@ import (
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
)
func playlistRender(c *Controller, playlist *db.Playlist, params params.Params) *spec.Playlist {
@@ -33,23 +34,47 @@ func playlistRender(c *Controller, playlist *db.Playlist, params params.Params)
transcodeMIME, transcodeSuffix := streamGetTransPrefProfile(c.DB, user.ID, params.GetOr("c", ""))
for i, id := range trackIDs {
track := db.Track{}
err := c.DB.
Where("id=?", id).
Preload("Album").
Preload("Album.TagArtist").
Preload("TrackStar", "user_id=?", user.ID).
Preload("TrackRating", "user_id=?", user.ID).
Find(&track).
Error
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("wasn't able to find track with id %d", id)
continue
switch id.Type {
case specid.Track:
track := db.Track{}
err := c.DB.
Where("id=?", id.Value).
Preload("Album").
Preload("Album.TagArtist").
Preload("TrackStar", "user_id=?", user.ID).
Preload("TrackRating", "user_id=?", user.ID).
Find(&track).
Error
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("wasn't able to find track with id %d", id.Value)
continue
}
resp.List[i] = spec.NewTCTrackByFolder(&track, track.Album)
resp.Duration += track.Length
case specid.PodcastEpisode:
pe := db.PodcastEpisode{}
err := c.DB.
Where("id=?", id.Value).
Find(&pe).
Error
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("wasn't able to find podcast episode with id %d", id.Value)
continue
}
p := db.Podcast{}
err = c.DB.
Where("id=?", pe.PodcastID).
Find(&p).
Error
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("wasn't able to find podcast with id %d", pe.PodcastID)
continue
}
resp.List[i] = spec.NewTCPodcastEpisode(&pe, &p)
resp.Duration += pe.Length
}
resp.List[i] = spec.NewTCTrackByFolder(&track, track.Album)
resp.List[i].TranscodedContentType = transcodeMIME
resp.List[i].TranscodedSuffix = transcodeSuffix
resp.Duration += track.Length
}
return resp
}
@@ -109,12 +134,7 @@ func (c *Controller) ServeCreatePlaylist(r *http.Request) *spec.Response {
}
// replace song IDs
var trackIDs []int
if p, err := params.GetIDList("songId"); err == nil {
for _, i := range p {
trackIDs = append(trackIDs, i.Value)
}
}
trackIDs, _ := params.GetIDList("songId")
// Set the items of the playlist
playlist.SetItems(trackIDs)
c.DB.Save(playlist)
@@ -161,9 +181,7 @@ func (c *Controller) ServeUpdatePlaylist(r *http.Request) *spec.Response {
// add items
if p, err := params.GetIDList("songIdToAdd"); err == nil {
for _, i := range p {
trackIDs = append(trackIDs, i.Value)
}
trackIDs = append(trackIDs, p...)
}
playlist.SetItems(trackIDs)

View File

@@ -95,6 +95,24 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
return trCh
}
func NewTCPodcastEpisode(pe *db.PodcastEpisode, parent *db.Podcast) *TrackChild {
trCh := &TrackChild{
ID: pe.SID(),
ContentType: pe.MIME(),
Suffix: pe.Ext(),
Size: pe.Size,
Title: pe.Title,
Path: pe.Path,
ParentID: parent.SID(),
Duration: pe.Length,
Bitrate: pe.Bitrate,
IsDir: false,
Type: "podcastepisode",
CreatedAt: pe.CreatedAt,
}
return trCh
}
func NewArtistByFolder(f *db.Album) *Artist {
// the db is structued around "browse by tags", and where
// an album is also a folder. so we're constructing an artist