From 897fed123f2dda8df584ec23e38eb4189b1f3d9d Mon Sep 17 00:00:00 2001 From: Nate Randa Date: Sun, 27 Apr 2025 18:42:43 -0400 Subject: [PATCH 1/5] add ID3-appended WAVE support --- tag.go | 20 ++++++++++----- wav.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 wav.go diff --git a/tag.go b/tag.go index 306f1d7..86b4bf7 100644 --- a/tag.go +++ b/tag.go @@ -6,12 +6,13 @@ // parsing and artwork extraction. // // Detect and parse tag metadata from an io.ReadSeeker (i.e. an *os.File): -// m, err := tag.ReadFrom(f) -// if err != nil { -// log.Fatal(err) -// } -// log.Print(m.Format()) // The detected format. -// log.Print(m.Title()) // The title of the track (see Metadata interface for more details). +// +// m, err := tag.ReadFrom(f) +// if err != nil { +// log.Fatal(err) +// } +// log.Print(m.Format()) // The detected format. +// log.Print(m.Title()) // The title of the track (see Metadata interface for more details). package tag import ( @@ -53,6 +54,13 @@ func ReadFrom(r io.ReadSeeker) (Metadata, error) { case string(b[0:4]) == "DSD ": return ReadDSFTags(r) + + case string(b[0:4]) == "RIFF": + err := setWavOffset(r) + if err != nil { + return nil, err + } + return ReadID3v2Tags(r) } m, err := ReadID3v1Tags(r) diff --git a/wav.go b/wav.go new file mode 100644 index 0000000..e187a28 --- /dev/null +++ b/wav.go @@ -0,0 +1,78 @@ +package tag + +import ( + "fmt" + "io" +) + +func setWavOffset(r io.ReadSeeker) error { + // verify RIFF format + str, err := readString(r, 4) + if err != nil { + return err + } + if str != "RIFF" { + return fmt.Errorf("format %v does not match expected 'RIFF'", str) + } + + // verify WAVE format + _, err = r.Seek(4, io.SeekCurrent) + if err != nil { + return err + } + str, err = readString(r, 4) + if err != nil { + return err + } + if str != "WAVE" { + return fmt.Errorf("filetype %v does not match exptected 'WAVE'", str) + } + + // identify chunk length + _, err = r.Seek(24, io.SeekCurrent) // 24-byte data format chunk is unneeded + if err != nil { + return err + } + str, err = readString(r, 4) + if err != nil { + return err + } + if str != "data" { + return fmt.Errorf("identifier %v does not match expected 'data'", err) + } + dataSize, err := readUint32LittleEndian(r) + if err != nil { + return err + } + + _, err = r.Seek(int64(dataSize), io.SeekCurrent) + if err != nil { + return err + } + + // skip past 8-byte Serato-specific identifier & size + // keep in mind, no documentation for this + str, err = readString(r, 3) + if err != nil { + return err + } + r.Seek(-3, io.SeekCurrent) + if str == "id3" { + _, err = r.Seek(8, io.SeekCurrent) + if err != nil { + return err + } + } + + // verify that ID3 was reached + str, err = readString(r, 3) + if err != nil { + return err + } + r.Seek(-3, io.SeekCurrent) + if str == "ID3" { + return nil + } else { + return ErrNoTagsFound + } +} From 3bcd14f131edd7448c64ff71ff46bf88b1c0029a Mon Sep 17 00:00:00 2001 From: Nate Randa Date: Sun, 27 Apr 2025 19:02:23 -0400 Subject: [PATCH 2/5] add WAV filetype/format identification --- id.go | 38 ++++++++++++++++++++++++++++++++++++-- tag.go | 1 + wav.go | 12 +----------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/id.go b/id.go index 2410356..0105ea1 100644 --- a/id.go +++ b/id.go @@ -7,12 +7,12 @@ import ( // Identify identifies the format and file type of the data in the ReadSeeker. func Identify(r io.ReadSeeker) (format Format, fileType FileType, err error) { - b, err := readBytes(r, 11) + b, err := readBytes(r, 12) if err != nil { return } - _, err = r.Seek(-11, io.SeekCurrent) + _, err = r.Seek(-12, io.SeekCurrent) if err != nil { err = fmt.Errorf("could not seek back to original position: %v", err) return @@ -56,6 +56,40 @@ func Identify(r io.ReadSeeker) (format Format, fileType FileType, err error) { return } return format, MP3, nil + case string(b[0:4]) == "RIFF" && string(b[8:12]) == "WAVE": + format = UnknownFormat + err = setWavOffset(r) + if err != nil { + return format, WAV, err + } + b, err = readBytes(r, 4) + if err != nil { + return + } + _, err = r.Seek(-4, io.SeekCurrent) + if err != nil { + err = fmt.Errorf("could not seek back to original position: %v", err) + return + } + switch { + case string(b[0:3]) == "ID3": + b := b[3:] + switch uint(b[0]) { + case 2: + format = ID3v2_2 + case 3: + format = ID3v2_3 + case 4: + format = ID3v2_4 + case 0, 1: + fallthrough + default: + err = fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0])) + return + } + } + return format, WAV, nil + } n, err := r.Seek(-128, io.SeekEnd) diff --git a/tag.go b/tag.go index 86b4bf7..efd3344 100644 --- a/tag.go +++ b/tag.go @@ -103,6 +103,7 @@ const ( 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 + WAV FileType = "WAV" // WAVE file ) // Metadata is an interface which is used to describe metadata retrieved by this package. diff --git a/wav.go b/wav.go index e187a28..e1d4c73 100644 --- a/wav.go +++ b/wav.go @@ -64,15 +64,5 @@ func setWavOffset(r io.ReadSeeker) error { } } - // verify that ID3 was reached - str, err = readString(r, 3) - if err != nil { - return err - } - r.Seek(-3, io.SeekCurrent) - if str == "ID3" { - return nil - } else { - return ErrNoTagsFound - } + return nil } From 0e7a9496bd8d10ae9377f323b1fdd28529b4505a Mon Sep 17 00:00:00 2001 From: Nate Randa Date: Sun, 27 Apr 2025 19:03:30 -0400 Subject: [PATCH 3/5] fix spacing --- id.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/id.go b/id.go index 0105ea1..07c8967 100644 --- a/id.go +++ b/id.go @@ -56,6 +56,7 @@ func Identify(r io.ReadSeeker) (format Format, fileType FileType, err error) { return } return format, MP3, nil + case string(b[0:4]) == "RIFF" && string(b[8:12]) == "WAVE": format = UnknownFormat err = setWavOffset(r) @@ -89,7 +90,6 @@ func Identify(r io.ReadSeeker) (format Format, fileType FileType, err error) { } } return format, WAV, nil - } n, err := r.Seek(-128, io.SeekEnd) From 7ecd532f643e70f83c007612413cc5efd3bd07fb Mon Sep 17 00:00:00 2001 From: Nate Randa Date: Sun, 27 Apr 2025 19:23:51 -0400 Subject: [PATCH 4/5] clarify RIFF chunk header --- wav.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/wav.go b/wav.go index e1d4c73..16cc609 100644 --- a/wav.go +++ b/wav.go @@ -6,16 +6,16 @@ import ( ) func setWavOffset(r io.ReadSeeker) error { - // verify RIFF format + // verify RIFF chunk str, err := readString(r, 4) if err != nil { return err } if str != "RIFF" { - return fmt.Errorf("format %v does not match expected 'RIFF'", str) + return fmt.Errorf("chunk header %v does not match expected 'RIFF'", str) } - // verify WAVE format + // verify WAVE filetype _, err = r.Seek(4, io.SeekCurrent) if err != nil { return err @@ -50,19 +50,13 @@ func setWavOffset(r io.ReadSeeker) error { return err } - // skip past 8-byte Serato-specific identifier & size - // keep in mind, no documentation for this - str, err = readString(r, 3) + // skip unneeded 8-byte RIFF chunk header (4-byte ASCII identifier + // and 4-byte little-endian uint32 chunk size), more info: + // https://en.wikipedia.org/wiki/Resource_Interchange_File_Format#Explanation + _, err = r.Seek(8, io.SeekCurrent) if err != nil { return err } - r.Seek(-3, io.SeekCurrent) - if str == "id3" { - _, err = r.Seek(8, io.SeekCurrent) - if err != nil { - return err - } - } return nil } From 0e097121889f83743b703f48eebee69097a9d83c Mon Sep 17 00:00:00 2001 From: Nate Randa Date: Sun, 27 Apr 2025 19:31:26 -0400 Subject: [PATCH 5/5] add metadata-appended WAVE support --- id.go | 30 +++--------------------------- tag.go | 3 ++- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/id.go b/id.go index 07c8967..f3efa45 100644 --- a/id.go +++ b/id.go @@ -63,33 +63,9 @@ func Identify(r io.ReadSeeker) (format Format, fileType FileType, err error) { if err != nil { return format, WAV, err } - b, err = readBytes(r, 4) - if err != nil { - return - } - _, err = r.Seek(-4, io.SeekCurrent) - if err != nil { - err = fmt.Errorf("could not seek back to original position: %v", err) - return - } - switch { - case string(b[0:3]) == "ID3": - b := b[3:] - switch uint(b[0]) { - case 2: - format = ID3v2_2 - case 3: - format = ID3v2_3 - case 4: - format = ID3v2_4 - case 0, 1: - fallthrough - default: - err = fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0])) - return - } - } - return format, WAV, nil + // call Identify() again, replacing whatever fileType it finds with WAV + format, _, err = Identify(r) + return format, WAV, err } n, err := r.Seek(-128, io.SeekEnd) diff --git a/tag.go b/tag.go index efd3344..7fc6a63 100644 --- a/tag.go +++ b/tag.go @@ -60,7 +60,8 @@ func ReadFrom(r io.ReadSeeker) (Metadata, error) { if err != nil { return nil, err } - return ReadID3v2Tags(r) + // call ReadFrom() again at the new offset + return ReadFrom(r) } m, err := ReadID3v1Tags(r)