package main import ( "bufio" "bytes" "errors" "fmt" "log" "os" "path" "strings" "text/template" "github.com/dhowden/tag" ) const ID3_TEMPLATE = "{{.Artist}} // {{.Album}} // {{.Title}}" type Tags struct { Title string Album string Artist string AlbumArtist string Composer string Genre string Year int Track int TrackCount int Disc int DiscCount int } func (t *Tags) Validate() error { if t.Title == "" { return errors.New("missing Title") } if t.Album == "" { return errors.New("missing Album") } if t.Artist == "" { return errors.New("missing Artist") } if t.AlbumArtist == "" { return errors.New("missing AlbumArtist") } return nil } func main() { err := mainErr() if err != nil { log.Fatal("ERROR: ", err) } } func mainErr() error { // file from arg playlistFile := os.Args[1] newFileLines, err := loadPlaylistFile(playlistFile) if err != nil { return err } newFileContent := strings.Join(newFileLines, "\n") return writeFile(playlistFile, newFileContent) } // returns array of all id3 tags func loadPlaylistFile(playlistFile string) ([]string, error) { file, err := os.Open(playlistFile) if err != nil { log.Fatal(err) } defer file.Close() playlistTags := make([]string, 0) lineNumber := 0 scanner := bufio.NewScanner(file) for scanner.Scan() { // line by line line := scanner.Text() tags, err := loadPlaylistFileLine(line) if err != nil { return nil, fmt.Errorf("%s line %d: %s", playlistFile, lineNumber, err) } if tags != nil { playlistTags = append(playlistTags, *tags) } lineNumber++ } if err := scanner.Err(); err != nil { return nil, err } return playlistTags, nil } // ignore m3u8 tags func loadPlaylistFileLine(line string) (*string, error) { line = strings.TrimSpace(line) if []rune(line)[1] == '#' { return nil, nil } if len(line) == 0 { return nil, nil } tags, err := loadSongFile(line) return &tags, err } // returns id3 tags func loadSongFile(fileName string) (string, error) { // load file file, err := os.Open(fileName) if err != nil { return "", err } defer file.Close() tags, err := tag.ReadFrom(file) if err != nil { return "", err } // parse tags track, trackCount := tags.Track() disc, discCount := tags.Disc() loadedTags := Tags{ Title: tags.Title(), Album: tags.Album(), Artist: tags.Artist(), AlbumArtist: tags.AlbumArtist(), Composer: tags.Composer(), Genre: tags.Genre(), Year: tags.Year(), Track: track, TrackCount: trackCount, Disc: disc, DiscCount: discCount, } err = loadedTags.Validate() if err != nil { return "", err } return id3Template(loadedTags) } // converts an id3 tag into a string func id3Template(tags Tags) (string, error) { tmpl := template.Must(template.New("id3template").Parse(ID3_TEMPLATE)) bufPath := bytes.NewBufferString("") err := tmpl.Execute(bufPath, tags) if err != nil { return "", err } return bufPath.String(), nil } // outputs all strings as a file func writeFile(inFileName string, fileContent string) error { inFileExt := path.Ext(inFileName) extIndex := strings.LastIndex(inFileName, inFileExt) newFileName := inFileName[:extIndex] + ".txt" return os.WriteFile(newFileName, []byte(fileContent), 0666) }