Enable transcoding of downlods (#1667)

* feat(download): Enable transcoding of downlods - #573

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>

* feat(download): Make automatic transcoding of downloads optional

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>

* Fix spelling

* address changes

* prettier

* fix config

* use previous name

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>
This commit is contained in:
Kendall Garner
2022-12-18 12:12:37 -05:00
committed by GitHub
parent 6489dd4478
commit 54395e7e6a
20 changed files with 421 additions and 72 deletions
+59 -30
View File
@@ -1,6 +1,7 @@
package subsonic
import (
"context"
"fmt"
"io"
"net/http"
@@ -16,31 +17,7 @@ import (
"github.com/navidrome/navidrome/utils"
)
func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
id, err := requiredParamString(r, "id")
if err != nil {
return nil, err
}
maxBitRate := utils.ParamInt(r, "maxBitRate", 0)
format := utils.ParamString(r, "format")
estimateContentLength := utils.ParamBool(r, "estimateContentLength", false)
stream, err := api.streamer.NewStream(ctx, id, format, maxBitRate)
if err != nil {
return nil, err
}
// Make sure the stream will be closed at the end, to avoid leakage
defer func() {
if err := stream.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug {
log.Error(r.Context(), "Error closing stream", "id", id, "file", stream.Name(), err)
}
}()
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Content-Duration", strconv.FormatFloat(float64(stream.Duration()), 'G', -1, 32))
func (api *Router) ServeStream(ctx context.Context, w http.ResponseWriter, r *http.Request, stream *core.Stream, id string) {
if stream.Seekable() {
http.ServeContent(w, r, stream.Name(), stream.ModTime(), stream)
} else {
@@ -48,6 +25,8 @@ func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Su
w.Header().Set("Accept-Ranges", "none")
w.Header().Set("Content-Type", stream.ContentType())
estimateContentLength := utils.ParamBool(r, "estimateContentLength", false)
// if Client requests the estimated content-length, send it
if estimateContentLength {
length := strconv.Itoa(stream.EstimatedContentLength())
@@ -68,6 +47,33 @@ func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Su
}
}
}
}
func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
id, err := requiredParamString(r, "id")
if err != nil {
return nil, err
}
maxBitRate := utils.ParamInt(r, "maxBitRate", 0)
format := utils.ParamString(r, "format")
stream, err := api.streamer.NewStream(ctx, id, format, maxBitRate)
if err != nil {
return nil, err
}
// Make sure the stream will be closed at the end, to avoid leakage
defer func() {
if err := stream.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug {
log.Error("Error closing stream", "id", id, "file", stream.Name(), err)
}
}()
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Content-Duration", strconv.FormatFloat(float64(stream.Duration()), 'G', -1, 32))
api.ServeStream(ctx, w, r, stream, id)
return nil, nil
}
@@ -90,6 +96,27 @@ func (api *Router) Download(w http.ResponseWriter, r *http.Request) (*responses.
return nil, err
}
maxBitRate := utils.ParamInt(r, "bitrate", 0)
format := utils.ParamString(r, "format")
if format == "" {
if conf.Server.AutoTranscodeDownload {
// if we are not provided a format, see if we have requested transcoding for this client
// This must be enabled via a config option. For the UI, we are always given an option.
// This will impact other clients which do not use the UI
transcoding, ok := request.TranscodingFrom(ctx)
if !ok {
format = "raw"
} else {
format = transcoding.TargetFormat
maxBitRate = transcoding.DefaultBitRate
}
} else {
format = "raw"
}
}
setHeaders := func(name string) {
name = strings.ReplaceAll(name, ",", "_")
disposition := fmt.Sprintf("attachment; filename=\"%s.zip\"", name)
@@ -99,24 +126,26 @@ func (api *Router) Download(w http.ResponseWriter, r *http.Request) (*responses.
switch v := entity.(type) {
case *model.MediaFile:
stream, err := api.streamer.NewStream(ctx, id, "raw", 0)
stream, err := api.streamer.NewStream(ctx, id, format, maxBitRate)
if err != nil {
return nil, err
}
disposition := fmt.Sprintf("attachment; filename=\"%s\"", stream.Name())
w.Header().Set("Content-Disposition", disposition)
http.ServeContent(w, r, stream.Name(), stream.ModTime(), stream)
api.ServeStream(ctx, w, r, stream, id)
return nil, nil
case *model.Album:
setHeaders(v.Name)
err = api.archiver.ZipAlbum(ctx, id, w)
err = api.archiver.ZipAlbum(ctx, id, format, maxBitRate, w)
case *model.Artist:
setHeaders(v.Name)
err = api.archiver.ZipArtist(ctx, id, w)
err = api.archiver.ZipArtist(ctx, id, format, maxBitRate, w)
case *model.Playlist:
setHeaders(v.Name)
err = api.archiver.ZipPlaylist(ctx, id, w)
err = api.archiver.ZipPlaylist(ctx, id, format, maxBitRate, w)
default:
err = model.ErrNotFound
}