package download import ( "fmt" "os" "path/filepath" "strconv" "strings" "github.com/mathismqn/godeez/internal/deezer" ) // m3uEntry pairs a playlist track with the file it ended up at, so the m3u // can be written after the fact without re-deriving the path. type m3uEntry struct { track *deezer.Track path string } // writeM3U records a playlist's tracks in their original order as an // extended M3U file next to the downloaded audio. // // This exists because the on-disk file names carry no ordering for // playlists (unlike albums, which get a zero padded track number prefix): // without an m3u, the sequence the user curated on Deezer is lost the moment // the tracks land in a directory that any player will list alphabetically. // // Skipped tracks are included: they are already on disk from a previous run // and belong at their playlist position just as much as one downloaded this // run. Failed tracks are left out since there is nothing on disk to point // at, and a broken entry would only confuse the player. // // Paths are written relative to outputDir so the m3u keeps working if the // whole playlist folder is moved or copied elsewhere. func writeM3U(outputDir string, entries []m3uEntry) error { if len(entries) == 0 { return nil } m3uPath := filepath.Join(outputDir, filepath.Base(outputDir)+".m3u") var b strings.Builder b.WriteString("#EXTM3U\n") for _, e := range entries { duration, err := strconv.Atoi(e.track.Duration) if err != nil { duration = 0 } rel, err := filepath.Rel(outputDir, e.path) if err != nil { rel = e.path } fmt.Fprintf(&b, "#EXTINF:%d,%s - %s\n%s\n", duration, e.track.Artist, e.track.FullTitle(), rel) } return os.WriteFile(m3uPath, []byte(b.String()), 0644) }