feat(server): add M3U file to downloaded playlist

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan
2025-05-27 20:13:37 -04:00
parent de698918ac
commit 1f9cbe7345
2 changed files with 41 additions and 4 deletions
+28 -3
View File
@@ -98,7 +98,7 @@ func (a *archiver) ZipShare(ctx context.Context, id string, out io.Writer) error
return model.ErrNotAuthorized return model.ErrNotAuthorized
} }
log.Debug(ctx, "Zipping share", "name", s.ID, "format", s.Format, "bitrate", s.MaxBitRate, "numTracks", len(s.Tracks)) log.Debug(ctx, "Zipping share", "name", s.ID, "format", s.Format, "bitrate", s.MaxBitRate, "numTracks", len(s.Tracks))
return a.zipMediaFiles(ctx, id, s.Format, s.MaxBitRate, out, s.Tracks) return a.zipMediaFiles(ctx, id, s.ID, s.Format, s.MaxBitRate, out, s.Tracks, false)
} }
func (a *archiver) ZipPlaylist(ctx context.Context, id string, format string, bitrate int, out io.Writer) error { func (a *archiver) ZipPlaylist(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
@@ -109,15 +109,40 @@ func (a *archiver) ZipPlaylist(ctx context.Context, id string, format string, bi
} }
mfs := pls.MediaFiles() mfs := pls.MediaFiles()
log.Debug(ctx, "Zipping playlist", "name", pls.Name, "format", format, "bitrate", bitrate, "numTracks", len(mfs)) log.Debug(ctx, "Zipping playlist", "name", pls.Name, "format", format, "bitrate", bitrate, "numTracks", len(mfs))
return a.zipMediaFiles(ctx, id, format, bitrate, out, mfs) return a.zipMediaFiles(ctx, id, pls.Name, format, bitrate, out, mfs, true)
} }
func (a *archiver) zipMediaFiles(ctx context.Context, id string, format string, bitrate int, out io.Writer, mfs model.MediaFiles) error { func (a *archiver) zipMediaFiles(ctx context.Context, id, name string, format string, bitrate int, out io.Writer, mfs model.MediaFiles, addM3U bool) error {
z := createZipWriter(out, format, bitrate) z := createZipWriter(out, format, bitrate)
zippedMfs := make(model.MediaFiles, len(mfs))
for idx, mf := range mfs { for idx, mf := range mfs {
file := a.playlistFilename(mf, format, idx) file := a.playlistFilename(mf, format, idx)
_ = a.addFileToZip(ctx, z, mf, format, bitrate, file) _ = a.addFileToZip(ctx, z, mf, format, bitrate, file)
mf.Path = file
zippedMfs[idx] = mf
} }
// Add M3U file if requested
if addM3U && len(zippedMfs) > 0 {
plsName := sanitizeName(name)
w, err := z.CreateHeader(&zip.FileHeader{
Name: plsName + ".m3u",
Modified: mfs[0].UpdatedAt,
Method: zip.Store,
})
if err != nil {
log.Error(ctx, "Error creating playlist zip entry", err)
return err
}
_, err = w.Write([]byte(zippedMfs.ToM3U8(plsName, false)))
if err != nil {
log.Error(ctx, "Error writing m3u in zip", err)
return err
}
}
err := z.Close() err := z.Close()
if err != nil { if err != nil {
log.Error(ctx, "Error closing zip file", "id", id, err) log.Error(ctx, "Error closing zip file", "id", id, err)
+13 -1
View File
@@ -145,9 +145,21 @@ var _ = Describe("Archiver", func() {
zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len())) zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len()))
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(len(zr.File)).To(Equal(2)) Expect(len(zr.File)).To(Equal(3))
Expect(zr.File[0].Name).To(Equal("01 - AC_DC - track1.mp3")) Expect(zr.File[0].Name).To(Equal("01 - AC_DC - track1.mp3"))
Expect(zr.File[1].Name).To(Equal("02 - Artist 2 - track2.mp3")) Expect(zr.File[1].Name).To(Equal("02 - Artist 2 - track2.mp3"))
Expect(zr.File[2].Name).To(Equal("Test Playlist.m3u"))
// Verify M3U content
m3uFile, err := zr.File[2].Open()
Expect(err).To(BeNil())
defer m3uFile.Close()
m3uContent, err := io.ReadAll(m3uFile)
Expect(err).To(BeNil())
expectedM3U := "#EXTM3U\n#PLAYLIST:Test Playlist\n#EXTINF:0,AC/DC - track1\n01 - AC_DC - track1.mp3\n#EXTINF:0,Artist 2 - track2\n02 - Artist 2 - track2.mp3\n"
Expect(string(m3uContent)).To(Equal(expectedM3U))
}) })
}) })
}) })