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 committed by Joe Stump
parent f2626e6ebc
commit 8a55dc27ca
No known key found for this signature in database
GPG Key ID: 29151C3EC48A0EB9
2 changed files with 41 additions and 4 deletions

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)

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))
}) })
}) })
}) })