2015-03-19 13:21:53 +01:00
|
|
|
// 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 (
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type frameNames map[string][2]string
|
|
|
|
|
|
|
|
func (f frameNames) Name(s string, fm Format) string {
|
|
|
|
l, ok := f[s]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
switch fm {
|
|
|
|
case ID3v2_2:
|
|
|
|
return l[0]
|
2017-09-14 00:43:05 +02:00
|
|
|
case ID3v2_3:
|
|
|
|
return l[1]
|
|
|
|
case ID3v2_4:
|
|
|
|
if s == "year" {
|
|
|
|
return "TDRC"
|
|
|
|
}
|
2015-03-19 13:21:53 +01:00
|
|
|
return l[1]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
var frames = frameNames(map[string][2]string{
|
|
|
|
"title": [2]string{"TT2", "TIT2"},
|
|
|
|
"artist": [2]string{"TP1", "TPE1"},
|
|
|
|
"album": [2]string{"TAL", "TALB"},
|
|
|
|
"album_artist": [2]string{"TP2", "TPE2"},
|
|
|
|
"composer": [2]string{"TCM", "TCOM"},
|
|
|
|
"year": [2]string{"TYE", "TYER"},
|
|
|
|
"track": [2]string{"TRK", "TRCK"},
|
|
|
|
"disc": [2]string{"TPA", "TPOS"},
|
|
|
|
"genre": [2]string{"TCO", "TCON"},
|
|
|
|
"picture": [2]string{"PIC", "APIC"},
|
2015-05-18 09:32:54 +02:00
|
|
|
"lyrics": [2]string{"", "USLT"},
|
2018-11-04 21:56:00 +01:00
|
|
|
"comment": [2]string{"COM", "COMM"},
|
2015-03-19 13:21:53 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
// metadataID3v2 is the implementation of Metadata used for ID3v2 tags.
|
|
|
|
type metadataID3v2 struct {
|
2015-06-28 04:40:49 +02:00
|
|
|
header *id3v2Header
|
2015-03-19 13:21:53 +01:00
|
|
|
frames map[string]interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) getString(k string) string {
|
|
|
|
v, ok := m.frames[k]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return v.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Format() Format { return m.header.Version }
|
2015-05-24 02:44:45 +02:00
|
|
|
func (m metadataID3v2) FileType() FileType { return MP3 }
|
2015-03-19 13:21:53 +01:00
|
|
|
func (m metadataID3v2) Raw() map[string]interface{} { return m.frames }
|
|
|
|
|
|
|
|
func (m metadataID3v2) Title() string {
|
|
|
|
return m.getString(frames.Name("title", m.Format()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Artist() string {
|
|
|
|
return m.getString(frames.Name("artist", m.Format()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Album() string {
|
|
|
|
return m.getString(frames.Name("album", m.Format()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) AlbumArtist() string {
|
|
|
|
return m.getString(frames.Name("album_artist", m.Format()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Composer() string {
|
|
|
|
return m.getString(frames.Name("composer", m.Format()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Genre() string {
|
Support for numeric genres in id3v2
TCON
The 'Content type', which previously was stored as a one byte numeric value
only, is now a numeric string. You may use one or several of the types as
ID3v1.1 did or, since the category list would be impossible to maintain with
accurate and up to date categories, define your own.
References to the ID3v1 genres can be made by, as first byte, enter "("
followed by a number from the genres list (appendix A) and ended with a ")"
character. This is optionally followed by a refinement, e.g. "(21)" or
"(4)Eurodisco". Several references can be made in the same frame, e.g.
"(51)(39)". If the refinement should begin with a "(" character it should be
replaced with "((", e.g. "((I can figure out any genre)" or "(55)((I
think...)". The following new content types is defined in ID3v2 and is
implemented in the same way as the numerig content types, e.g. "(RX)".
To test it, use the id3v2 tool
% id3v2 -g 79 test.mp3
% id3v2 -l test.mp3| grep TCON
TCON (Content type): Hard Rock (79)
% ./tag test.mp3| grep Genre
Genre: (79)
With the patch :
% go build && ./tag test.mp3| grep Genre
Genre: Hard Rock
2015-07-04 14:17:53 +02:00
|
|
|
return id3v2genre(m.getString(frames.Name("genre", m.Format())))
|
2015-03-19 13:21:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Year() int {
|
|
|
|
year, _ := strconv.Atoi(m.getString(frames.Name("year", m.Format())))
|
|
|
|
return year
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseXofN(s string) (x, n int) {
|
|
|
|
xn := strings.Split(s, "/")
|
|
|
|
if len(xn) != 2 {
|
|
|
|
x, _ = strconv.Atoi(s)
|
|
|
|
return x, 0
|
|
|
|
}
|
|
|
|
x, _ = strconv.Atoi(strings.TrimSpace(xn[0]))
|
|
|
|
n, _ = strconv.Atoi(strings.TrimSpace(xn[1]))
|
|
|
|
return x, n
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Track() (int, int) {
|
|
|
|
return parseXofN(m.getString(frames.Name("track", m.Format())))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m metadataID3v2) Disc() (int, int) {
|
|
|
|
return parseXofN(m.getString(frames.Name("disc", m.Format())))
|
|
|
|
}
|
|
|
|
|
2015-05-18 09:32:54 +02:00
|
|
|
func (m metadataID3v2) Lyrics() string {
|
|
|
|
t, ok := m.frames[frames.Name("lyrics", m.Format())]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return t.(*Comm).Text
|
|
|
|
}
|
|
|
|
|
2018-11-04 21:56:00 +01:00
|
|
|
func (m metadataID3v2) Comment() string {
|
|
|
|
t, ok := m.frames[frames.Name("comment", m.Format())]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
// id3v23 has Text, id3v24 has Description
|
|
|
|
if t.(*Comm).Description == "" {
|
|
|
|
return trimString(t.(*Comm).Text)
|
|
|
|
}
|
|
|
|
return trimString(t.(*Comm).Description)
|
|
|
|
}
|
|
|
|
|
2015-03-19 13:21:53 +01:00
|
|
|
func (m metadataID3v2) Picture() *Picture {
|
|
|
|
v, ok := m.frames[frames.Name("picture", m.Format())]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return v.(*Picture)
|
|
|
|
}
|