Commits

Robert Lowry  committed 3c292d8

replaced Tags struct with map[string]string, simplified ID3v1 parser

  • Participants
  • Parent commits ec89fcc

Comments (0)

Files changed (5)

 	"io"
 )
 
-// A parsed ID3 file with common fields exposed.
-type SimpleTags struct {
-	Title   string
-	Artist string
-	Album  string
-	Year   string
-	Track  string
-	Disc   string
-	Genre  string
-	Length string
-}
-
-// Parse stream for ID3 information. Returns nil if parsing failed or the
-// input didn't contain ID3 information.
-// NOTE: ID3v1 and appended ID3v2.x are not supported without the ability
-// to seek in the input. Use ReadFile instead.
-func Read(reader io.Reader) (*SimpleTags, error) {
-	buf := bufio.NewReader(reader)
-	if !hasID3v2Tag(buf) {
-		return nil, fmt.Errorf("no id3 tags")
-	}
-	tags, err := parseID3v2File(buf)
-	if err != nil {
-		return nil, err
-	}
-	return tags, nil
-}
-
 // Parse seekable stream for ID3 information. Returns nil if ID3 tag is
 // not found or parsing fails.
-func ReadFile(reader io.ReadSeeker) (*SimpleTags, error) {
+func ReadFile(reader io.ReadSeeker) (map[string]string, error) {
 	buf := bufio.NewReader(reader)
 	if hasID3v1Tag(reader) {
 		tags, err := parseID3v1File(reader)

File id3/id3v1.go

 package id3
 
 import (
-	"bufio"
 	"fmt"
 	"io"
 	"strings"
 )
 
+type ID3v1Frame struct {
+	name   string
+	length int
+}
+
+var ID3v1Frames = []ID3v1Frame{
+	{"title", 30},
+	{"artist", 30},
+	{"album", 30},
+	{"year", 4},
+	{"comment", 30},
+}
+
 func hasID3v1Tag(reader io.ReadSeeker) bool {
 	origin, err := reader.Seek(0, 1)
 	if err != nil {
 	return string(buf) == "TAG"
 }
 
-func parseID3v1File(reader io.ReadSeeker) (*SimpleTags, error) {
-	origin, err := reader.Seek(-128, 2)
+func readID3v1String(reader io.Reader, c int) (string, error) {
+	data, err := readBytes(reader, c)
 	if err != nil {
-		return nil, fmt.Errorf("seek failed")
+		return "", err
 	}
-	buf := bufio.NewReader(reader)
+	return strings.TrimRight(string(data), "\u0000"), nil
+}
 
-	header, err := readBytes(buf, 3)
+func parseID3v1File(reader io.ReadSeeker) (map[string]string, error) {
+	origin, err := reader.Seek(-128, 2)
 	if err != nil {
-		return nil, fmt.Errorf("read error")
-	}
-	if string(header) != "TAG" {
-		return nil, fmt.Errorf("ID3v1 tag not found")
+		return nil, fmt.Errorf("seek failed")
 	}
-	tags := new(SimpleTags)
 
-	data, err := readBytes(buf, 30)
-	if err != nil {
-		return nil, fmt.Errorf("read error")
+	// verify tag header
+	header, err := readID3v1String(reader, 3)
+	if err != nil || header != "TAG" {
+		return nil, fmt.Errorf("could not parse ID3v1 tag")
 	}
-	tags.Title = strings.TrimRight(string(data), "\u0000")
 
-	data, err = readBytes(buf, 30)
-	if err != nil {
-		return nil, fmt.Errorf("read error")
-	}
-	tags.Artist = strings.TrimRight(string(data), "\u0000")
+	tags := map[string]string{}
 
-	data, err = readBytes(buf, 30)
-	if err != nil {
-		return nil, fmt.Errorf("read error")
+	// parse simple string frames
+	for _, v := range ID3v1Frames {
+		str, err := readID3v1String(reader, v.length)
+		if err != nil {
+			return nil, fmt.Errorf("read error")
+		}
+		tags[v.name] = str
 	}
-	tags.Album = strings.TrimRight(string(data), "\u0000")
 
-	data, err = readBytes(buf, 4)
+	// parse track number (if present)
+	_, err = reader.Seek(-2, 1)
 	if err != nil {
-		return nil, fmt.Errorf("read error")
+		return nil, fmt.Errorf("seek error")
 	}
-	tags.Year = strings.TrimRight(string(data), "\u0000")
-
-	data, err = readBytes(buf, 30)
+	data, err := readBytes(reader, 2)
 	if err != nil {
 		return nil, fmt.Errorf("read error")
 	}
-	if data[28] == 0 {
-		tags.Track = fmt.Sprint(data[29])
+	if data[0] == 0 {
+		tags["track"] = fmt.Sprint(data[1])
 	}
 
-	data, err = readBytes(buf, 1)
+	// parse genre
+	data, err = readBytes(reader, 1)
 	if err != nil {
 		return nil, fmt.Errorf("read error")
 	}
 	if int(data[0]) > len(id3v1Genres) {
-		tags.Genre = "Unspecified"
+		tags["genre"] = "Unspecified"
 	} else {
-		tags.Genre = id3v1Genres[int(data[0])]
+		tags["genre"] = id3v1Genres[int(data[0])]
 	}
+
 	reader.Seek(origin, 0)
 	return tags, nil
 }

File id3/id3v2.go

 	Size              int32
 }
 
-func parseID3v2File(reader *bufio.Reader) (*SimpleTags, error) {
+func parseID3v2File(reader *bufio.Reader) (map[string]string, error) {
 	var parseSize func(*bufio.Reader) (int, error)
 	var tagMap map[string]string
 	var tagLen int
 		return nil, fmt.Errorf("Unrecognized ID3v2 version: %d", header.Version)
 	}
 
-	tags := new(SimpleTags)
+	tags := map[string]string{}
 	lreader := bufio.NewReader(io.LimitReader(reader, int64(header.Size)))
 	for hasID3v2Frame(lreader, tagLen) {
 		b, err := readBytes(lreader, tagLen)
 		if ok != true {
 			// skip over unknown tags
 			skipBytes(lreader, size)
+			continue
 		}
-
-		switch id {
-		case "album":
-			tags.Album = readID3v2String(lreader, size)
-		case "track":
-			tags.Track = readID3v2String(lreader, size)
-		case "artist":
-			tags.Artist = readID3v2String(lreader, size)
-		case "title":
-			tags.Title = readID3v2String(lreader, size)
-		case "year":
-			tags.Year = readID3v2String(lreader, size)
-		case "disc":
-			tags.Disc = readID3v2String(lreader, size)
-		case "genre":
-			tags.Genre = readID3v2Genre(lreader, size)
-		case "length":
-			tags.Length = readID3v2String(lreader, size)
+		if id == "genre" {
+			tags[id] = readID3v2Genre(lreader, size)
+		} else {
+			tags[id] = readID3v2String(lreader, size)
 		}
 	}
 	return tags, nil
 import (
 	"bufio"
 	"fmt"
+	"io"
 )
 
 var skipBuffer []byte = make([]byte, 1024*4)
 	return s
 }
 
-func readBytes(reader *bufio.Reader, c int) ([]byte, error) {
+func readBytes(reader io.Reader, c int) ([]byte, error) {
 	b := make([]byte, c)
 
 	n, err := reader.Read(b)

File tagreader/main.go

 	}
 
 	fmt.Println(path)
-	fmt.Printf("Title\t%s\n", tags.Title)
-	fmt.Printf("Artist\t%s\n", tags.Artist)
-	fmt.Printf("Album\t%s\n", tags.Album)
-	fmt.Printf("Year\t%s\n", tags.Year)
-	fmt.Printf("Track\t%s\n", tags.Track)
-	fmt.Printf("Disc\t%s\n", tags.Disc)
-	fmt.Printf("Genre\t%s\n", tags.Genre)
-	fmt.Printf("Length\t%s\n", tags.Length)
+	fmt.Printf("Title\t%s\n", tags["title"])
+	fmt.Printf("Artist\t%s\n", tags["artist"])
+	fmt.Printf("Album\t%s\n", tags["album"])
+	fmt.Printf("Year\t%s\n", tags["year"])
+	fmt.Printf("Track\t%s\n", tags["track"])
+	fmt.Printf("Disc\t%s\n", tags["disc"])
+	fmt.Printf("Genre\t%s\n", tags["genre"])
+	fmt.Printf("Length\t%s\n", tags["length"])
 	fmt.Println()
 }