package main import ( "bytes" "errors" "fmt" "math" "regexp" "strconv" "strings" "time" "github.com/mmcdole/gofeed" ) const durationPattern = `^([0-9]+:)?([0-9]+:)?([0-9]+(\.[0-9]+)?)$` var durationRegexp = regexp.MustCompile(durationPattern) func main() { playlist, err := feed2m3u("https://feeds.soundcloud.com/users/soundcloud:users:520257816/sounds.rss") if err != nil { panic(err) } fmt.Println(playlist) } func feed2m3u(feedURL string) (string, error) { parser := gofeed.NewParser() feed, err := parser.ParseURL(feedURL) if err != nil { return "", err } defaultAuthor := "No Author" if feed.Author != nil && feed.Author.Name != "" { defaultAuthor = feed.Author.Name } defaultTitle := "No Title" if feed.Title != "" { defaultTitle = feed.Title } playlistBuffer := bytes.NewBuffer(nil) fmt.Fprintln(playlistBuffer, "#EXTM3U") for _, item := range feed.Items { duration := -1 if item.ITunesExt != nil && item.ITunesExt.Duration != "" { duration, err = normalizeDuration(item.ITunesExt.Duration) if err != nil { return "", err } } title := defaultTitle if item.Title != "" { title = item.Title } author := defaultAuthor if item.Author != nil && item.Author.Name != "" { author = item.Author.Name } for _, enclosure := range item.Enclosures { if strings.HasPrefix(strings.ToLower(enclosure.Type), "audio/") { fmt.Fprintf(playlistBuffer, "\n#EXTINF:%d, %s - %s\n%s\n", duration, title, author, enclosure.URL) break } } } return playlistBuffer.String(), nil } func normalizeDuration(durationString string) (int, error) { durationString = strings.ReplaceAll(durationString, " ", "") ok := durationRegexp.MatchString(durationString) if ok { durationComponents := strings.Split(durationString, ":") var total float64 = 0 for i, value := range durationComponents { intValue, err := strconv.ParseFloat(value, 64) if err != nil { return 0, err } total += intValue * math.Pow(60, float64(len(durationComponents)-i-1)) } return int(total), nil } durationString = strings.ReplaceAll(durationString, "min", "m") durationString = strings.ReplaceAll(durationString, "sec", "s") if duration, err := time.ParseDuration(durationString); err == nil { return int(duration.Seconds()), nil } return 0, errors.New("NormalizeDuration: duration didn't match the duration pattern") }