tag/id3v2.go
2015-04-27 22:55:59 +10:00

253 lines
5.3 KiB
Go

// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tag
import (
"fmt"
"io"
"os"
"strconv"
"strings"
)
// ID3v2Header is a type which represents an ID3v2 tag header.
type ID3v2Header struct {
Version Format
Unsynchronisation bool
ExtendedHeader bool
Experimental bool
Size int
}
// readID3v2Header reads the ID3v2 header from the given io.Reader.
func readID3v2Header(r io.Reader) (*ID3v2Header, error) {
b, err := readBytes(r, 10)
if err != nil {
return nil, fmt.Errorf("expected to read 10 bytes (ID3v2Header): %v", err)
}
if string(b[0:3]) != "ID3" {
return nil, fmt.Errorf("expected to read \"ID3\"")
}
b = b[3:]
var vers Format
switch uint(b[0]) {
case 2:
vers = ID3v2_2
case 3:
vers = ID3v2_3
case 4:
vers = ID3v2_4
case 0, 1:
fallthrough
default:
return nil, fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0]))
}
// NB: We ignore b[1] (the revision) as we don't currently rely on it.
return &ID3v2Header{
Version: vers,
Unsynchronisation: getBit(b[2], 7),
ExtendedHeader: getBit(b[2], 6),
Experimental: getBit(b[2], 5),
Size: get7BitChunkedInt(b[3:7]),
}, nil
}
// ID3v2FrameFlags is a type which represents the flags which can be set on an ID3v2 frame.
type ID3v2FrameFlags struct {
// Message
TagAlterPreservation bool
FileAlterPreservation bool
ReadOnly bool
// Format
GroupIdentity bool
Compression bool
Encryption bool
Unsynchronisation bool
DataLengthIndicator bool
}
func readID3v2FrameFlags(r io.Reader) (*ID3v2FrameFlags, error) {
b, err := readBytes(r, 2)
if err != nil {
return nil, err
}
msg := b[0]
fmt := b[1]
return &ID3v2FrameFlags{
TagAlterPreservation: getBit(msg, 6),
FileAlterPreservation: getBit(msg, 5),
ReadOnly: getBit(msg, 4),
GroupIdentity: getBit(fmt, 7),
Compression: getBit(fmt, 3),
Encryption: getBit(fmt, 2),
Unsynchronisation: getBit(fmt, 1),
DataLengthIndicator: getBit(fmt, 0),
}, nil
}
func readID3v2_2FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
name, err = readString(r, 3)
if err != nil {
return
}
size, err = readInt(r, 3)
if err != nil {
return
}
headerSize = 6
return
}
func readID3v2_3FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
name, err = readString(r, 4)
if err != nil {
return
}
size, err = readInt(r, 4)
if err != nil {
return
}
headerSize = 8
return
}
func readID3v2_4FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
name, err = readString(r, 4)
if err != nil {
return
}
size, err = read7BitChunkedInt(r, 4)
if err != nil {
return
}
headerSize = 8
return
}
// readID3v2Frames reads ID3v2 frames from the given reader using the ID3v2Header.
func readID3v2Frames(r io.Reader, h *ID3v2Header) (map[string]interface{}, error) {
offset := 10 // the size of the header
result := make(map[string]interface{})
for offset < h.Size {
var err error
var name string
var size, headerSize int
var flags *ID3v2FrameFlags
switch h.Version {
case ID3v2_2:
name, size, headerSize, err = readID3v2_2FrameHeader(r)
case ID3v2_3:
name, size, headerSize, err = readID3v2_3FrameHeader(r)
if err != nil {
return nil, err
}
flags, err = readID3v2FrameFlags(r)
headerSize += 2
case ID3v2_4:
name, size, headerSize, err = readID3v2_4FrameHeader(r)
if err != nil {
return nil, err
}
flags, err = readID3v2FrameFlags(r)
headerSize += 2
}
if err != nil {
return nil, err
}
// There can be multiple tag with the same name. Append a number to the
// name if there is more than one.
if _, ok := result[name]; ok {
orig := name
for i := 0; ok; i++ {
_, ok = result[orig+"_"+strconv.Itoa(i)]
name = orig + "_" + strconv.Itoa(i)
}
}
offset += headerSize + size
// Check this stuff out...
if flags != nil && flags.DataLengthIndicator {
_, err = read7BitChunkedInt(r, 4) // read 4
if err != nil {
return nil, err
}
size -= 4
}
if flags != nil && flags.Unsynchronisation {
// FIXME: Implement this.
continue
}
name = strings.TrimSpace(name)
if name == "" {
break
}
b, err := readBytes(r, size)
if err != nil {
return nil, err
}
switch {
case name[0] == 'T':
txt, err := readTFrame(b)
if err != nil {
return nil, err
}
result[name] = txt
case len(name) > 3 && name[0:4] == "APIC":
p, err := readAPICFrame(b)
if err != nil {
return nil, err
}
result[name] = p
case len(name) > 2 && name[0:3] == "PIC":
p, err := readPICFrame(b)
if err != nil {
return nil, err
}
result[name] = p
}
continue
}
return result, nil
}
// ReadID3v2Tags parses ID3v2.{2,3,4} tags from the io.ReadSeeker into a Metadata, returning
// non-nil error on failure.
func ReadID3v2Tags(r io.ReadSeeker) (Metadata, error) {
_, err := r.Seek(0, os.SEEK_SET)
if err != nil {
return nil, err
}
h, err := readID3v2Header(r)
if err != nil {
return nil, err
}
f, err := readID3v2Frames(r, h)
if err != nil {
return nil, err
}
return metadataID3v2{header: h, frames: f}, nil
}