add licence

This commit is contained in:
sentriz
2019-04-11 18:27:45 +01:00
parent ec53d04730
commit 76ad2ec4eb
25 changed files with 2978 additions and 20 deletions

View File

@@ -36,15 +36,19 @@ func main() {
cont.CheckParameters, cont.CheckParameters,
) )
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/rest/ping", withWare(cont.Ping))
mux.HandleFunc("/rest/ping.view", withWare(cont.Ping)) mux.HandleFunc("/rest/ping.view", withWare(cont.Ping))
mux.HandleFunc("/rest/getIndexes.view", withWare(cont.GetIndexes)) mux.HandleFunc("/rest/stream", withWare(cont.Stream))
mux.HandleFunc("/rest/getMusicDirectory.view", withWare(cont.GetMusicDirectory))
mux.HandleFunc("/rest/getCoverArt.view", withWare(cont.GetCoverArt))
mux.HandleFunc("/rest/stream.view", withWare(cont.Stream)) mux.HandleFunc("/rest/stream.view", withWare(cont.Stream))
mux.HandleFunc("/rest/getMusicFolders.view", withWare(cont.GetMusicFolders)) mux.HandleFunc("/rest/getMusicDirectory", withWare(cont.GetMusicDirectory))
mux.HandleFunc("/rest/getPlaylists.view", withWare(cont.GetPlaylists)) mux.HandleFunc("/rest/getMusicDirectory.view", withWare(cont.GetMusicDirectory))
mux.HandleFunc("/rest/getGenres.view", withWare(cont.GetGenres)) mux.HandleFunc("/rest/getCoverArt", withWare(cont.GetCoverArt))
mux.HandleFunc("/rest/getPodcasts.view", withWare(cont.GetPodcasts)) mux.HandleFunc("/rest/getCoverArt.view", withWare(cont.GetCoverArt))
mux.HandleFunc("/rest/getIndexes", withWare(cont.GetIndexes))
mux.HandleFunc("/rest/getIndexes.view", withWare(cont.GetIndexes))
mux.HandleFunc("/rest/getLicense", withWare(cont.GetLicence))
mux.HandleFunc("/rest/getLicense.view", withWare(cont.GetLicence))
mux.HandleFunc("/", withWare(cont.NotFound))
server := &http.Server{ server := &http.Server{
Addr: address, Addr: address,
Handler: mux, Handler: mux,

2
go.sum
View File

@@ -5,6 +5,7 @@ cloud.google.com/go v0.37.1 h1:2kHhTjz+eKEI7tt3Fqf5j3APCq+z9tuY2CzeCIxTo+A=
cloud.google.com/go v0.37.1/go.mod h1:SAbnLi6YTSPKSI0dTUEOVLCkyPfKXK8n4ibqiMoj4ok= cloud.google.com/go v0.37.1/go.mod h1:SAbnLi6YTSPKSI0dTUEOVLCkyPfKXK8n4ibqiMoj4ok=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -62,6 +63,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA= github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=

View File

@@ -27,6 +27,7 @@ func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.
w.Write([]byte(`{"subsonic-response":`)) w.Write([]byte(`{"subsonic-response":`))
w.Write(data) w.Write(data)
w.Write([]byte("}")) w.Write([]byte("}"))
fmt.Println("THE JSON", string(data))
case "jsonp": case "jsonp":
w.Header().Set("Content-Type", "application/javascript") w.Header().Set("Content-Type", "application/javascript")
data, err := json.Marshal(sub) data, err := json.Marshal(sub)
@@ -37,6 +38,7 @@ func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.
w.Write([]byte(fmt.Sprintf(`%s({"subsonic-response":`, callback))) w.Write([]byte(fmt.Sprintf(`%s({"subsonic-response":`, callback)))
w.Write(data) w.Write(data)
w.Write([]byte("});")) w.Write([]byte("});"))
fmt.Println("THE JSONP", string(data))
default: default:
w.Header().Set("Content-Type", "application/xml") w.Header().Set("Content-Type", "application/xml")
data, err := xml.Marshal(sub) data, err := xml.Marshal(sub)
@@ -44,6 +46,7 @@ func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.
log.Printf("could not marshall to xml: %v\n", err) log.Printf("could not marshall to xml: %v\n", err)
} }
w.Write(data) w.Write(data)
fmt.Println("THE XML", string(data))
} }
} }

View File

@@ -2,7 +2,6 @@ package handler
import ( import (
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
@@ -167,14 +166,17 @@ func (c *Controller) Stream(w http.ResponseWriter, req *http.Request) {
return return
} }
stat, _ := file.Stat() stat, _ := file.Stat()
size := strconv.FormatInt(stat.Size(), 10) http.ServeContent(w, req, track.Path, stat.ModTime(), file)
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", size)
file.Seek(0, 0)
io.Copy(w, file)
} }
func (c *Controller) GetMusicFolders(w http.ResponseWriter, req *http.Request) {} func (c *Controller) GetLicence(w http.ResponseWriter, req *http.Request) {
func (c *Controller) GetPlaylists(w http.ResponseWriter, req *http.Request) {} sub := subsonic.NewResponse()
func (c *Controller) GetGenres(w http.ResponseWriter, req *http.Request) {} sub.Licence = &subsonic.Licence{
func (c *Controller) GetPodcasts(w http.ResponseWriter, req *http.Request) {} Valid: true,
}
respond(w, req, sub)
}
func (c *Controller) NotFound(w http.ResponseWriter, req *http.Request) {
respondError(w, req, 0, "unknown route")
}

View File

@@ -94,3 +94,8 @@ type Child struct {
BitRate uint `xml:"bitRate,attr,omitempty" json:"bitrate,omitempty"` BitRate uint `xml:"bitRate,attr,omitempty" json:"bitrate,omitempty"`
Path string `xml:"path,attr,omitempty" json:"path,omitempty"` Path string `xml:"path,attr,omitempty" json:"path,omitempty"`
} }
type Licence struct {
XMLName xml.Name `xml:"license" json:"-"`
Valid bool `xml:"valid,attr,omitempty" json:"valid,omitempty"`
}

View File

@@ -7,7 +7,7 @@ import (
) )
var ( var (
apiVersion = "1.16.1" apiVersion = "1.9.0"
xmlns = "http://subsonic.org/restapi" xmlns = "http://subsonic.org/restapi"
) )
@@ -25,6 +25,7 @@ type Response struct {
Artist *Artist `xml:"artist" json:"artist,omitempty"` Artist *Artist `xml:"artist" json:"artist,omitempty"`
MusicDirectory *Directory `xml:"directory" json:"directory,omitempty"` MusicDirectory *Directory `xml:"directory" json:"directory,omitempty"`
RandomSongs *RandomSongs `xml:"randomSongs" json:"randomSongs,omitempty"` RandomSongs *RandomSongs `xml:"randomSongs" json:"randomSongs,omitempty"`
Licence *Licence `xml:"license" json:"license,omitempty"`
} }
type Error struct { type Error struct {

View File

@@ -188,13 +188,13 @@ func Read(filename string) (Metadata, error) {
) )
probe, err := command.Output() probe, err := command.Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("when running ffprobe with `%s`: %v\n", return nil, fmt.Errorf("when running ffprobe with `%s`: %v",
filename, err) filename, err)
} }
var data probeData var data probeData
err = json.Unmarshal(probe, &data) err = json.Unmarshal(probe, &data)
if err != nil { if err != nil {
return nil, fmt.Errorf("when unmarshalling: %v\n", err) return nil, fmt.Errorf("when unmarshalling: %v", err)
} }
track := Track{ track := Track{
format: data.Format, format: data.Format,

19
vendor/github.com/dhowden/tag/.editorconfig generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*.go]
indent_style = tab
indent_size = 3
[*.md]
trim_trailing_whitespace = false
[*]
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

5
vendor/github.com/dhowden/tag/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,5 @@
language: go
go:
- 1.7
- tip

23
vendor/github.com/dhowden/tag/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,23 @@
Copyright 2015, David Howden
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

72
vendor/github.com/dhowden/tag/README.md generated vendored Normal file
View File

@@ -0,0 +1,72 @@
# MP3/MP4/OGG/FLAC metadata parsing library
[![Build Status](https://travis-ci.org/dhowden/tag.svg?branch=master)](https://travis-ci.org/dhowden/tag)
[![GoDoc](https://godoc.org/github.com/dhowden/tag?status.svg)](https://godoc.org/github.com/dhowden/tag)
This package provides MP3 (ID3v1,2.{2,3,4}) and MP4 (ACC, M4A, ALAC), OGG and FLAC metadata detection, parsing and artwork extraction.
Detect and parse tag metadata from an `io.ReadSeeker` (i.e. an `*os.File`):
```go
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).
```
Parsed metadata is exported via a single interface (giving a consistent API for all supported metadata formats).
```go
// Metadata is an interface which is used to describe metadata retrieved by this package.
type Metadata interface {
Format() Format
FileType() FileType
Title() string
Album() string
Artist() string
AlbumArtist() string
Composer() string
Genre() string
Year() int
Track() (int, int) // Number, Total
Disc() (int, int) // Number, Total
Picture() *Picture // Artwork
Lyrics() string
Comment() string
Raw() map[string]interface{} // NB: raw tag names are not consistent across formats.
}
```
## Audio Data Checksum (SHA1)
This package also provides a metadata-invariant checksum for audio files: only the audio data is used to
construct the checksum.
[http://godoc.org/github.com/dhowden/tag#Sum](http://godoc.org/github.com/dhowden/tag#Sum)
## Tools
There are simple command-line tools which demonstrate basic tag extraction and summing:
```console
$ go get github.com/dhowden/tag/...
$ cd $GOPATH/bin
$ ./tag 11\ High\ Hopes.m4a
Metadata Format: MP4
Title: High Hopes
Album: The Division Bell
Artist: Pink Floyd
Composer: Abbey Road Recording Studios/David Gilmour/Polly Samson
Year: 1994
Track: 11 of 11
Disc: 1 of 1
Picture: Picture{Ext: jpeg, MIMEType: image/jpeg, Type: , Description: , Data.Size: 606109}
$ ./sum 11\ High\ Hopes.m4a
2ae208c5f00a1f21f5fac9b7f6e0b8e52c06da29
```

110
vendor/github.com/dhowden/tag/dsf.go generated vendored Normal file
View 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()
}

89
vendor/github.com/dhowden/tag/flac.go generated vendored Normal file
View File

@@ -0,0 +1,89 @@
// 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"
)
// blockType is a type which represents an enumeration of valid FLAC blocks
type blockType byte
// FLAC block types.
const (
// Stream Info Block 0
// Padding Block 1
// Application Block 2
// Seektable Block 3
// Cue Sheet Block 5
vorbisCommentBlock blockType = 4
pictureBlock blockType = 6
)
// ReadFLACTags reads FLAC metadata from the io.ReadSeeker, returning the resulting
// metadata in a Metadata implementation, or non-nil error if there was a problem.
func ReadFLACTags(r io.ReadSeeker) (Metadata, error) {
flac, err := readString(r, 4)
if err != nil {
return nil, err
}
if flac != "fLaC" {
return nil, errors.New("expected 'fLaC'")
}
m := &metadataFLAC{
newMetadataVorbis(),
}
for {
last, err := m.readFLACMetadataBlock(r)
if err != nil {
return nil, err
}
if last {
break
}
}
return m, nil
}
type metadataFLAC struct {
*metadataVorbis
}
func (m *metadataFLAC) readFLACMetadataBlock(r io.ReadSeeker) (last bool, err error) {
blockHeader, err := readBytes(r, 1)
if err != nil {
return
}
if getBit(blockHeader[0], 7) {
blockHeader[0] ^= (1 << 7)
last = true
}
blockLen, err := readInt(r, 3)
if err != nil {
return
}
switch blockType(blockHeader[0]) {
case vorbisCommentBlock:
err = m.readVorbisComment(r)
case pictureBlock:
err = m.readPictureBlock(r)
default:
_, err = r.Seek(int64(blockLen), io.SeekCurrent)
}
return
}
func (m *metadataFLAC) FileType() FileType {
return FLAC
}

81
vendor/github.com/dhowden/tag/id.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package tag
import (
"fmt"
"io"
)
// 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)
if err != nil {
return
}
_, err = r.Seek(-11, io.SeekCurrent)
if err != nil {
err = fmt.Errorf("could not seek back to original position: %v", err)
return
}
switch {
case string(b[0:4]) == "fLaC":
return VORBIS, FLAC, nil
case string(b[0:4]) == "OggS":
return VORBIS, OGG, nil
case string(b[4:8]) == "ftyp":
b = b[8:11]
fileType = UnknownFileType
switch string(b) {
case "M4A":
fileType = M4A
case "M4B":
fileType = M4B
case "M4P":
fileType = M4P
}
return MP4, fileType, nil
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, MP3, nil
}
n, err := r.Seek(-128, io.SeekEnd)
if err != nil {
return
}
tag, err := readString(r, 3)
if err != nil {
return
}
_, err = r.Seek(-n, io.SeekCurrent)
if err != nil {
return
}
if tag != "TAG" {
err = ErrNoTagsFound
return
}
return ID3v1, MP3, nil
}

144
vendor/github.com/dhowden/tag/id3v1.go generated vendored Normal file
View File

@@ -0,0 +1,144 @@
// 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"
"strconv"
"strings"
)
// id3v1Genres is a list of genres as given in the ID3v1 specification.
var id3v1Genres = [...]string{
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
"Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
"Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
"Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
"Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
"Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel",
"Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
"Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
"Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
"Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
"Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
"Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer",
"Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro",
"Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk-Rock",
"National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival",
"Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock",
"Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
"Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
"Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
"Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
"Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle",
"Duet", "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall",
}
// ErrNotID3v1 is an error which is returned when no ID3v1 header is found.
var ErrNotID3v1 = errors.New("invalid ID3v1 header")
// ReadID3v1Tags reads ID3v1 tags from the io.ReadSeeker. Returns ErrNotID3v1
// if there are no ID3v1 tags, otherwise non-nil error if there was a problem.
func ReadID3v1Tags(r io.ReadSeeker) (Metadata, error) {
_, err := r.Seek(-128, io.SeekEnd)
if err != nil {
return nil, err
}
if tag, err := readString(r, 3); err != nil {
return nil, err
} else if tag != "TAG" {
return nil, ErrNotID3v1
}
title, err := readString(r, 30)
if err != nil {
return nil, err
}
artist, err := readString(r, 30)
if err != nil {
return nil, err
}
album, err := readString(r, 30)
if err != nil {
return nil, err
}
year, err := readString(r, 4)
if err != nil {
return nil, err
}
commentBytes, err := readBytes(r, 30)
if err != nil {
return nil, err
}
var comment string
var track int
if commentBytes[28] == 0 {
comment = trimString(string(commentBytes[:28]))
track = int(commentBytes[29])
} else {
comment = trimString(string(commentBytes))
}
var genre string
genreID, err := readBytes(r, 1)
if err != nil {
return nil, err
}
if int(genreID[0]) < len(id3v1Genres) {
genre = id3v1Genres[int(genreID[0])]
}
m := make(map[string]interface{})
m["title"] = trimString(title)
m["artist"] = trimString(artist)
m["album"] = trimString(album)
m["year"] = trimString(year)
m["comment"] = trimString(comment)
m["track"] = track
m["genre"] = genre
return metadataID3v1(m), nil
}
func trimString(x string) string {
return strings.TrimSpace(strings.Trim(x, "\x00"))
}
// metadataID3v1 is the implementation of Metadata used for ID3v1 tags.
type metadataID3v1 map[string]interface{}
func (metadataID3v1) Format() Format { return ID3v1 }
func (metadataID3v1) FileType() FileType { return MP3 }
func (m metadataID3v1) Raw() map[string]interface{} { return m }
func (m metadataID3v1) Title() string { return m["title"].(string) }
func (m metadataID3v1) Album() string { return m["album"].(string) }
func (m metadataID3v1) Artist() string { return m["artist"].(string) }
func (m metadataID3v1) Genre() string { return m["genre"].(string) }
func (m metadataID3v1) Year() int {
y := m["year"].(string)
n, err := strconv.Atoi(y)
if err != nil {
return 0
}
return n
}
func (m metadataID3v1) Track() (int, int) { return m["track"].(int), 0 }
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 "" }
func (m metadataID3v1) Comment() string { return m["comment"].(string) }

434
vendor/github.com/dhowden/tag/id3v2.go generated vendored Normal file
View File

@@ -0,0 +1,434 @@
// 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 (
"fmt"
"io"
"regexp"
"strconv"
"strings"
)
var id3v2Genres = [...]string{
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
"Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
"Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
"Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
"Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
"Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel",
"Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
"Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
"Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
"Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
"Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
"Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer",
"Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro",
"Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk-Rock",
"National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival",
"Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock",
"Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
"Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
"Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
"Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
"Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle",
"Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",
"Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie",
"Britpop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap",
"Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
"Christian Rock ", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop",
"Synthpop",
}
// id3v2Header is a type which represents an ID3v2 tag header.
type id3v2Header struct {
Version Format
Unsynchronisation bool
ExtendedHeader bool
Experimental bool
Size int
}
// readID3v2Header reads the ID3v2 header from the given io.Reader.
// offset it number of bytes of header that was read
func readID3v2Header(r io.Reader) (h *id3v2Header, offset int, err error) {
offset = 10
b, err := readBytes(r, offset)
if err != nil {
return nil, 0, fmt.Errorf("expected to read 10 bytes (ID3v2Header): %v", err)
}
if string(b[0:3]) != "ID3" {
return nil, 0, fmt.Errorf("expected to read \"ID3\"")
}
b = b[3:]
var vers Format
switch uint(b[0]) {
case 2:
vers = ID3v2_2
case 3:
vers = ID3v2_3
case 4:
vers = ID3v2_4
case 0, 1:
fallthrough
default:
return nil, 0, fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0]))
}
// NB: We ignore b[1] (the revision) as we don't currently rely on it.
h = &id3v2Header{
Version: vers,
Unsynchronisation: getBit(b[2], 7),
ExtendedHeader: getBit(b[2], 6),
Experimental: getBit(b[2], 5),
Size: get7BitChunkedInt(b[3:7]),
}
if h.ExtendedHeader {
switch vers {
case ID3v2_3:
b, err := readBytes(r, 4)
if err != nil {
return nil, 0, fmt.Errorf("expected to read 4 bytes (ID3v23 extended header len): %v", err)
}
// skip header, size is excluding len bytes
extendedHeaderSize := getInt(b)
_, err = readBytes(r, extendedHeaderSize)
if err != nil {
return nil, 0, fmt.Errorf("expected to read %d bytes (ID3v23 skip extended header): %v", extendedHeaderSize, err)
}
offset += extendedHeaderSize
case ID3v2_4:
b, err := readBytes(r, 4)
if err != nil {
return nil, 0, fmt.Errorf("expected to read 4 bytes (ID3v24 extended header len): %v", err)
}
// skip header, size is synchsafe int including len bytes
extendedHeaderSize := get7BitChunkedInt(b) - 4
_, err = readBytes(r, extendedHeaderSize)
if err != nil {
return nil, 0, fmt.Errorf("expected to read %d bytes (ID3v24 skip extended header): %v", extendedHeaderSize, err)
}
offset += extendedHeaderSize
default:
// nop, only 2.3 and 2.4 should have extended header
}
}
return h, offset, nil
}
// id3v2FrameFlags is a type which represents the flags which can be set on an ID3v2 frame.
type id3v2FrameFlags struct {
// Message (ID3 2.3.0 and 2.4.0)
TagAlterPreservation bool
FileAlterPreservation bool
ReadOnly bool
// Format (ID3 2.3.0 and 2.4.0)
Compression bool
Encryption bool
GroupIdentity bool
// ID3 2.4.0 only (see http://id3.org/id3v2.4.0-structure sec 4.1)
Unsynchronisation bool
DataLengthIndicator bool
}
func readID3v23FrameFlags(r io.Reader) (*id3v2FrameFlags, error) {
b, err := readBytes(r, 2)
if err != nil {
return nil, err
}
msg := b[0]
fmt := b[1]
return &id3v2FrameFlags{
TagAlterPreservation: getBit(msg, 7),
FileAlterPreservation: getBit(msg, 6),
ReadOnly: getBit(msg, 5),
Compression: getBit(fmt, 7),
Encryption: getBit(fmt, 6),
GroupIdentity: getBit(fmt, 5),
}, nil
}
func readID3v24FrameFlags(r io.Reader) (*id3v2FrameFlags, error) {
b, err := readBytes(r, 2)
if err != nil {
return nil, err
}
msg := b[0]
fmt := b[1]
return &id3v2FrameFlags{
TagAlterPreservation: getBit(msg, 6),
FileAlterPreservation: getBit(msg, 5),
ReadOnly: getBit(msg, 4),
GroupIdentity: getBit(fmt, 6),
Compression: getBit(fmt, 3),
Encryption: getBit(fmt, 2),
Unsynchronisation: getBit(fmt, 1),
DataLengthIndicator: getBit(fmt, 0),
}, nil
}
func readID3v2_2FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
name, err = readString(r, 3)
if err != nil {
return
}
size, err = readInt(r, 3)
if err != nil {
return
}
headerSize = 6
return
}
func readID3v2_3FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
name, err = readString(r, 4)
if err != nil {
return
}
size, err = readInt(r, 4)
if err != nil {
return
}
headerSize = 8
return
}
func readID3v2_4FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
name, err = readString(r, 4)
if err != nil {
return
}
size, err = read7BitChunkedInt(r, 4)
if err != nil {
return
}
headerSize = 8
return
}
// readID3v2Frames reads ID3v2 frames from the given reader using the ID3v2Header.
func readID3v2Frames(r io.Reader, offset int, h *id3v2Header) (map[string]interface{}, error) {
result := make(map[string]interface{})
for offset < h.Size {
var err error
var name string
var size, headerSize int
var flags *id3v2FrameFlags
switch h.Version {
case ID3v2_2:
name, size, headerSize, err = readID3v2_2FrameHeader(r)
case ID3v2_3:
name, size, headerSize, err = readID3v2_3FrameHeader(r)
if err != nil {
return nil, err
}
flags, err = readID3v23FrameFlags(r)
headerSize += 2
case ID3v2_4:
name, size, headerSize, err = readID3v2_4FrameHeader(r)
if err != nil {
return nil, err
}
flags, err = readID3v24FrameFlags(r)
headerSize += 2
}
if err != nil {
return nil, err
}
// FIXME: Do we still need this?
// if size=0, we certainly are in a padding zone. ignore the rest of
// the tags
if size == 0 {
break
}
offset += headerSize + size
// Avoid corrupted padding (see http://id3.org/Compliance%20Issues).
if !validID3Frame(h.Version, name) && offset > h.Size {
break
}
if flags != nil {
if flags.Compression {
_, err = read7BitChunkedInt(r, 4) // read 4
if err != nil {
return nil, err
}
size -= 4
}
if flags.Encryption {
_, err = readBytes(r, 1) // read 1 byte of encryption method
if err != nil {
return nil, err
}
size -= 1
}
}
b, err := readBytes(r, size)
if err != nil {
return nil, err
}
// There can be multiple tag with the same name. Append a number to the
// name if there is more than one.
rawName := name
if _, ok := result[rawName]; ok {
for i := 0; ok; i++ {
rawName = name + "_" + strconv.Itoa(i)
_, ok = result[rawName]
}
}
switch {
case name == "TXXX" || name == "TXX":
t, err := readTextWithDescrFrame(b, false, true) // no lang, but enc
if err != nil {
return nil, err
}
result[rawName] = t
case name[0] == 'T':
txt, err := readTFrame(b)
if err != nil {
return nil, err
}
result[rawName] = txt
case name == "UFID" || name == "UFI":
t, err := readUFID(b)
if err != nil {
return nil, err
}
result[rawName] = t
case name == "WXXX" || name == "WXX":
t, err := readTextWithDescrFrame(b, false, false) // no lang, no enc
if err != nil {
return nil, err
}
result[rawName] = t
case name[0] == 'W':
txt, err := readWFrame(b)
if err != nil {
return nil, err
}
result[rawName] = txt
case name == "COMM" || name == "COM" || name == "USLT" || name == "ULT":
t, err := readTextWithDescrFrame(b, true, true) // both lang and enc
if err != nil {
return nil, err
}
result[rawName] = t
case name == "APIC":
p, err := readAPICFrame(b)
if err != nil {
return nil, err
}
result[rawName] = p
case name == "PIC":
p, err := readPICFrame(b)
if err != nil {
return nil, err
}
result[rawName] = p
default:
result[rawName] = b
}
}
return result, nil
}
type unsynchroniser struct {
io.Reader
ff bool
}
// filter io.Reader which skip the Unsynchronisation bytes
func (r *unsynchroniser) Read(p []byte) (int, error) {
b := make([]byte, 1)
i := 0
for i < len(p) {
if n, err := r.Reader.Read(b); err != nil || n == 0 {
return i, err
}
if r.ff && b[0] == 0x00 {
r.ff = false
continue
}
p[i] = b[0]
i++
r.ff = (b[0] == 0xFF)
}
return i, nil
}
// ReadID3v2Tags parses ID3v2.{2,3,4} tags from the io.ReadSeeker into a Metadata, returning
// non-nil error on failure.
func ReadID3v2Tags(r io.ReadSeeker) (Metadata, error) {
h, offset, err := readID3v2Header(r)
if err != nil {
return nil, err
}
var ur io.Reader = r
if h.Unsynchronisation {
ur = &unsynchroniser{Reader: r}
}
f, err := readID3v2Frames(ur, offset, h)
if err != nil {
return nil, err
}
return metadataID3v2{header: h, frames: f}, nil
}
var id3v2genreRe = regexp.MustCompile(`(.*[^(]|.* |^)\(([0-9]+)\) *(.*)$`)
// id3v2genre parse a id3v2 genre tag and expand the numeric genres
func id3v2genre(genre string) string {
c := true
for c {
orig := genre
if match := id3v2genreRe.FindStringSubmatch(genre); len(match) > 0 {
if genreID, err := strconv.Atoi(match[2]); err == nil {
if genreID < len(id3v2Genres) {
genre = id3v2Genres[genreID]
if match[1] != "" {
genre = strings.TrimSpace(match[1]) + " " + genre
}
if match[3] != "" {
genre = genre + " " + match[3]
}
}
}
}
c = (orig != genre)
}
return strings.Replace(genre, "((", "(", -1)
}

638
vendor/github.com/dhowden/tag/id3v2frames.go generated vendored Normal file
View File

@@ -0,0 +1,638 @@
// 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 (
"bytes"
"encoding/binary"
"errors"
"fmt"
"strings"
"unicode/utf16"
)
// DefaultUTF16WithBOMByteOrder is the byte order used when the "UTF16 with BOM" encoding
// is specified without a corresponding BOM in the data.
var DefaultUTF16WithBOMByteOrder binary.ByteOrder = binary.LittleEndian
// ID3v2.2.0 frames (see http://id3.org/id3v2-00, sec 4).
var id3v22Frames = map[string]string{
"BUF": "Recommended buffer size",
"CNT": "Play counter",
"COM": "Comments",
"CRA": "Audio encryption",
"CRM": "Encrypted meta frame",
"ETC": "Event timing codes",
"EQU": "Equalization",
"GEO": "General encapsulated object",
"IPL": "Involved people list",
"LNK": "Linked information",
"MCI": "Music CD Identifier",
"MLL": "MPEG location lookup table",
"PIC": "Attached picture",
"POP": "Popularimeter",
"REV": "Reverb",
"RVA": "Relative volume adjustment",
"SLT": "Synchronized lyric/text",
"STC": "Synced tempo codes",
"TAL": "Album/Movie/Show title",
"TBP": "BPM (Beats Per Minute)",
"TCM": "Composer",
"TCO": "Content type",
"TCR": "Copyright message",
"TDA": "Date",
"TDY": "Playlist delay",
"TEN": "Encoded by",
"TFT": "File type",
"TIM": "Time",
"TKE": "Initial key",
"TLA": "Language(s)",
"TLE": "Length",
"TMT": "Media type",
"TOA": "Original artist(s)/performer(s)",
"TOF": "Original filename",
"TOL": "Original Lyricist(s)/text writer(s)",
"TOR": "Original release year",
"TOT": "Original album/Movie/Show title",
"TP1": "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",
"TP2": "Band/Orchestra/Accompaniment",
"TP3": "Conductor/Performer refinement",
"TP4": "Interpreted, remixed, or otherwise modified by",
"TPA": "Part of a set",
"TPB": "Publisher",
"TRC": "ISRC (International Standard Recording Code)",
"TRD": "Recording dates",
"TRK": "Track number/Position in set",
"TSI": "Size",
"TSS": "Software/hardware and settings used for encoding",
"TT1": "Content group description",
"TT2": "Title/Songname/Content description",
"TT3": "Subtitle/Description refinement",
"TXT": "Lyricist/text writer",
"TXX": "User defined text information frame",
"TYE": "Year",
"UFI": "Unique file identifier",
"ULT": "Unsychronized lyric/text transcription",
"WAF": "Official audio file webpage",
"WAR": "Official artist/performer webpage",
"WAS": "Official audio source webpage",
"WCM": "Commercial information",
"WCP": "Copyright/Legal information",
"WPB": "Publishers official webpage",
"WXX": "User defined URL link frame",
}
// ID3v2.3.0 frames (see http://id3.org/id3v2.3.0#Declared_ID3v2_frames).
var id3v23Frames = map[string]string{
"AENC": "Audio encryption]",
"APIC": "Attached picture",
"COMM": "Comments",
"COMR": "Commercial frame",
"ENCR": "Encryption method registration",
"EQUA": "Equalization",
"ETCO": "Event timing codes",
"GEOB": "General encapsulated object",
"GRID": "Group identification registration",
"IPLS": "Involved people list",
"LINK": "Linked information",
"MCDI": "Music CD identifier",
"MLLT": "MPEG location lookup table",
"OWNE": "Ownership frame",
"PRIV": "Private frame",
"PCNT": "Play counter",
"POPM": "Popularimeter",
"POSS": "Position synchronisation frame",
"RBUF": "Recommended buffer size",
"RVAD": "Relative volume adjustment",
"RVRB": "Reverb",
"SYLT": "Synchronized lyric/text",
"SYTC": "Synchronized tempo codes",
"TALB": "Album/Movie/Show title",
"TBPM": "BPM (beats per minute)",
"TCMP": "iTunes Compilation Flag",
"TCOM": "Composer",
"TCON": "Content type",
"TCOP": "Copyright message",
"TDAT": "Date",
"TDLY": "Playlist delay",
"TENC": "Encoded by",
"TEXT": "Lyricist/Text writer",
"TFLT": "File type",
"TIME": "Time",
"TIT1": "Content group description",
"TIT2": "Title/songname/content description",
"TIT3": "Subtitle/Description refinement",
"TKEY": "Initial key",
"TLAN": "Language(s)",
"TLEN": "Length",
"TMED": "Media type",
"TOAL": "Original album/movie/show title",
"TOFN": "Original filename",
"TOLY": "Original lyricist(s)/text writer(s)",
"TOPE": "Original artist(s)/performer(s)",
"TORY": "Original release year",
"TOWN": "File owner/licensee",
"TPE1": "Lead performer(s)/Soloist(s)",
"TPE2": "Band/orchestra/accompaniment",
"TPE3": "Conductor/performer refinement",
"TPE4": "Interpreted, remixed, or otherwise modified by",
"TPOS": "Part of a set",
"TPUB": "Publisher",
"TRCK": "Track number/Position in set",
"TRDA": "Recording dates",
"TRSN": "Internet radio station name",
"TRSO": "Internet radio station owner",
"TSIZ": "Size",
"TSO2": "iTunes uses this for Album Artist sort order",
"TSOC": "iTunes uses this for Composer sort order",
"TSRC": "ISRC (international standard recording code)",
"TSSE": "Software/Hardware and settings used for encoding",
"TYER": "Year",
"TXXX": "User defined text information frame",
"UFID": "Unique file identifier",
"USER": "Terms of use",
"USLT": "Unsychronized lyric/text transcription",
"WCOM": "Commercial information",
"WCOP": "Copyright/Legal information",
"WOAF": "Official audio file webpage",
"WOAR": "Official artist/performer webpage",
"WOAS": "Official audio source webpage",
"WORS": "Official internet radio station homepage",
"WPAY": "Payment",
"WPUB": "Publishers official webpage",
"WXXX": "User defined URL link frame",
}
// ID3v2.4.0 frames (see http://id3.org/id3v2.4.0-frames, sec 4).
var id3v24Frames = map[string]string{
"AENC": "Audio encryption",
"APIC": "Attached picture",
"ASPI": "Audio seek point index",
"COMM": "Comments",
"COMR": "Commercial frame",
"ENCR": "Encryption method registration",
"EQU2": "Equalisation (2)",
"ETCO": "Event timing codes",
"GEOB": "General encapsulated object",
"GRID": "Group identification registration",
"LINK": "Linked information",
"MCDI": "Music CD identifier",
"MLLT": "MPEG location lookup table",
"OWNE": "Ownership frame",
"PRIV": "Private frame",
"PCNT": "Play counter",
"POPM": "Popularimeter",
"POSS": "Position synchronisation frame",
"RBUF": "Recommended buffer size",
"RVA2": "Relative volume adjustment (2)",
"RVRB": "Reverb",
"SEEK": "Seek frame",
"SIGN": "Signature frame",
"SYLT": "Synchronised lyric/text",
"SYTC": "Synchronised tempo codes",
"TALB": "Album/Movie/Show title",
"TBPM": "BPM (beats per minute)",
"TCMP": "iTunes Compilation Flag",
"TCOM": "Composer",
"TCON": "Content type",
"TCOP": "Copyright message",
"TDEN": "Encoding time",
"TDLY": "Playlist delay",
"TDOR": "Original release time",
"TDRC": "Recording time",
"TDRL": "Release time",
"TDTG": "Tagging time",
"TENC": "Encoded by",
"TEXT": "Lyricist/Text writer",
"TFLT": "File type",
"TIPL": "Involved people list",
"TIT1": "Content group description",
"TIT2": "Title/songname/content description",
"TIT3": "Subtitle/Description refinement",
"TKEY": "Initial key",
"TLAN": "Language(s)",
"TLEN": "Length",
"TMCL": "Musician credits list",
"TMED": "Media type",
"TMOO": "Mood",
"TOAL": "Original album/movie/show title",
"TOFN": "Original filename",
"TOLY": "Original lyricist(s)/text writer(s)",
"TOPE": "Original artist(s)/performer(s)",
"TOWN": "File owner/licensee",
"TPE1": "Lead performer(s)/Soloist(s)",
"TPE2": "Band/orchestra/accompaniment",
"TPE3": "Conductor/performer refinement",
"TPE4": "Interpreted, remixed, or otherwise modified by",
"TPOS": "Part of a set",
"TPRO": "Produced notice",
"TPUB": "Publisher",
"TRCK": "Track number/Position in set",
"TRSN": "Internet radio station name",
"TRSO": "Internet radio station owner",
"TSO2": "iTunes uses this for Album Artist sort order",
"TSOA": "Album sort order",
"TSOC": "iTunes uses this for Composer sort order",
"TSOP": "Performer sort order",
"TSOT": "Title sort order",
"TSRC": "ISRC (international standard recording code)",
"TSSE": "Software/Hardware and settings used for encoding",
"TSST": "Set subtitle",
"TXXX": "User defined text information frame",
"UFID": "Unique file identifier",
"USER": "Terms of use",
"USLT": "Unsynchronised lyric/text transcription",
"WCOM": "Commercial information",
"WCOP": "Copyright/Legal information",
"WOAF": "Official audio file webpage",
"WOAR": "Official artist/performer webpage",
"WOAS": "Official audio source webpage",
"WORS": "Official Internet radio station homepage",
"WPAY": "Payment",
"WPUB": "Publishers official webpage",
"WXXX": "User defined URL link frame",
}
// ID3 frames that are defined in the specs.
var id3Frames = map[Format]map[string]string{
ID3v2_2: id3v22Frames,
ID3v2_3: id3v23Frames,
ID3v2_4: id3v24Frames,
}
func validID3Frame(version Format, name string) bool {
names, ok := id3Frames[version]
if !ok {
return false
}
_, ok = names[name]
return ok
}
func readWFrame(b []byte) (string, error) {
// Frame text is always encoded in ISO-8859-1
b = append([]byte{0}, b...)
return readTFrame(b)
}
func readTFrame(b []byte) (string, error) {
if len(b) == 0 {
return "", nil
}
txt, err := decodeText(b[0], b[1:])
if err != nil {
return "", err
}
return strings.Join(strings.Split(txt, string(singleZero)), ""), nil
}
const (
encodingISO8859 byte = 0
encodingUTF16WithBOM byte = 1
encodingUTF16 byte = 2
encodingUTF8 byte = 3
)
func decodeText(enc byte, b []byte) (string, error) {
if len(b) == 0 {
return "", nil
}
switch enc {
case encodingISO8859: // ISO-8859-1
return decodeISO8859(b), nil
case encodingUTF16WithBOM: // UTF-16 with byte order marker
if len(b) == 1 {
return "", nil
}
return decodeUTF16WithBOM(b)
case encodingUTF16: // UTF-16 without byte order (assuming BigEndian)
if len(b) == 1 {
return "", nil
}
return decodeUTF16(b, binary.BigEndian)
case encodingUTF8: // UTF-8
return string(b), nil
default: // Fallback to ISO-8859-1
return decodeISO8859(b), nil
}
}
var (
singleZero = []byte{0}
doubleZero = []byte{0, 0}
)
func dataSplit(b []byte, enc byte) [][]byte {
delim := singleZero
if enc == encodingUTF16 || enc == encodingUTF16WithBOM {
delim = doubleZero
}
result := bytes.SplitN(b, delim, 2)
if len(result) != 2 {
return result
}
if len(result[1]) == 0 {
return result
}
if result[1][0] == 0 {
// there was a double (or triple) 0 and we cut too early
result[0] = append(result[0], result[1][0])
result[1] = result[1][1:]
}
return result
}
func decodeISO8859(b []byte) string {
r := make([]rune, len(b))
for i, x := range b {
r[i] = rune(x)
}
return string(r)
}
func decodeUTF16WithBOM(b []byte) (string, error) {
if len(b) < 2 {
return "", errors.New("invalid encoding: expected at least 2 bytes for UTF-16 byte order mark")
}
var bo binary.ByteOrder
switch {
case b[0] == 0xFE && b[1] == 0xFF:
bo = binary.BigEndian
b = b[2:]
case b[0] == 0xFF && b[1] == 0xFE:
bo = binary.LittleEndian
b = b[2:]
default:
bo = DefaultUTF16WithBOMByteOrder
}
return decodeUTF16(b, bo)
}
func decodeUTF16(b []byte, bo binary.ByteOrder) (string, error) {
if len(b)%2 != 0 {
return "", errors.New("invalid encoding: expected even number of bytes for UTF-16 encoded text")
}
s := make([]uint16, 0, len(b)/2)
for i := 0; i < len(b); i += 2 {
s = append(s, bo.Uint16(b[i:i+2]))
}
return string(utf16.Decode(s)), nil
}
// Comm is a type used in COMM, UFID, TXXX, WXXX and USLT tag.
// It's a text with a description and a specified language
// For WXXX, TXXX and UFID, we don't set a 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 {
if t.Language != "" {
return fmt.Sprintf("Text{Lang: '%v', Description: '%v', %v lines}",
t.Language, t.Description, strings.Count(t.Text, "\n"))
}
return fmt.Sprintf("Text{Description: '%v', %v}", t.Description, t.Text)
}
// IDv2.{3,4}
// -- Header
// <Header for 'Unsynchronised lyrics/text transcription', ID: "USLT">
// <Header for 'Comment', ID: "COMM">
// -- readTextWithDescrFrame(data, true, true)
// Text encoding $xx
// Language $xx xx xx
// Content descriptor <text string according to encoding> $00 (00)
// Lyrics/text <full text string according to encoding>
// -- Header
// <Header for 'User defined text information frame', ID: "TXXX">
// <Header for 'User defined URL link frame', ID: "WXXX">
// -- readTextWithDescrFrame(data, false, <isDataEncoded>)
// Text encoding $xx
// Description <text string according to encoding> $00 (00)
// Value <text string according to encoding>
func readTextWithDescrFrame(b []byte, hasLang bool, encoded bool) (*Comm, error) {
enc := b[0]
b = b[1:]
c := &Comm{}
if hasLang {
c.Language = string(b[:3])
b = b[3:]
}
descTextSplit := dataSplit(b, enc)
if len(descTextSplit) < 1 {
return nil, fmt.Errorf("error decoding tag description text: invalid encoding")
}
desc, err := decodeText(enc, descTextSplit[0])
if err != nil {
return nil, fmt.Errorf("error decoding tag description text: %v", err)
}
c.Description = desc
if len(descTextSplit) == 1 {
return c, nil
}
if !encoded {
enc = byte(0)
}
text, err := decodeText(enc, descTextSplit[1])
if err != nil {
return nil, fmt.Errorf("error decoding tag text: %v", err)
}
c.Text = text
return c, nil
}
// UFID is composed of a provider (frequently a URL and a binary identifier)
// The identifier can be a text (Musicbrainz use texts, but not necessary)
type UFID struct {
Provider string
Identifier []byte
}
func (u UFID) String() string {
return fmt.Sprintf("%v (%v)", u.Provider, string(u.Identifier))
}
func readUFID(b []byte) (*UFID, error) {
result := bytes.SplitN(b, singleZero, 2)
if len(result) != 2 {
return nil, errors.New("expected to split UFID data into 2 pieces")
}
return &UFID{
Provider: string(result[0]),
Identifier: result[1],
}, nil
}
var pictureTypes = map[byte]string{
0x00: "Other",
0x01: "32x32 pixels 'file icon' (PNG only)",
0x02: "Other file icon",
0x03: "Cover (front)",
0x04: "Cover (back)",
0x05: "Leaflet page",
0x06: "Media (e.g. lable side of CD)",
0x07: "Lead artist/lead performer/soloist",
0x08: "Artist/performer",
0x09: "Conductor",
0x0A: "Band/Orchestra",
0x0B: "Composer",
0x0C: "Lyricist/text writer",
0x0D: "Recording Location",
0x0E: "During recording",
0x0F: "During performance",
0x10: "Movie/video screen capture",
0x11: "A bright coloured fish",
0x12: "Illustration",
0x13: "Band/artist logotype",
0x14: "Publisher/Studio logotype",
}
// Picture is a type which represents an attached picture extracted from metadata.
type Picture struct {
Ext string // Extension of the picture file.
MIMEType string // MIMEType of the picture.
Type string // Type of the picture (see pictureTypes).
Description string // Description.
Data []byte // Raw picture data.
}
// String returns a string representation of the underlying Picture instance.
func (p Picture) String() string {
return fmt.Sprintf("Picture{Ext: %v, MIMEType: %v, Type: %v, Description: %v, Data.Size: %v}",
p.Ext, p.MIMEType, p.Type, p.Description, len(p.Data))
}
// IDv2.2
// -- Header
// Attached picture "PIC"
// Frame size $xx xx xx
// -- readPICFrame
// Text encoding $xx
// Image format $xx xx xx
// Picture type $xx
// Description <textstring> $00 (00)
// Picture data <binary data>
func readPICFrame(b []byte) (*Picture, error) {
enc := b[0]
ext := string(b[1:4])
picType := b[4]
descDataSplit := dataSplit(b[5:], enc)
if len(descDataSplit) != 2 {
return nil, errors.New("error decoding PIC description text: invalid encoding")
}
desc, err := decodeText(enc, descDataSplit[0])
if err != nil {
return nil, fmt.Errorf("error decoding PIC description text: %v", err)
}
var mimeType string
switch ext {
case "jpeg", "jpg":
mimeType = "image/jpeg"
case "png":
mimeType = "image/png"
}
return &Picture{
Ext: ext,
MIMEType: mimeType,
Type: pictureTypes[picType],
Description: desc,
Data: descDataSplit[1],
}, nil
}
// IDv2.{3,4}
// -- Header
// <Header for 'Attached picture', ID: "APIC">
// -- readAPICFrame
// Text encoding $xx
// MIME type <text string> $00
// Picture type $xx
// Description <text string according to encoding> $00 (00)
// Picture data <binary data>
func readAPICFrame(b []byte) (*Picture, error) {
enc := b[0]
mimeDataSplit := bytes.SplitN(b[1:], singleZero, 2)
mimeType := string(mimeDataSplit[0])
b = mimeDataSplit[1]
if len(b) < 1 {
return nil, fmt.Errorf("error decoding APIC mimetype")
}
picType := b[0]
descDataSplit := dataSplit(b[1:], enc)
if len(descDataSplit) != 2 {
return nil, errors.New("error decoding APIC description text: invalid encoding")
}
desc, err := decodeText(enc, descDataSplit[0])
if err != nil {
return nil, fmt.Errorf("error decoding APIC description text: %v", err)
}
var ext string
switch mimeType {
case "image/jpeg":
ext = "jpg"
case "image/png":
ext = "png"
}
return &Picture{
Ext: ext,
MIMEType: mimeType,
Type: pictureTypes[picType],
Description: desc,
Data: descDataSplit[1],
}, nil
}

141
vendor/github.com/dhowden/tag/id3v2metadata.go generated vendored Normal file
View File

@@ -0,0 +1,141 @@
// 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]
case ID3v2_3:
return l[1]
case ID3v2_4:
if s == "year" {
return "TDRC"
}
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"},
"lyrics": [2]string{"", "USLT"},
"comment": [2]string{"COM", "COMM"},
})
// metadataID3v2 is the implementation of Metadata used for ID3v2 tags.
type metadataID3v2 struct {
header *id3v2Header
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 }
func (m metadataID3v2) FileType() FileType { return MP3 }
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 {
return id3v2genre(m.getString(frames.Name("genre", m.Format())))
}
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())))
}
func (m metadataID3v2) Lyrics() string {
t, ok := m.frames[frames.Name("lyrics", m.Format())]
if !ok {
return ""
}
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 {
return nil
}
return v.(*Picture)
}

362
vendor/github.com/dhowden/tag/mp4.go generated vendored Normal file
View File

@@ -0,0 +1,362 @@
// 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 (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
)
var atomTypes = map[int]string{
0: "implicit", // automatic based on atom name
1: "text",
13: "jpeg",
14: "png",
21: "uint8",
}
// NB: atoms does not include "----", this is handled separately
var atoms = atomNames(map[string]string{
"\xa9alb": "album",
"\xa9art": "artist",
"\xa9ART": "artist",
"aART": "album_artist",
"\xa9day": "year",
"\xa9nam": "title",
"\xa9gen": "genre",
"trkn": "track",
"\xa9wrt": "composer",
"\xa9too": "encoder",
"cprt": "copyright",
"covr": "picture",
"\xa9grp": "grouping",
"keyw": "keyword",
"\xa9lyr": "lyrics",
"\xa9cmt": "comment",
"tmpo": "tempo",
"cpil": "compilation",
"disk": "disc",
})
// Detect PNG image if "implicit" class is used
var pngHeader = []byte{137, 80, 78, 71, 13, 10, 26, 10}
type atomNames map[string]string
func (f atomNames) Name(n string) []string {
res := make([]string, 1)
for k, v := range f {
if v == n {
res = append(res, k)
}
}
return res
}
// metadataMP4 is the implementation of Metadata for MP4 tag (atom) data.
type metadataMP4 struct {
fileType FileType
data map[string]interface{}
}
// ReadAtoms reads MP4 metadata atoms from the io.ReadSeeker into a Metadata, returning
// non-nil error if there was a problem.
func ReadAtoms(r io.ReadSeeker) (Metadata, error) {
m := metadataMP4{
data: make(map[string]interface{}),
fileType: UnknownFileType,
}
err := m.readAtoms(r)
return m, err
}
func (m metadataMP4) readAtoms(r io.ReadSeeker) error {
for {
name, size, err := readAtomHeader(r)
if err != nil {
if err == io.EOF {
return nil
}
return err
}
switch name {
case "meta":
// next_item_id (int32)
_, err := readBytes(r, 4)
if err != nil {
return err
}
fallthrough
case "moov", "udta", "ilst":
return m.readAtoms(r)
}
_, ok := atoms[name]
if name == "----" {
name, size, err = readCustomAtom(r, size)
if err != nil {
return err
}
if name != "----" {
ok = true
}
}
if !ok {
_, err := r.Seek(int64(size-8), io.SeekCurrent)
if err != nil {
return err
}
continue
}
err = m.readAtomData(r, name, size-8)
if err != nil {
return err
}
}
}
func (m metadataMP4) readAtomData(r io.ReadSeeker, name string, size uint32) error {
b, err := readBytes(r, int(size))
if err != nil {
return err
}
if len(b) < 8 {
return fmt.Errorf("invalid encoding: expected at least %d bytes, got %d", 8, len(b))
}
// "data" + size (4 bytes each)
b = b[8:]
if len(b) < 3 {
return fmt.Errorf("invalid encoding: expected at least %d bytes, for class, got %d", 3, len(b))
}
class := getInt(b[1:4])
contentType, ok := atomTypes[class]
if !ok {
return fmt.Errorf("invalid content type: %v (%x) (%x)", class, b[1:4], b)
}
// 4: atom version (1 byte) + atom flags (3 bytes)
// 4: NULL (usually locale indicator)
if len(b) < 8 {
return fmt.Errorf("invalid encoding: expected at least %d bytes, for atom version and flags, got %d", 8, len(b))
}
b = b[8:]
if name == "trkn" || name == "disk" {
if len(b) < 6 {
return fmt.Errorf("invalid encoding: expected at least %d bytes, for track and disk numbers, got %d", 6, len(b))
}
m.data[name] = int(b[3])
m.data[name+"_count"] = int(b[5])
return nil
}
if contentType == "implicit" {
if name == "covr" {
if bytes.HasPrefix(b, pngHeader) {
contentType = "png"
}
// TODO(dhowden): Detect JPEG formats too (harder).
}
}
var data interface{}
switch contentType {
case "implicit":
if _, ok := atoms[name]; ok {
return fmt.Errorf("unhandled implicit content type for required atom: %q", name)
}
return nil
case "text":
data = string(b)
case "uint8":
if len(b) < 1 {
return fmt.Errorf("invalid encoding: expected at least %d bytes, for integer tag data, got %d", 1, len(b))
}
data = getInt(b[:1])
case "jpeg", "png":
data = &Picture{
Ext: contentType,
MIMEType: "image/" + contentType,
Data: b,
}
}
m.data[name] = data
return nil
}
func readAtomHeader(r io.ReadSeeker) (name string, size uint32, err error) {
err = binary.Read(r, binary.BigEndian, &size)
if err != nil {
return
}
name, err = readString(r, 4)
return
}
// Generic atom.
// Should have 3 sub atoms : mean, name and data.
// We check that mean is "com.apple.iTunes" and we use the subname as
// the name, and move to the data atom.
// If anything goes wrong, we jump at the end of the "----" atom.
func readCustomAtom(r io.ReadSeeker, size uint32) (string, uint32, error) {
subNames := make(map[string]string)
var dataSize uint32
for size > 8 {
subName, subSize, err := readAtomHeader(r)
if err != nil {
return "", 0, err
}
// Remove the size of the atom from the size counter
size -= subSize
switch subName {
case "mean", "name":
b, err := readBytes(r, int(subSize-8))
if err != nil {
return "", 0, err
}
if len(b) < 4 {
return "", 0, fmt.Errorf("invalid encoding: expected at least %d bytes, got %d", 4, len(b))
}
subNames[subName] = string(b[4:])
case "data":
// Found the "data" atom, rewind
dataSize = subSize + 8 // will need to re-read "data" + size (4 + 4)
_, err := r.Seek(-8, io.SeekCurrent)
if err != nil {
return "", 0, err
}
}
}
// there should remain only the header size
if size != 8 {
err := errors.New("---- atom out of bounds")
return "", 0, err
}
if subNames["mean"] != "com.apple.iTunes" || subNames["name"] == "" || dataSize == 0 {
return "----", 0, nil
}
return subNames["name"], dataSize, nil
}
func (metadataMP4) Format() Format { return MP4 }
func (m metadataMP4) FileType() FileType { return m.fileType }
func (m metadataMP4) Raw() map[string]interface{} { return m.data }
func (m metadataMP4) getString(n []string) string {
for _, k := range n {
if x, ok := m.data[k]; ok {
return x.(string)
}
}
return ""
}
func (m metadataMP4) getInt(n []string) int {
for _, k := range n {
if x, ok := m.data[k]; ok {
return x.(int)
}
}
return 0
}
func (m metadataMP4) Title() string {
return m.getString(atoms.Name("title"))
}
func (m metadataMP4) Artist() string {
return m.getString(atoms.Name("artist"))
}
func (m metadataMP4) Album() string {
return m.getString(atoms.Name("album"))
}
func (m metadataMP4) AlbumArtist() string {
return m.getString(atoms.Name("album_artist"))
}
func (m metadataMP4) Composer() string {
return m.getString(atoms.Name("composer"))
}
func (m metadataMP4) Genre() string {
return m.getString(atoms.Name("genre"))
}
func (m metadataMP4) Year() int {
date := m.getString(atoms.Name("year"))
if len(date) >= 4 {
year, _ := strconv.Atoi(date[:4])
return year
}
return 0
}
func (m metadataMP4) Track() (int, int) {
x := m.getInt([]string{"trkn"})
if n, ok := m.data["trkn_count"]; ok {
return x, n.(int)
}
return x, 0
}
func (m metadataMP4) Disc() (int, int) {
x := m.getInt([]string{"disk"})
if n, ok := m.data["disk_count"]; ok {
return x, n.(int)
}
return x, 0
}
func (m metadataMP4) Lyrics() string {
t, ok := m.data["\xa9lyr"]
if !ok {
return ""
}
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 {
return nil
}
p, _ := v.(*Picture)
return p
}

119
vendor/github.com/dhowden/tag/ogg.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
// 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"
)
const (
idType int = 1
commentType int = 3
)
// ReadOGGTags reads OGG metadata from the io.ReadSeeker, returning the resulting
// metadata in a Metadata implementation, or non-nil error if there was a problem.
// See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
// and http://www.xiph.org/ogg/doc/framing.html for details.
func ReadOGGTags(r io.ReadSeeker) (Metadata, error) {
oggs, err := readString(r, 4)
if err != nil {
return nil, err
}
if oggs != "OggS" {
return nil, errors.New("expected 'OggS'")
}
// Skip 22 bytes of Page header to read page_segments length byte at position 26
// See http://www.xiph.org/ogg/doc/framing.html
_, err = r.Seek(22, io.SeekCurrent)
if err != nil {
return nil, err
}
nS, err := readInt(r, 1)
if err != nil {
return nil, err
}
// Seek and discard the segments
_, err = r.Seek(int64(nS), io.SeekCurrent)
if err != nil {
return nil, err
}
// First packet type is identification, type 1
t, err := readInt(r, 1)
if err != nil {
return nil, err
}
if t != idType {
return nil, errors.New("expected 'vorbis' identification type 1")
}
// Seek and discard 29 bytes from common and identification header
// See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2
_, err = r.Seek(29, io.SeekCurrent)
if err != nil {
return nil, err
}
// Beginning of a new page. Comment packet is on a separate page
// See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-132000A.2
oggs, err = readString(r, 4)
if err != nil {
return nil, err
}
if oggs != "OggS" {
return nil, errors.New("expected 'OggS'")
}
// Skip page 2 header, same as line 30
_, err = r.Seek(22, io.SeekCurrent)
if err != nil {
return nil, err
}
nS, err = readInt(r, 1)
if err != nil {
return nil, err
}
_, err = r.Seek(int64(nS), io.SeekCurrent)
if err != nil {
return nil, err
}
// Packet type is comment, type 3
t, err = readInt(r, 1)
if err != nil {
return nil, err
}
if t != commentType {
return nil, errors.New("expected 'vorbis' comment type 3")
}
// Seek and discard 6 bytes from common header
_, err = r.Seek(6, io.SeekCurrent)
if err != nil {
return nil, err
}
m := &metadataOGG{
newMetadataVorbis(),
}
err = m.readVorbisComment(r)
return m, err
}
type metadataOGG struct {
*metadataVorbis
}
func (m *metadataOGG) FileType() FileType {
return OGG
}

219
vendor/github.com/dhowden/tag/sum.go generated vendored Normal file
View File

@@ -0,0 +1,219 @@
package tag
import (
"crypto/sha1"
"encoding/binary"
"errors"
"fmt"
"hash"
"io"
)
// Sum creates a checksum of the audio file data provided by the io.ReadSeeker which is metadata
// (ID3, MP4) invariant.
func Sum(r io.ReadSeeker) (string, error) {
b, err := readBytes(r, 11)
if err != nil {
return "", err
}
_, err = r.Seek(-11, io.SeekCurrent)
if err != nil {
return "", fmt.Errorf("could not seek back to original position: %v", err)
}
switch {
case string(b[0:4]) == "fLaC":
return SumFLAC(r)
case string(b[4:11]) == "ftypM4A":
return SumAtoms(r)
case string(b[0:3]) == "ID3":
return SumID3v2(r)
}
h, err := SumID3v1(r)
if err != nil {
if err == ErrNotID3v1 {
return SumAll(r)
}
return "", err
}
return h, nil
}
// SumAll returns a checksum of the content from the reader (until EOF).
func SumAll(r io.ReadSeeker) (string, error) {
h := sha1.New()
_, err := io.Copy(h, r)
if err != nil {
return "", nil
}
return hashSum(h), nil
}
// SumAtoms constructs a checksum of MP4 audio file data provided by the io.ReadSeeker which is
// metadata invariant.
func SumAtoms(r io.ReadSeeker) (string, error) {
for {
var size uint32
err := binary.Read(r, binary.BigEndian, &size)
if err != nil {
if err == io.EOF {
return "", fmt.Errorf("reached EOF before audio data")
}
return "", err
}
name, err := readString(r, 4)
if err != nil {
return "", err
}
switch name {
case "meta":
// next_item_id (int32)
_, err := r.Seek(4, io.SeekCurrent)
if err != nil {
return "", err
}
fallthrough
case "moov", "udta", "ilst":
continue
case "mdat": // stop when we get to the data
h := sha1.New()
_, err := io.CopyN(h, r, int64(size-8))
if err != nil {
return "", fmt.Errorf("error reading audio data: %v", err)
}
return hashSum(h), nil
}
_, err = r.Seek(int64(size-8), io.SeekCurrent)
if err != nil {
return "", fmt.Errorf("error reading '%v' tag: %v", name, err)
}
}
}
func sizeToEndOffset(r io.ReadSeeker, offset int64) (int64, error) {
n, err := r.Seek(-128, io.SeekEnd)
if err != nil {
return 0, fmt.Errorf("error seeking end offset (%d bytes): %v", offset, err)
}
_, err = r.Seek(-n, io.SeekCurrent)
if err != nil {
return 0, fmt.Errorf("error seeking back to original position: %v", err)
}
return n, nil
}
// SumID3v1 constructs a checksum of MP3 audio file data (assumed to have ID3v1 tags) provided
// by the io.ReadSeeker which is metadata invariant.
func SumID3v1(r io.ReadSeeker) (string, error) {
n, err := sizeToEndOffset(r, 128)
if err != nil {
return "", fmt.Errorf("error determining read size to ID3v1 header: %v", err)
}
// TODO: improve this check???
if n <= 0 {
return "", fmt.Errorf("file size must be greater than 128 bytes (ID3v1 header size) for MP3")
}
h := sha1.New()
_, err = io.CopyN(h, r, n)
if err != nil {
return "", fmt.Errorf("error reading %v bytes: %v", n, err)
}
return hashSum(h), nil
}
// SumID3v2 constructs a checksum of MP3 audio file data (assumed to have ID3v2 tags) provided by the
// io.ReadSeeker which is metadata invariant.
func SumID3v2(r io.ReadSeeker) (string, error) {
header, _, err := readID3v2Header(r)
if err != nil {
return "", fmt.Errorf("error reading ID3v2 header: %v", err)
}
_, err = r.Seek(int64(header.Size), io.SeekCurrent)
if err != nil {
return "", fmt.Errorf("error seeking to end of ID3V2 header: %v", err)
}
n, err := sizeToEndOffset(r, 128)
if err != nil {
return "", fmt.Errorf("error determining read size to ID3v1 header: %v", err)
}
// TODO: remove this check?????
if n < 0 {
return "", fmt.Errorf("file size must be greater than 128 bytes for MP3: %v bytes", n)
}
h := sha1.New()
_, err = io.CopyN(h, r, n)
if err != nil {
return "", fmt.Errorf("error reading %v bytes: %v", n, err)
}
return hashSum(h), nil
}
// SumFLAC costructs a checksum of the FLAC audio file data provided by the io.ReadSeeker (ignores
// metadata fields).
func SumFLAC(r io.ReadSeeker) (string, error) {
flac, err := readString(r, 4)
if err != nil {
return "", err
}
if flac != "fLaC" {
return "", errors.New("expected 'fLaC'")
}
for {
last, err := skipFLACMetadataBlock(r)
if err != nil {
return "", err
}
if last {
break
}
}
h := sha1.New()
_, err = io.Copy(h, r)
if err != nil {
return "", fmt.Errorf("error reading data bytes from FLAC: %v", err)
}
return hashSum(h), nil
}
func skipFLACMetadataBlock(r io.ReadSeeker) (last bool, err error) {
blockHeader, err := readBytes(r, 1)
if err != nil {
return
}
if getBit(blockHeader[0], 7) {
blockHeader[0] ^= (1 << 7)
last = true
}
blockLen, err := readInt(r, 3)
if err != nil {
return
}
_, err = r.Seek(int64(blockLen), io.SeekCurrent)
return
}
func hashSum(h hash.Hash) string {
return fmt.Sprintf("%x", h.Sum([]byte{}))
}

147
vendor/github.com/dhowden/tag/tag.go generated vendored Normal file
View File

@@ -0,0 +1,147 @@
// 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 provides MP3 (ID3: v1, 2.2, 2.3 and 2.4), MP4, FLAC and OGG metadata detection,
// 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).
package tag
import (
"errors"
"fmt"
"io"
)
// ErrNoTagsFound is the error returned by ReadFrom when the metadata format
// cannot be identified.
var ErrNoTagsFound = errors.New("no tags found")
// ReadFrom detects and parses audio file metadata tags (currently supports ID3v1,2.{2,3,4}, MP4, FLAC/OGG).
// Returns non-nil error if the format of the given data could not be determined, or if there was a problem
// parsing the data.
func ReadFrom(r io.ReadSeeker) (Metadata, error) {
b, err := readBytes(r, 11)
if err != nil {
return nil, err
}
_, err = r.Seek(-11, io.SeekCurrent)
if err != nil {
return nil, fmt.Errorf("could not seek back to original position: %v", err)
}
switch {
case string(b[0:4]) == "fLaC":
return ReadFLACTags(r)
case string(b[0:4]) == "OggS":
return ReadOGGTags(r)
case string(b[4:8]) == "ftyp":
return ReadAtoms(r)
case string(b[0:3]) == "ID3":
return ReadID3v2Tags(r)
case string(b[0:4]) == "DSD ":
return ReadDSFTags(r)
}
m, err := ReadID3v1Tags(r)
if err != nil {
if err == ErrNotID3v1 {
err = ErrNoTagsFound
}
return nil, err
}
return m, nil
}
// Format is an enumeration of metadata types supported by this package.
type Format string
// Supported tag formats.
const (
UnknownFormat Format = "" // Unknown Format.
ID3v1 Format = "ID3v1" // ID3v1 tag format.
ID3v2_2 Format = "ID3v2.2" // ID3v2.2 tag format.
ID3v2_3 Format = "ID3v2.3" // ID3v2.3 tag format (most common).
ID3v2_4 Format = "ID3v2.4" // ID3v2.4 tag format.
MP4 Format = "MP4" // MP4 tag (atom) format (see http://www.ftyps.com/ for a full file type list)
VORBIS Format = "VORBIS" // Vorbis Comment tag format.
)
// FileType is an enumeration of the audio file types supported by this package, in particular
// there are audio file types which share metadata formats, and this type is used to distinguish
// between them.
type FileType string
// Supported file types.
const (
UnknownFileType FileType = "" // Unknown FileType.
MP3 FileType = "MP3" // MP3 file
M4A FileType = "M4A" // M4A file Apple iTunes (ACC) Audio
M4B FileType = "M4B" // M4A file Apple iTunes (ACC) Audio Book
M4P FileType = "M4P" // M4A file Apple iTunes (ACC) AES Protected Audio
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.
type Metadata interface {
// Format returns the metadata Format used to encode the data.
Format() Format
// FileType returns the file type of the audio file.
FileType() FileType
// Title returns the title of the track.
Title() string
// Album returns the album name of the track.
Album() string
// Artist returns the artist name of the track.
Artist() string
// AlbumArtist returns the album artist name of the track.
AlbumArtist() string
// Composer returns the composer of the track.
Composer() string
// Year returns the year of the track.
Year() int
// Genre returns the genre of the track.
Genre() string
// Track returns the track number and total tracks, or zero values if unavailable.
Track() (int, int)
// Disc returns the disc number and total discs, or zero values if unavailable.
Disc() (int, int)
// Picture returns a picture, or nil if not available.
Picture() *Picture
// 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{}
}

81
vendor/github.com/dhowden/tag/util.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
// 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 (
"encoding/binary"
"io"
)
func getBit(b byte, n uint) bool {
x := byte(1 << n)
return (b & x) == x
}
func get7BitChunkedInt(b []byte) int {
var n int
for _, x := range b {
n = n << 7
n |= int(x)
}
return n
}
func getInt(b []byte) int {
var n int
for _, x := range b {
n = n << 8
n |= int(x)
}
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)
if err != nil {
return nil, err
}
return b, nil
}
func readString(r io.Reader, n int) (string, error) {
b, err := readBytes(r, n)
if err != nil {
return "", err
}
return string(b), nil
}
func readInt(r io.Reader, n int) (int, error) {
b, err := readBytes(r, n)
if err != nil {
return 0, err
}
return getInt(b), nil
}
func read7BitChunkedInt(r io.Reader, n int) (int, error) {
b, err := readBytes(r, n)
if err != nil {
return 0, err
}
return get7BitChunkedInt(b), nil
}
func readInt32LittleEndian(r io.Reader) (int, error) {
var n int32
err := binary.Read(r, binary.LittleEndian, &n)
return int(n), err
}

255
vendor/github.com/dhowden/tag/vorbis.go generated vendored Normal file
View File

@@ -0,0 +1,255 @@
// 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"
"fmt"
"io"
"strconv"
"strings"
"time"
)
func newMetadataVorbis() *metadataVorbis {
return &metadataVorbis{
c: make(map[string]string),
}
}
type metadataVorbis struct {
c map[string]string // the vorbis comments
p *Picture
}
func (m *metadataVorbis) readVorbisComment(r io.Reader) error {
vendorLen, err := readInt32LittleEndian(r)
if err != nil {
return err
}
if vendorLen < 0 {
return fmt.Errorf("invalid encoding: expected positive length, got %d", vendorLen)
}
vendor, err := readString(r, vendorLen)
if err != nil {
return err
}
m.c["vendor"] = vendor
commentsLen, err := readInt32LittleEndian(r)
if err != nil {
return err
}
for i := 0; i < commentsLen; i++ {
l, err := readInt32LittleEndian(r)
if err != nil {
return err
}
s, err := readString(r, l)
if err != nil {
return err
}
k, v, err := parseComment(s)
if err != nil {
return err
}
m.c[strings.ToLower(k)] = v
}
return nil
}
func (m *metadataVorbis) readPictureBlock(r io.Reader) error {
b, err := readInt(r, 4)
if err != nil {
return err
}
pictureType, ok := pictureTypes[byte(b)]
if !ok {
return fmt.Errorf("invalid picture type: %v", b)
}
mimeLen, err := readInt(r, 4)
if err != nil {
return err
}
mime, err := readString(r, mimeLen)
if err != nil {
return err
}
ext := ""
switch mime {
case "image/jpeg":
ext = "jpg"
case "image/png":
ext = "png"
case "image/gif":
ext = "gif"
}
descLen, err := readInt(r, 4)
if err != nil {
return err
}
desc, err := readString(r, descLen)
if err != nil {
return err
}
// We skip width <32>, height <32>, colorDepth <32>, coloresUsed <32>
_, err = readInt(r, 4) // width
if err != nil {
return err
}
_, err = readInt(r, 4) // height
if err != nil {
return err
}
_, err = readInt(r, 4) // color depth
if err != nil {
return err
}
_, err = readInt(r, 4) // colors used
if err != nil {
return err
}
dataLen, err := readInt(r, 4)
if err != nil {
return err
}
data := make([]byte, dataLen)
_, err = io.ReadFull(r, data)
if err != nil {
return err
}
m.p = &Picture{
Ext: ext,
MIMEType: mime,
Type: pictureType,
Description: desc,
Data: data,
}
return nil
}
func parseComment(c string) (k, v string, err error) {
kv := strings.SplitN(c, "=", 2)
if len(kv) != 2 {
err = errors.New("vorbis comment must contain '='")
return
}
k = kv[0]
v = kv[1]
return
}
func (m *metadataVorbis) Format() Format {
return VORBIS
}
func (m *metadataVorbis) Raw() map[string]interface{} {
raw := make(map[string]interface{}, len(m.c))
for k, v := range m.c {
raw[k] = v
}
return raw
}
func (m *metadataVorbis) Title() string {
return m.c["title"]
}
func (m *metadataVorbis) Artist() string {
// PERFORMER
// The artist(s) who performed the work. In classical music this would be the
// conductor, orchestra, soloists. In an audio book it would be the actor who
// did the reading. In popular music this is typically the same as the ARTIST
// and is omitted.
if m.c["performer"] != "" {
return m.c["performer"]
}
return m.c["artist"]
}
func (m *metadataVorbis) Album() string {
return m.c["album"]
}
func (m *metadataVorbis) AlbumArtist() string {
// This field isn't actually included in the standard, though
// it is commonly assigned to albumartist.
return m.c["albumartist"]
}
func (m *metadataVorbis) Composer() string {
// ARTIST
// The artist generally considered responsible for the work. In popular music
// this is usually the performing band or singer. For classical music it would
// be the composer. For an audio book it would be the author of the original text.
if m.c["composer"] != "" {
return m.c["composer"]
}
if m.c["performer"] == "" {
return ""
}
return m.c["artist"]
}
func (m *metadataVorbis) Genre() string {
return m.c["genre"]
}
func (m *metadataVorbis) Year() int {
var dateFormat string
// The date need to follow the international standard https://en.wikipedia.org/wiki/ISO_8601
// and obviously the VorbisComment standard https://wiki.xiph.org/VorbisComment#Date_and_time
switch len(m.c["date"]) {
case 0:
return 0
case 4:
dateFormat = "2006"
case 7:
dateFormat = "2006-01"
case 10:
dateFormat = "2006-01-02"
}
t, _ := time.Parse(dateFormat, m.c["date"])
return t.Year()
}
func (m *metadataVorbis) Track() (int, int) {
x, _ := strconv.Atoi(m.c["tracknumber"])
// https://wiki.xiph.org/Field_names
n, _ := strconv.Atoi(m.c["tracktotal"])
return x, n
}
func (m *metadataVorbis) Disc() (int, int) {
// https://wiki.xiph.org/Field_names
x, _ := strconv.Atoi(m.c["discnumber"])
n, _ := strconv.Atoi(m.c["disctotal"])
return x, n
}
func (m *metadataVorbis) Lyrics() string {
return m.c["lyrics"]
}
func (m *metadataVorbis) Comment() string {
if m.c["comment"] != "" {
return m.c["comment"]
}
return m.c["description"]
}
func (m *metadataVorbis) Picture() *Picture {
return m.p
}

2
vendor/modules.txt vendored
View File

@@ -1,3 +1,5 @@
# github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca
github.com/dhowden/tag
# github.com/jinzhu/gorm v1.9.2 # github.com/jinzhu/gorm v1.9.2
github.com/jinzhu/gorm github.com/jinzhu/gorm
github.com/jinzhu/gorm/dialects/sqlite github.com/jinzhu/gorm/dialects/sqlite