Initial commit
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
// 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 (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var atomTypes = map[int]string{
|
||||
0: "uint8",
|
||||
1: "text",
|
||||
13: "jpeg",
|
||||
14: "png",
|
||||
21: "uint8",
|
||||
}
|
||||
|
||||
var atoms = atomNames(map[string]string{
|
||||
"\xa9alb": "album",
|
||||
"\xa9art": "artist",
|
||||
"\xa9ART": "artist",
|
||||
"aART": "album_artist",
|
||||
"\xa9day": "year",
|
||||
"\xa9nam": "title",
|
||||
"\xa9gen": "genre",
|
||||
"trkn": "track",
|
||||
"\xa9wrt": "composer",
|
||||
"\xa9too": "encoder",
|
||||
"cprt": "copyright",
|
||||
"covr": "picture",
|
||||
"\xa9grp": "grouping",
|
||||
"keyw": "keyword",
|
||||
"\xa9lyr": "lyrics",
|
||||
"\xa9cmt": "comment",
|
||||
"tmpo": "tempo",
|
||||
"cpil": "compilation",
|
||||
"disk": "disc",
|
||||
})
|
||||
|
||||
type atomNames map[string]string
|
||||
|
||||
func (f atomNames) Name(n string) []string {
|
||||
res := make([]string, 1)
|
||||
for k, v := range f {
|
||||
if v == n {
|
||||
res = append(res, k)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// metadataMP4 is the implementation of Metadata for MP4 tag (atom) data.
|
||||
type metadataMP4 map[string]interface{}
|
||||
|
||||
// ReadAtoms reads MP4 metadata atoms from the reader into a Metadata, returning non-nil
|
||||
// error if there was a problem.
|
||||
func ReadAtoms(r io.Reader) (Metadata, error) {
|
||||
m := make(metadataMP4)
|
||||
err := m.readAtoms(r)
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (m metadataMP4) readAtoms(r io.Reader) error {
|
||||
for {
|
||||
var size uint32
|
||||
err := binary.Read(r, binary.BigEndian, &size)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
name, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "meta":
|
||||
// next_item_id (int32)
|
||||
_, err := readBytes(r, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fallthrough
|
||||
case "moov", "udta", "ilst":
|
||||
return m.readAtoms(r)
|
||||
case "free":
|
||||
discardN(r, int64(size-8))
|
||||
continue
|
||||
case "mdat": // stop when we get to the data
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := readBytes(r, int(size-8))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ok := atoms[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// 16: name + size + "data" + size (4 bytes each), have already read 8
|
||||
b = b[8:]
|
||||
class := getInt(b[1:4])
|
||||
contentType, ok := atomTypes[class]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid content type: %v", class)
|
||||
}
|
||||
|
||||
b = b[8:]
|
||||
switch name {
|
||||
case "trkn", "disk":
|
||||
m[name] = int(b[3])
|
||||
m[name+"_count"] = int(b[5])
|
||||
default:
|
||||
var data interface{}
|
||||
// 4: atom version (1 byte) + atom flags (3 bytes)
|
||||
// 4: NULL (usually locale indicator)
|
||||
switch contentType {
|
||||
case "text":
|
||||
data = string(b)
|
||||
|
||||
case "uint8":
|
||||
data = getInt(b[:1])
|
||||
|
||||
case "jpeg", "png":
|
||||
data = &Picture{
|
||||
Ext: contentType,
|
||||
MIMEType: "image/" + contentType,
|
||||
Data: b,
|
||||
}
|
||||
}
|
||||
m[name] = data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (metadataMP4) Format() Format { return MP4 }
|
||||
|
||||
func (m metadataMP4) Raw() map[string]interface{} { return m }
|
||||
|
||||
func (m metadataMP4) getString(n []string) string {
|
||||
for _, k := range n {
|
||||
if x, ok := m[k]; ok {
|
||||
return x.(string)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m metadataMP4) getInt(n []string) int {
|
||||
for _, k := range n {
|
||||
if x, ok := m[k]; ok {
|
||||
return x.(int)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Title() string {
|
||||
return m.getString(atoms.Name("title"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Artist() string {
|
||||
return m.getString(atoms.Name("artist"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Album() string {
|
||||
return m.getString(atoms.Name("album"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) AlbumArtist() string {
|
||||
return m.getString(atoms.Name("album_artist"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Composer() string {
|
||||
return m.getString(atoms.Name("composer"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Genre() string {
|
||||
return m.getString(atoms.Name("genre"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Year() int {
|
||||
date := m.getString(atoms.Name("year"))
|
||||
if len(date) >= 4 {
|
||||
year, _ := strconv.Atoi(date[:4])
|
||||
return year
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Track() (int, int) {
|
||||
x := m.getInt([]string{"trkn"})
|
||||
if n, ok := m["trkn_count"]; ok {
|
||||
return x, n.(int)
|
||||
}
|
||||
return x, 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Disc() (int, int) {
|
||||
x := m.getInt([]string{"disk"})
|
||||
if n, ok := m["disk_count"]; ok {
|
||||
return x, n.(int)
|
||||
}
|
||||
return x, 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Picture() *Picture {
|
||||
v, ok := m["covr"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return v.(*Picture)
|
||||
}
|
||||
Reference in New Issue
Block a user