mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-03 20:47:35 +03:00
Refactor zip archiver.
Add `disc` to path when downloading albums. Fix #2121
This commit is contained in:
parent
5163df6531
commit
dc56c52557
1 changed files with 73 additions and 66 deletions
139
core/archiver.go
139
core/archiver.go
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/Masterminds/squirrel"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
)
|
||||
|
||||
type Archiver interface {
|
||||
|
@ -29,8 +30,6 @@ type archiver struct {
|
|||
ms MediaStreamer
|
||||
}
|
||||
|
||||
type createHeader func(idx int, mf model.MediaFile, format string) *zip.FileHeader
|
||||
|
||||
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},
|
||||
|
@ -40,19 +39,52 @@ func (a *archiver) ZipAlbum(ctx context.Context, id string, format string, bitra
|
|||
log.Error(ctx, "Error loading mediafiles from album", "id", id, err)
|
||||
return err
|
||||
}
|
||||
return a.zipTracks(ctx, id, format, bitrate, out, mfs, a.createHeader)
|
||||
return a.zipAlbums(ctx, id, format, bitrate, out, mfs)
|
||||
}
|
||||
|
||||
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},
|
||||
Sort: "album",
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error loading mediafiles from artist", "id", id, err)
|
||||
return err
|
||||
}
|
||||
return a.zipTracks(ctx, id, format, bitrate, out, mfs, a.createHeader)
|
||||
return a.zipAlbums(ctx, id, format, bitrate, out, mfs)
|
||||
}
|
||||
|
||||
func (a *archiver) zipAlbums(ctx context.Context, id string, format string, bitrate int, out io.Writer, mfs model.MediaFiles) error {
|
||||
z := zip.NewWriter(out)
|
||||
albums := slice.Group(mfs, func(mf model.MediaFile) string {
|
||||
return mf.AlbumID
|
||||
})
|
||||
for _, album := range albums {
|
||||
discs := slice.Group(album, func(mf model.MediaFile) int { return mf.DiscNumber })
|
||||
isMultDisc := len(discs) > 1
|
||||
log.Debug(ctx, "Zipping album", "name", album[0].Album, "artist", album[0].AlbumArtist,
|
||||
"format", format, "bitrate", bitrate, "isMultDisc", isMultDisc, "numTracks", len(album))
|
||||
for _, mf := range album {
|
||||
file := a.albumFilename(mf, format, isMultDisc)
|
||||
_ = a.addFileToZip(ctx, z, mf, format, bitrate, file)
|
||||
}
|
||||
}
|
||||
err := z.Close()
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error closing zip file", "id", id, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *archiver) albumFilename(mf model.MediaFile, format string, isMultDisc bool) string {
|
||||
_, file := filepath.Split(mf.Path)
|
||||
if format != "raw" {
|
||||
file = strings.TrimSuffix(file, mf.Suffix) + format
|
||||
}
|
||||
if isMultDisc {
|
||||
file = fmt.Sprintf("Disc %02d/%s", mf.DiscNumber, file)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", mf.Album, file)
|
||||
}
|
||||
|
||||
func (a *archiver) ZipPlaylist(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
|
||||
|
@ -61,16 +93,17 @@ func (a *archiver) ZipPlaylist(ctx context.Context, id string, format string, bi
|
|||
log.Error(ctx, "Error loading mediafiles from playlist", "id", id, err)
|
||||
return err
|
||||
}
|
||||
return a.zipTracks(ctx, id, format, bitrate, out, pls.MediaFiles(), a.createPlaylistHeader)
|
||||
return a.zipPlaylist(ctx, id, format, bitrate, out, pls)
|
||||
}
|
||||
|
||||
func (a *archiver) zipTracks(ctx context.Context, id string, format string, bitrate int, out io.Writer, mfs model.MediaFiles, ch createHeader) error {
|
||||
func (a *archiver) zipPlaylist(ctx context.Context, id string, format string, bitrate int, out io.Writer, pls *model.Playlist) error {
|
||||
mfs := pls.MediaFiles()
|
||||
z := zip.NewWriter(out)
|
||||
|
||||
log.Debug(ctx, "Zipping playlist", "name", pls.Name, "format", format, "bitrate", bitrate, "numTracks", len(mfs))
|
||||
for idx, mf := range mfs {
|
||||
_ = a.addFileToZip(ctx, z, mf, format, bitrate, ch(idx, mf, format))
|
||||
file := a.playlistFilename(mf, format, idx)
|
||||
_ = a.addFileToZip(ctx, z, mf, format, bitrate, file)
|
||||
}
|
||||
|
||||
err := z.Close()
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error closing zip file", "id", id, err)
|
||||
|
@ -78,74 +111,48 @@ func (a *archiver) zipTracks(ctx context.Context, id string, format string, bitr
|
|||
return err
|
||||
}
|
||||
|
||||
func (a *archiver) createHeader(idx int, mf model.MediaFile, format string) *zip.FileHeader {
|
||||
_, file := filepath.Split(mf.Path)
|
||||
|
||||
func (a *archiver) playlistFilename(mf model.MediaFile, format string, idx int) string {
|
||||
ext := mf.Suffix
|
||||
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,
|
||||
Method: zip.Store,
|
||||
ext = format
|
||||
}
|
||||
file := fmt.Sprintf("%02d - %s - %s.%s", idx+1, mf.Artist, mf.Title, ext)
|
||||
return file
|
||||
}
|
||||
|
||||
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),
|
||||
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile, format string, bitrate int, filename string) error {
|
||||
w, err := z.CreateHeader(&zip.FileHeader{
|
||||
Name: filename,
|
||||
Modified: mf.UpdatedAt,
|
||||
Method: zip.Store,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var r io.ReadCloser
|
||||
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
|
||||
r, err = a.ms.DoStream(ctx, &mf, format, bitrate)
|
||||
} 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
|
||||
r, err = os.Open(mf.Path)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error opening file for zipping", "file", mf.Path, "format", format, err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := r.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug {
|
||||
log.Error("Error closing stream", "id", mf.ID, "file", mf.Path, err)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(w, r)
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error zipping file", "file", mf.Path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue