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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 421 additions and 72 deletions

View file

@ -7,6 +7,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/log"
@ -14,22 +15,23 @@ import (
)
type Archiver interface {
ZipAlbum(ctx context.Context, id string, w io.Writer) error
ZipArtist(ctx context.Context, id string, w io.Writer) error
ZipPlaylist(ctx context.Context, id string, w io.Writer) error
ZipAlbum(ctx context.Context, id string, format string, bitrate int, w io.Writer) error
ZipArtist(ctx context.Context, id string, format string, bitrate int, w io.Writer) error
ZipPlaylist(ctx context.Context, id string, format string, bitrate int, w io.Writer) error
}
func NewArchiver(ds model.DataStore) Archiver {
return &archiver{ds: ds}
func NewArchiver(ms MediaStreamer, ds model.DataStore) Archiver {
return &archiver{ds: ds, ms: ms}
}
type archiver struct {
ds model.DataStore
ms MediaStreamer
}
type createHeader func(idx int, mf model.MediaFile) *zip.FileHeader
type createHeader func(idx int, mf model.MediaFile, format string) *zip.FileHeader
func (a *archiver) ZipAlbum(ctx context.Context, id string, out io.Writer) error {
func (a *archiver) ZipAlbum(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
mfs, err := a.ds.MediaFile(ctx).GetAll(model.QueryOptions{
Filters: squirrel.Eq{"album_id": id},
Sort: "album",
@ -38,10 +40,10 @@ func (a *archiver) ZipAlbum(ctx context.Context, id string, out io.Writer) error
log.Error(ctx, "Error loading mediafiles from album", "id", id, err)
return err
}
return a.zipTracks(ctx, id, out, mfs, a.createHeader)
return a.zipTracks(ctx, id, format, bitrate, out, mfs, a.createHeader)
}
func (a *archiver) ZipArtist(ctx context.Context, id string, out io.Writer) error {
func (a *archiver) ZipArtist(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
mfs, err := a.ds.MediaFile(ctx).GetAll(model.QueryOptions{
Sort: "album",
Filters: squirrel.Eq{"album_artist_id": id},
@ -50,23 +52,25 @@ func (a *archiver) ZipArtist(ctx context.Context, id string, out io.Writer) erro
log.Error(ctx, "Error loading mediafiles from artist", "id", id, err)
return err
}
return a.zipTracks(ctx, id, out, mfs, a.createHeader)
return a.zipTracks(ctx, id, format, bitrate, out, mfs, a.createHeader)
}
func (a *archiver) ZipPlaylist(ctx context.Context, id string, out io.Writer) error {
func (a *archiver) ZipPlaylist(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
pls, err := a.ds.Playlist(ctx).GetWithTracks(id)
if err != nil {
log.Error(ctx, "Error loading mediafiles from playlist", "id", id, err)
return err
}
return a.zipTracks(ctx, id, out, pls.MediaFiles(), a.createPlaylistHeader)
return a.zipTracks(ctx, id, format, bitrate, out, pls.MediaFiles(), a.createPlaylistHeader)
}
func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs model.MediaFiles, ch createHeader) error {
func (a *archiver) zipTracks(ctx context.Context, id string, format string, bitrate int, out io.Writer, mfs model.MediaFiles, ch createHeader) error {
z := zip.NewWriter(out)
for idx, mf := range mfs {
_ = a.addFileToZip(ctx, z, mf, ch(idx, mf))
_ = a.addFileToZip(ctx, z, mf, format, bitrate, ch(idx, mf, format))
}
err := z.Close()
if err != nil {
log.Error(ctx, "Error closing zip file", "id", id, err)
@ -74,8 +78,13 @@ func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs
return err
}
func (a *archiver) createHeader(idx int, mf model.MediaFile) *zip.FileHeader {
func (a *archiver) createHeader(idx int, mf model.MediaFile, format string) *zip.FileHeader {
_, file := filepath.Split(mf.Path)
if format != "raw" {
file = strings.Replace(file, "."+mf.Suffix, "."+format, 1)
}
return &zip.FileHeader{
Name: fmt.Sprintf("%s/%s", mf.Album, file),
Modified: mf.UpdatedAt,
@ -83,8 +92,13 @@ func (a *archiver) createHeader(idx int, mf model.MediaFile) *zip.FileHeader {
}
}
func (a *archiver) createPlaylistHeader(idx int, mf model.MediaFile) *zip.FileHeader {
func (a *archiver) createPlaylistHeader(idx int, mf model.MediaFile, format string) *zip.FileHeader {
_, file := filepath.Split(mf.Path)
if format != "raw" {
file = strings.Replace(file, "."+mf.Suffix, "."+format, 1)
}
return &zip.FileHeader{
Name: fmt.Sprintf("%d - %s - %s", idx+1, mf.AlbumArtist, file),
Modified: mf.UpdatedAt,
@ -92,22 +106,46 @@ func (a *archiver) createPlaylistHeader(idx int, mf model.MediaFile) *zip.FileHe
}
}
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile, zh *zip.FileHeader) error {
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile, format string, bitrate int, zh *zip.FileHeader) error {
w, err := z.CreateHeader(zh)
if err != nil {
log.Error(ctx, "Error creating zip entry", "file", mf.Path, err)
return err
}
f, err := os.Open(mf.Path)
defer func() { _ = f.Close() }()
if err != nil {
log.Error(ctx, "Error opening file for zipping", "file", mf.Path, err)
return err
if format != "raw" {
stream, err := a.ms.DoStream(ctx, &mf, format, bitrate)
if err != nil {
return err
}
defer func() {
if err := stream.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug {
log.Error("Error closing stream", "id", mf.ID, "file", stream.Name(), err)
}
}()
_, err = io.Copy(w, stream)
if err != nil {
log.Error(ctx, "Error zipping file", "file", mf.Path, err)
return err
}
return nil
} else {
f, err := os.Open(mf.Path)
defer func() { _ = f.Close() }()
if err != nil {
log.Error(ctx, "Error opening file for zipping", "file", mf.Path, err)
return err
}
_, err = io.Copy(w, f)
if err != nil {
log.Error(ctx, "Error zipping file", "file", mf.Path, err)
return err
}
return nil
}
_, err = io.Copy(w, f)
if err != nil {
log.Error(ctx, "Error zipping file", "file", mf.Path, err)
return err
}
return nil
}