diff --git a/dsf.go b/dsf.go new file mode 100644 index 0000000..d826a74 --- /dev/null +++ b/dsf.go @@ -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() +} diff --git a/tag.go b/tag.go index 3837682..306f1d7 100644 --- a/tag.go +++ b/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. diff --git a/tag_test.go b/tag_test.go index 043d995..3913e4e 100644 --- a/tag_test.go +++ b/tag_test.go @@ -62,12 +62,12 @@ func TestReadFrom(t *testing.T) { "with_tags/sample.m4a": fullMetadata, "with_tags/sample.mp4": fullMetadata, "with_tags/sample.ogg": fullMetadata, - - "without_tags/sample.flac": emptyMetadata, - "without_tags/sample.m4a": emptyMetadata, - "without_tags/sample.mp3": emptyMetadata, - "without_tags/sample.mp4": emptyMetadata, - "without_tags/sample.ogg": emptyMetadata, + "with_tags/sample.dsf": fullMetadata, + "without_tags/sample.flac": emptyMetadata, + "without_tags/sample.m4a": emptyMetadata, + "without_tags/sample.mp3": emptyMetadata, + "without_tags/sample.mp4": emptyMetadata, + "without_tags/sample.ogg": emptyMetadata, } for path, metadata := range testdata { diff --git a/testdata/with_tags/sample.dsf b/testdata/with_tags/sample.dsf new file mode 100644 index 0000000..8e8a603 Binary files /dev/null and b/testdata/with_tags/sample.dsf differ diff --git a/util.go b/util.go index ec28ac6..ff9c4f1 100644 --- a/util.go +++ b/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) diff --git a/util_test.go b/util_test.go index aae0bcd..a122fce 100644 --- a/util_test.go +++ b/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) + } + } +}