From 0f62dc116ab9031cff5911bb63697b0171403cf8 Mon Sep 17 00:00:00 2001 From: Xavier Henner Date: Mon, 18 May 2015 09:32:54 +0200 Subject: [PATCH] Add support for lyrics. Not supported in id3v1 and id3v22 --- flac.go | 4 ++++ id3v1.go | 1 + id3v2.go | 7 +++++++ id3v2frames.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ id3v2metadata.go | 9 +++++++++ mp4.go | 8 ++++++++ tag.go | 5 ++++- 7 files changed, 77 insertions(+), 1 deletion(-) diff --git a/flac.go b/flac.go index defcf9a..bd1c831 100644 --- a/flac.go +++ b/flac.go @@ -288,6 +288,10 @@ func (m *metadataFLAC) Disc() (int, int) { return x, n } +func (m *metadataFLAC) Lyrics() string { + return m.c["lyrics"] +} + func (m *metadataFLAC) Picture() *Picture { return m.p } diff --git a/id3v1.go b/id3v1.go index 2243e84..09bed90 100644 --- a/id3v1.go +++ b/id3v1.go @@ -134,3 +134,4 @@ func (m metadataID3v1) AlbumArtist() string { return "" } 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 "" } diff --git a/id3v2.go b/id3v2.go index cd63f33..a2af714 100644 --- a/id3v2.go +++ b/id3v2.go @@ -218,6 +218,13 @@ func readID3v2Frames(r io.Reader, h *ID3v2Header) (map[string]interface{}, error } result[rawName] = txt + case name == "COMM" || name == "USLT": + t, err := readTextWithDescrFrame(b) + if err != nil { + return nil, err + } + result[rawName] = t + case name == "APIC": p, err := readAPICFrame(b) if err != nil { diff --git a/id3v2frames.go b/id3v2frames.go index 389017c..9876dfd 100644 --- a/id3v2frames.go +++ b/id3v2frames.go @@ -81,6 +81,21 @@ func decodeUTF16(b []byte, bo binary.ByteOrder) string { return string(utf16.Decode(s)) } +// Comm is a type used in COMM and USLT tag. It's a text with a description and +// a specified language + +type Comm struct { + Language string + Description string + Text string +} + +// String returns a string representation of the underlying Comm instance. +func (t Comm) String() string { + return fmt.Sprintf("Text{Lang: '%v', Description: '%v', %v lines}", + t.Language, t.Description, strings.Count(t.Text, "\n")) +} + var pictureTypes = map[byte]string{ 0x00: "Other", 0x01: "32x32 pixels 'file icon' (PNG only)", @@ -158,6 +173,35 @@ func readPICFrame(b []byte) (*Picture, error) { }, nil } +// IDv2.{3,4} +// -- Header +//
+//
+// -- readTextWithDescrFrame +// Text encoding $xx +// Language $xx xx xx +// Content descriptor $00 (00) +// Lyrics/text +func readTextWithDescrFrame(b []byte) (*Comm, error) { + enc := b[0] + descTextSplit := bytes.SplitN(b[4:], []byte{0}, 2) + desc, err := decodeText(enc, descTextSplit[0]) + if err != nil { + return nil, fmt.Errorf("error decoding tag description text: %v", err) + } + + text, err := decodeText(enc, descTextSplit[1]) + if err != nil { + return nil, fmt.Errorf("error decoding tag description text: %v", err) + } + + return &Comm{ + Language: string(b[1:4]), + Description: desc, + Text: text, + }, nil +} + // IDv2.{3,4} // -- Header //
diff --git a/id3v2metadata.go b/id3v2metadata.go index 7b8d29e..cf0f122 100644 --- a/id3v2metadata.go +++ b/id3v2metadata.go @@ -37,6 +37,7 @@ var frames = frameNames(map[string][2]string{ "disc": [2]string{"TPA", "TPOS"}, "genre": [2]string{"TCO", "TCON"}, "picture": [2]string{"PIC", "APIC"}, + "lyrics": [2]string{"", "USLT"}, }) // metadataID3v2 is the implementation of Metadata used for ID3v2 tags. @@ -112,6 +113,14 @@ func (m metadataID3v2) Disc() (int, int) { return parseXofN(m.getString(frames.Name("disc", m.Format()))) } +func (m metadataID3v2) Lyrics() string { + t, ok := m.frames[frames.Name("lyrics", m.Format())] + if !ok { + return "" + } + return t.(*Comm).Text +} + func (m metadataID3v2) Picture() *Picture { v, ok := m.frames[frames.Name("picture", m.Format())] if !ok { diff --git a/mp4.go b/mp4.go index c83275c..52279c7 100644 --- a/mp4.go +++ b/mp4.go @@ -222,6 +222,14 @@ func (m metadataMP4) Disc() (int, int) { return x, 0 } +func (m metadataMP4) Lyrics() string { + t, ok := m["\xa9lyr"] + if !ok { + return "" + } + return t.(string) +} + func (m metadataMP4) Picture() *Picture { v, ok := m["covr"] if !ok { diff --git a/tag.go b/tag.go index fe37411..57a15a1 100644 --- a/tag.go +++ b/tag.go @@ -87,9 +87,12 @@ type Metadata interface { // Disc returns the disc number and total discs, or zero values if unavailable. Disc() (int, int) - // Picture returns a picture, or nil if not avilable. + // Picture returns a picture, or nil if not available. Picture() *Picture + // Lyrics returns the lyrics, or an empty string if unavailable. + Lyrics() 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{}