Merge branch 'master' of https://github.com/jooola/tag into enhance_testing
This commit is contained in:
commit
b00b4fe75f
@ -36,6 +36,7 @@ type Metadata interface {
|
||||
|
||||
Picture() *Picture // Artwork
|
||||
Lyrics() string
|
||||
Comment() string
|
||||
|
||||
Raw() map[string]interface{} // NB: raw tag names are not consistent across formats.
|
||||
}
|
||||
|
@ -95,4 +95,5 @@ func printMetadata(m tag.Metadata) {
|
||||
|
||||
fmt.Printf(" Picture: %v\n", m.Picture())
|
||||
fmt.Printf(" Lyrics: %v\n", m.Lyrics())
|
||||
fmt.Printf(" Comment: %v\n", m.Comment())
|
||||
}
|
||||
|
110
dsf.go
Normal file
110
dsf.go
Normal file
@ -0,0 +1,110 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ReadDSFTags reads DSF metadata from the io.ReadSeeker, returning the resulting
|
||||
// metadata in a Metadata implementation, or non-nil error if there was a problem.
|
||||
// samples: http://www.2l.no/hires/index.html
|
||||
func ReadDSFTags(r io.ReadSeeker) (Metadata, error) {
|
||||
dsd, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dsd != "DSD " {
|
||||
return nil, errors.New("expected 'DSD '")
|
||||
}
|
||||
|
||||
_, err = r.Seek(int64(16), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n4, err := readBytes(r, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id3Pointer := getIntLittleEndian(n4)
|
||||
|
||||
_, err = r.Seek(int64(id3Pointer), io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id3, err := ReadID3v2Tags(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return metadataDSF{id3}, nil
|
||||
}
|
||||
|
||||
type metadataDSF struct {
|
||||
id3 Metadata
|
||||
}
|
||||
|
||||
func (m metadataDSF) Format() Format {
|
||||
return m.id3.Format()
|
||||
}
|
||||
|
||||
func (m metadataDSF) FileType() FileType {
|
||||
return DSF
|
||||
}
|
||||
|
||||
func (m metadataDSF) Title() string {
|
||||
return m.id3.Title()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Album() string {
|
||||
return m.id3.Album()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Artist() string {
|
||||
return m.id3.Artist()
|
||||
}
|
||||
|
||||
func (m metadataDSF) AlbumArtist() string {
|
||||
return m.id3.AlbumArtist()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Composer() string {
|
||||
return m.id3.Composer()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Year() int {
|
||||
return m.id3.Year()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Genre() string {
|
||||
return m.id3.Genre()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Track() (int, int) {
|
||||
return m.id3.Track()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Disc() (int, int) {
|
||||
return m.id3.Disc()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Picture() *Picture {
|
||||
return m.id3.Picture()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Lyrics() string {
|
||||
return m.id3.Lyrics()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Comment() string {
|
||||
return m.id3.Comment()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Raw() map[string]interface{} {
|
||||
return m.id3.Raw()
|
||||
}
|
1
id3v1.go
1
id3v1.go
@ -141,3 +141,4 @@ func (m metadataID3v1) Composer() string { return "" }
|
||||
func (metadataID3v1) Disc() (int, int) { return 0, 0 }
|
||||
func (m metadataID3v1) Picture() *Picture { return nil }
|
||||
func (m metadataID3v1) Lyrics() string { return "" }
|
||||
func (m metadataID3v1) Comment() string { return m["comment"].(string) }
|
||||
|
@ -43,6 +43,7 @@ var frames = frameNames(map[string][2]string{
|
||||
"genre": [2]string{"TCO", "TCON"},
|
||||
"picture": [2]string{"PIC", "APIC"},
|
||||
"lyrics": [2]string{"", "USLT"},
|
||||
"comment": [2]string{"COM", "COMM"},
|
||||
})
|
||||
|
||||
// metadataID3v2 is the implementation of Metadata used for ID3v2 tags.
|
||||
@ -119,6 +120,18 @@ func (m metadataID3v2) Lyrics() string {
|
||||
return t.(*Comm).Text
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Picture() *Picture {
|
||||
v, ok := m.frames[frames.Name("picture", m.Format())]
|
||||
if !ok {
|
||||
|
8
mp4.go
8
mp4.go
@ -344,6 +344,14 @@ func (m metadataMP4) Lyrics() string {
|
||||
return t.(string)
|
||||
}
|
||||
|
||||
func (m metadataMP4) Comment() string {
|
||||
t, ok := m.data["\xa9cmt"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return t.(string)
|
||||
}
|
||||
|
||||
func (m metadataMP4) Picture() *Picture {
|
||||
v, ok := m.data["covr"]
|
||||
if !ok {
|
||||
|
7
tag.go
7
tag.go
@ -50,6 +50,9 @@ func ReadFrom(r io.ReadSeeker) (Metadata, error) {
|
||||
|
||||
case string(b[0:3]) == "ID3":
|
||||
return ReadID3v2Tags(r)
|
||||
|
||||
case string(b[0:4]) == "DSD ":
|
||||
return ReadDSFTags(r)
|
||||
}
|
||||
|
||||
m, err := ReadID3v1Tags(r)
|
||||
@ -91,6 +94,7 @@ const (
|
||||
ALAC FileType = "ALAC" // Apple Lossless file FIXME: actually detect this
|
||||
FLAC FileType = "FLAC" // FLAC file
|
||||
OGG FileType = "OGG" // OGG file
|
||||
DSF FileType = "DSF" // DSF file DSD Sony format see https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
|
||||
)
|
||||
|
||||
// Metadata is an interface which is used to describe metadata retrieved by this package.
|
||||
@ -134,6 +138,9 @@ type Metadata interface {
|
||||
// Lyrics returns the lyrics, or an empty string if unavailable.
|
||||
Lyrics() string
|
||||
|
||||
// Comment returns the comment, or an empty string if unavailable.
|
||||
Comment() string
|
||||
|
||||
// Raw returns the raw mapping of retrieved tag names and associated values.
|
||||
// NB: tag/atom names are not standardised between formats.
|
||||
Raw() map[string]interface{}
|
||||
|
17
tag_test.go
17
tag_test.go
@ -39,15 +39,17 @@ var fullMetadata = testMetadata{
|
||||
Track: 3,
|
||||
TrackTotal: 6,
|
||||
Year: 2000,
|
||||
Comment: "Test Comment",
|
||||
}
|
||||
var mp3id3v11Metadata = testMetadata{
|
||||
Album: "Test Album",
|
||||
Artist: "Test Artist",
|
||||
Genre: "Jazz",
|
||||
Lyrics: "",
|
||||
Title: "Test Title",
|
||||
Track: 3,
|
||||
Year: 2000,
|
||||
Album: "Test Album",
|
||||
Artist: "Test Artist",
|
||||
Genre: "Jazz",
|
||||
Lyrics: "",
|
||||
Title: "Test Title",
|
||||
Track: 3,
|
||||
Year: 2000,
|
||||
Comment: "Test Comment",
|
||||
}
|
||||
|
||||
type testData struct {
|
||||
@ -116,6 +118,7 @@ func compareMetadata(t *testing.T, m Metadata, tt testData) {
|
||||
testValue(t, tt.Lyrics, m.Lyrics())
|
||||
testValue(t, tt.Title, m.Title())
|
||||
testValue(t, tt.Year, m.Year())
|
||||
testValue(t, tt.Comment, m.Comment())
|
||||
|
||||
disc, discTotal := m.Disc()
|
||||
testValue(t, tt.Disc, disc)
|
||||
|
BIN
testdata/with_tags/sample.dsf
vendored
Normal file
BIN
testdata/with_tags/sample.dsf
vendored
Normal file
Binary file not shown.
BIN
testdata/with_tags/sample.id3v11.mp3
vendored
BIN
testdata/with_tags/sample.id3v11.mp3
vendored
Binary file not shown.
9
util.go
9
util.go
@ -32,6 +32,15 @@ func getInt(b []byte) int {
|
||||
return n
|
||||
}
|
||||
|
||||
func getIntLittleEndian(b []byte) int {
|
||||
var n int
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
n = n << 8
|
||||
n |= int(b[i])
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func readBytes(r io.Reader, n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := io.ReadFull(r, b)
|
||||
|
35
util_test.go
35
util_test.go
@ -79,3 +79,38 @@ func TestGetInt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIntLittleEndian(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []byte
|
||||
output int
|
||||
}{
|
||||
{
|
||||
[]byte{},
|
||||
0,
|
||||
},
|
||||
{
|
||||
[]byte{0x01},
|
||||
1,
|
||||
},
|
||||
{
|
||||
[]byte{0xF1, 0xF2},
|
||||
0xF2F1,
|
||||
},
|
||||
{
|
||||
[]byte{0xF1, 0xF2, 0xF3},
|
||||
0xF3F2F1,
|
||||
},
|
||||
{
|
||||
[]byte{0xF1, 0xF2, 0xF3, 0xF4},
|
||||
0xF4F3F2F1,
|
||||
},
|
||||
}
|
||||
|
||||
for ii, tt := range tests {
|
||||
got := getIntLittleEndian(tt.input)
|
||||
if got != tt.output {
|
||||
t.Errorf("[%d] getInt(%v) = %v, expected %v", ii, tt.input, got, tt.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user