feat: mp3 tagging
This commit is contained in:
parent
c4606a55e2
commit
9efd333e79
2
go.mod
2
go.mod
@ -4,6 +4,7 @@ go 1.17
|
||||
|
||||
require (
|
||||
github.com/gocolly/colly/v2 v2.1.0
|
||||
github.com/mikkyang/id3-go v0.0.0-20191012064224-2c6ab3bb1fbd
|
||||
go.xsfx.dev/workgroups v0.2.0
|
||||
)
|
||||
|
||||
@ -13,6 +14,7 @@ require (
|
||||
github.com/antchfx/htmlquery v1.2.3 // indirect
|
||||
github.com/antchfx/xmlquery v1.2.4 // indirect
|
||||
github.com/antchfx/xpath v1.1.8 // indirect
|
||||
github.com/djimenez/iconv-go v0.0.0-20160305225143-8960e66bd3da // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -249,6 +249,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/djimenez/iconv-go v0.0.0-20160305225143-8960e66bd3da h1:0qwwqQCLOOXPl58ljnq3sTJR7yRuMolM02vjxDh4ZVE=
|
||||
github.com/djimenez/iconv-go v0.0.0-20160305225143-8960e66bd3da/go.mod h1:ns+zIWBBchgfRdxNgIJWn2x6U95LQchxeqiN5Cgdgts=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
@ -630,6 +632,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mikkyang/id3-go v0.0.0-20191012064224-2c6ab3bb1fbd h1:Cqivkwpk34qJJsi0xbZp2TOhpMsG381iaum8mb+6T/s=
|
||||
github.com/mikkyang/id3-go v0.0.0-20191012064224-2c6ab3bb1fbd/go.mod h1:6ReX25kzt2D67Dt9vH3kTm8R4luFEfW9W3RDuytp0IA=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
|
||||
|
30
main.go
30
main.go
@ -15,11 +15,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gocolly/colly/v2"
|
||||
id3 "github.com/mikkyang/id3-go"
|
||||
"go.xsfx.dev/workgroups"
|
||||
)
|
||||
|
||||
const (
|
||||
URL = "https://susallefolgen.netlify.app/"
|
||||
Artist = "Sanft & Sorgfältig"
|
||||
)
|
||||
|
||||
type Episode struct {
|
||||
@ -62,7 +64,9 @@ func (e *Episode) Download() error {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.Create(fmt.Sprintf("%d.mp3", e.Number))
|
||||
e.file = fmt.Sprintf("%d.mp3", e.Number)
|
||||
|
||||
f, err := os.Create(e.file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create file: %w", err)
|
||||
}
|
||||
@ -75,6 +79,22 @@ func (e *Episode) Download() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Episode) Tag() error {
|
||||
f, err := id3.Open(e.file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
title := fmt.Sprintf("#%03d %s", e.Number, e.Title)
|
||||
|
||||
f.SetArtist(Artist)
|
||||
f.SetAlbum(title)
|
||||
f.SetTitle(title)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func main() {
|
||||
c := colly.NewCollector()
|
||||
@ -118,12 +138,12 @@ func main() {
|
||||
d, ctx := workgroups.NewDispatcher(
|
||||
context.Background(),
|
||||
runtime.GOMAXPROCS(0),
|
||||
len(episodes.Episodes),
|
||||
len(episodes.Episodes[:1]),
|
||||
)
|
||||
|
||||
d.Start()
|
||||
|
||||
for _, ep := range episodes.Episodes {
|
||||
for _, ep := range episodes.Episodes[:1] {
|
||||
ep := ep
|
||||
|
||||
d.Append(workgroups.NewJob(ctx, func(ctx context.Context) error {
|
||||
@ -137,6 +157,10 @@ func main() {
|
||||
return fmt.Errorf("could not download: %w", err)
|
||||
}
|
||||
|
||||
if err := ep.Tag(); err != nil {
|
||||
return fmt.Errorf("could not tag: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
22
vendor/github.com/djimenez/iconv-go/LICENSE
generated
vendored
Normal file
22
vendor/github.com/djimenez/iconv-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013, Donovan Jimenez
|
||||
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.
|
111
vendor/github.com/djimenez/iconv-go/README.md
generated
vendored
Normal file
111
vendor/github.com/djimenez/iconv-go/README.md
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
# Install
|
||||
|
||||
The main method of installation is through "go get" (provided in $GOROOT/bin)
|
||||
|
||||
go get github.com/djimenez/iconv-go
|
||||
|
||||
This both downloads from github and installs the package into your $GOPATH. To just
|
||||
recompile the package after you've already "get"ed (e.g. for a new go version),
|
||||
use "go install" instead.
|
||||
|
||||
go install github.com/djimenez/iconv-go
|
||||
|
||||
See documentation for "go get" and "go install" for more information.
|
||||
|
||||
__PLEASE NOTE__ that this package requires the use of cgo, since it is only a wrapper around iconv - either provided by libiconv or glibc on your system. Attempts to build without cgo enabled will fail.
|
||||
|
||||
# Usage
|
||||
|
||||
To use the package, you'll need the appropriate import statement:
|
||||
|
||||
import (
|
||||
iconv "github.com/djimenez/iconv-go"
|
||||
)
|
||||
|
||||
## Converting string Values
|
||||
|
||||
Converting a string can be done with two methods. First, there's
|
||||
iconv.ConvertString(input, fromEncoding, toEncoding string)
|
||||
|
||||
output,_ := iconv.ConvertString("Hello World!", "utf-8", "windows-1252")
|
||||
|
||||
Alternatively, you can create a converter and use its ConvertString method.
|
||||
Reuse of a Converter instance is recommended when doing many string conversions
|
||||
between the same encodings.
|
||||
|
||||
converter := iconv.NewConverter("utf-8", "windows-1252")
|
||||
output,_ := converter.ConvertString("Hello World!")
|
||||
|
||||
// converter can then be closed explicitly
|
||||
// this will also happen when garbage collected
|
||||
converter.Close()
|
||||
|
||||
ConvertString may return errors for the following reasons:
|
||||
|
||||
* EINVAL - when either the from or to encoding is not supported by iconv
|
||||
* EILSEQ - when the input string contains an invalid byte sequence for the
|
||||
given from encoding
|
||||
|
||||
## Converting []byte Values
|
||||
|
||||
Converting a []byte can similarly be done with two methods. First, there's
|
||||
iconv.Convert(input, output []byte, fromEncoding, toEncoding string). You'll
|
||||
immediately notice this requires you to give it both the input and output
|
||||
buffer. Ideally, the output buffer should be sized so that it can hold all
|
||||
converted bytes from input, but if it cannot, then Convert will put as many
|
||||
bytes as it can into the buffer without creating an invalid sequence. For
|
||||
example, if iconv only has a single byte left in the output buffer but needs 2
|
||||
or more for the complete character in a multibyte encoding it will stop writing
|
||||
to the buffer and return with an iconv.E2BIG error.
|
||||
|
||||
in := []byte("Hello World!")
|
||||
out := make([]byte, len(input))
|
||||
|
||||
bytesRead, bytesWritten, err := iconv.Convert(in, out, "utf-8", "latin1")
|
||||
|
||||
Just like with ConvertString, there is also a Convert method on Converter that
|
||||
can be used.
|
||||
|
||||
...
|
||||
converter := iconv.NewConverter("utf-8", "windows-1252")
|
||||
|
||||
bytesRead, bytesWritten, error := converter.Convert(input, output)
|
||||
|
||||
Convert may return errors for the following reasons:
|
||||
|
||||
* EINVAL - when either the from or to encoding is not supported by iconv
|
||||
* EILSEQ - when the input string contains an invalid byte sequence for the
|
||||
given from encoding
|
||||
* E2BIG - when the output buffer is not big enough to hold the full
|
||||
conversion of input
|
||||
|
||||
Note on E2BIG: this is a common error value especially when converting to a
|
||||
multibyte encoding and should not be considered fatal. Partial conversion
|
||||
has probably occurred be sure to check bytesRead and bytesWritten.
|
||||
|
||||
### Note on Shift Based Encodings
|
||||
|
||||
When using iconv.Convert convenience method it will automatically try to append
|
||||
to your output buffer with a nil input so that any end shift sequences are
|
||||
appropiately written. Using a Converter.Convert method however will not
|
||||
automatically do this since it can be used to process a full stream in chunks.
|
||||
So you'll need to remember to pass a nil input buffer at the end yourself, just
|
||||
like you would with direct iconv usage.
|
||||
|
||||
## Converting an \*io.Reader
|
||||
|
||||
The iconv.Reader allows any other \*io.Reader to be wrapped and have its bytes
|
||||
transcoded as they are read.
|
||||
|
||||
// We're wrapping stdin for simplicity, but a File or network reader could
|
||||
// be wrapped as well
|
||||
reader,_ := iconv.NewReader(os.Stdin, "utf-8", "windows-1252")
|
||||
|
||||
## Converting an \*io.Writer
|
||||
|
||||
The iconv.Writer allows any other \*io.Writer to be wrapped and have its bytes
|
||||
transcoded as they are written.
|
||||
|
||||
// We're wrapping stdout for simplicity, but a File or network reader could
|
||||
// be wrapped as well
|
||||
writer,_ := iconv.NewWriter(os.Stdout, "utf-8", "windows-1252")
|
168
vendor/github.com/djimenez/iconv-go/converter.go
generated
vendored
Normal file
168
vendor/github.com/djimenez/iconv-go/converter.go
generated
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
package iconv
|
||||
|
||||
/*
|
||||
#cgo darwin LDFLAGS: -liconv
|
||||
#cgo freebsd LDFLAGS: -liconv
|
||||
#cgo windows LDFLAGS: -liconv
|
||||
#include <stdlib.h>
|
||||
#include <iconv.h>
|
||||
|
||||
// As of GO 1.6 passing a pointer to Go pointer, will lead to panic
|
||||
// Therofore we use this wrapper function, to avoid passing **char directly from go
|
||||
size_t call_iconv(iconv_t ctx, char *in, size_t *size_in, char *out, size_t *size_out){
|
||||
return iconv(ctx, &in, size_in, &out, size_out);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
type Converter struct {
|
||||
context C.iconv_t
|
||||
open bool
|
||||
}
|
||||
|
||||
// Initialize a new Converter. If fromEncoding or toEncoding are not supported by
|
||||
// iconv then an EINVAL error will be returned. An ENOMEM error maybe returned if
|
||||
// there is not enough memory to initialize an iconv descriptor
|
||||
func NewConverter(fromEncoding string, toEncoding string) (converter *Converter, err error) {
|
||||
converter = new(Converter)
|
||||
|
||||
// convert to C strings
|
||||
toEncodingC := C.CString(toEncoding)
|
||||
fromEncodingC := C.CString(fromEncoding)
|
||||
|
||||
// open an iconv descriptor
|
||||
converter.context, err = C.iconv_open(toEncodingC, fromEncodingC)
|
||||
|
||||
// free the C Strings
|
||||
C.free(unsafe.Pointer(toEncodingC))
|
||||
C.free(unsafe.Pointer(fromEncodingC))
|
||||
|
||||
// check err
|
||||
if err == nil {
|
||||
// no error, mark the context as open
|
||||
converter.open = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// destroy is called during garbage collection
|
||||
func (this *Converter) destroy() {
|
||||
this.Close()
|
||||
}
|
||||
|
||||
// Close a Converter's iconv description explicitly
|
||||
func (this *Converter) Close() (err error) {
|
||||
if this.open {
|
||||
_, err = C.iconv_close(this.context)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Convert bytes from an input byte slice into a give output byte slice
|
||||
//
|
||||
// As many bytes that can converted and fit into the size of output will be
|
||||
// processed and the number of bytes read for input as well as the number of
|
||||
// bytes written to output will be returned. If not all converted bytes can fit
|
||||
// into output and E2BIG error will also be returned. If input contains an invalid
|
||||
// sequence of bytes for the Converter's fromEncoding an EILSEQ error will be returned
|
||||
//
|
||||
// For shift based output encodings, any end shift byte sequences can be generated by
|
||||
// passing a 0 length byte slice as input. Also passing a 0 length byte slice for output
|
||||
// will simply reset the iconv descriptor shift state without writing any bytes.
|
||||
func (this *Converter) Convert(input []byte, output []byte) (bytesRead int, bytesWritten int, err error) {
|
||||
// make sure we are still open
|
||||
if this.open {
|
||||
inputLeft := C.size_t(len(input))
|
||||
outputLeft := C.size_t(len(output))
|
||||
|
||||
if inputLeft > 0 && outputLeft > 0 {
|
||||
// we have to give iconv a pointer to a pointer of the underlying
|
||||
// storage of each byte slice - so far this is the simplest
|
||||
// way i've found to do that in Go, but it seems ugly
|
||||
inputPointer := (*C.char)(unsafe.Pointer(&input[0]))
|
||||
outputPointer := (*C.char)(unsafe.Pointer(&output[0]))
|
||||
|
||||
_, err = C.call_iconv(this.context, inputPointer, &inputLeft, outputPointer, &outputLeft)
|
||||
|
||||
// update byte counters
|
||||
bytesRead = len(input) - int(inputLeft)
|
||||
bytesWritten = len(output) - int(outputLeft)
|
||||
} else if inputLeft == 0 && outputLeft > 0 {
|
||||
// inputPointer will be nil, outputPointer is generated as above
|
||||
outputPointer := (*C.char)(unsafe.Pointer(&output[0]))
|
||||
|
||||
_, err = C.call_iconv(this.context, nil, &inputLeft, outputPointer, &outputLeft)
|
||||
|
||||
// update write byte counter
|
||||
bytesWritten = len(output) - int(outputLeft)
|
||||
} else {
|
||||
// both input and output are zero length, do a shift state reset
|
||||
_, err = C.call_iconv(this.context, nil, &inputLeft, nil, &outputLeft)
|
||||
}
|
||||
} else {
|
||||
err = syscall.EBADF
|
||||
}
|
||||
|
||||
return bytesRead, bytesWritten, err
|
||||
}
|
||||
|
||||
// Convert an input string
|
||||
//
|
||||
// EILSEQ error may be returned if input contains invalid bytes for the
|
||||
// Converter's fromEncoding.
|
||||
func (this *Converter) ConvertString(input string) (output string, err error) {
|
||||
// make sure we are still open
|
||||
if this.open {
|
||||
// construct the buffers
|
||||
inputBuffer := []byte(input)
|
||||
outputBuffer := make([]byte, len(inputBuffer)*2) // we use a larger buffer to help avoid resizing later
|
||||
|
||||
// call Convert until all input bytes are read or an error occurs
|
||||
var bytesRead, totalBytesRead, bytesWritten, totalBytesWritten int
|
||||
|
||||
for totalBytesRead < len(inputBuffer) && err == nil {
|
||||
// use the totals to create buffer slices
|
||||
bytesRead, bytesWritten, err = this.Convert(inputBuffer[totalBytesRead:], outputBuffer[totalBytesWritten:])
|
||||
|
||||
totalBytesRead += bytesRead
|
||||
totalBytesWritten += bytesWritten
|
||||
|
||||
// check for the E2BIG error specifically, we can add to the output
|
||||
// buffer to correct for it and then continue
|
||||
if err == syscall.E2BIG {
|
||||
// increase the size of the output buffer by another input length
|
||||
// first, create a new buffer
|
||||
tempBuffer := make([]byte, len(outputBuffer)+len(inputBuffer))
|
||||
|
||||
// copy the existing data
|
||||
copy(tempBuffer, outputBuffer)
|
||||
|
||||
// switch the buffers
|
||||
outputBuffer = tempBuffer
|
||||
|
||||
// forget the error
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// perform a final shift state reset
|
||||
_, bytesWritten, err = this.Convert([]byte{}, outputBuffer[totalBytesWritten:])
|
||||
|
||||
// update total count
|
||||
totalBytesWritten += bytesWritten
|
||||
}
|
||||
|
||||
// construct the final output string
|
||||
output = string(outputBuffer[:totalBytesWritten])
|
||||
} else {
|
||||
err = syscall.EBADF
|
||||
}
|
||||
|
||||
return output, err
|
||||
}
|
48
vendor/github.com/djimenez/iconv-go/iconv.go
generated
vendored
Normal file
48
vendor/github.com/djimenez/iconv-go/iconv.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Wraps the iconv API present on most systems, which allows for conversion
|
||||
of bytes from one encoding to another. This package additionally provides
|
||||
some convenient interface implementations like a Reader and Writer.
|
||||
*/
|
||||
package iconv
|
||||
|
||||
// All in one Convert method, rather than requiring the construction of an iconv.Converter
|
||||
func Convert(input []byte, output []byte, fromEncoding string, toEncoding string) (bytesRead int, bytesWritten int, err error) {
|
||||
// create a temporary converter
|
||||
converter, err := NewConverter(fromEncoding, toEncoding)
|
||||
|
||||
if err == nil {
|
||||
// call converter's Convert
|
||||
bytesRead, bytesWritten, err = converter.Convert(input, output)
|
||||
|
||||
if err == nil {
|
||||
var shiftBytesWritten int
|
||||
|
||||
// call Convert with a nil input to generate any end shift sequences
|
||||
_, shiftBytesWritten, err = converter.Convert(nil, output[bytesWritten:])
|
||||
|
||||
// add shift bytes to total bytes
|
||||
bytesWritten += shiftBytesWritten
|
||||
}
|
||||
|
||||
// close the converter
|
||||
converter.Close()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// All in one ConvertString method, rather than requiring the construction of an iconv.Converter
|
||||
func ConvertString(input string, fromEncoding string, toEncoding string) (output string, err error) {
|
||||
// create a temporary converter
|
||||
converter, err := NewConverter(fromEncoding, toEncoding)
|
||||
|
||||
if err == nil {
|
||||
// convert the string
|
||||
output, err = converter.ConvertString(input)
|
||||
|
||||
// close the converter
|
||||
converter.Close()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
100
vendor/github.com/djimenez/iconv-go/reader.go
generated
vendored
Normal file
100
vendor/github.com/djimenez/iconv-go/reader.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
package iconv
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
source io.Reader
|
||||
converter *Converter
|
||||
buffer []byte
|
||||
readPos, writePos int
|
||||
err error
|
||||
}
|
||||
|
||||
func NewReader(source io.Reader, fromEncoding string, toEncoding string) (*Reader, error) {
|
||||
// create a converter
|
||||
converter, err := NewConverter(fromEncoding, toEncoding)
|
||||
|
||||
if err == nil {
|
||||
return NewReaderFromConverter(source, converter), err
|
||||
}
|
||||
|
||||
// return the error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func NewReaderFromConverter(source io.Reader, converter *Converter) (reader *Reader) {
|
||||
reader = new(Reader)
|
||||
|
||||
// copy elements
|
||||
reader.source = source
|
||||
reader.converter = converter
|
||||
|
||||
// create 8K buffers
|
||||
reader.buffer = make([]byte, 8*1024)
|
||||
|
||||
return reader
|
||||
}
|
||||
|
||||
func (this *Reader) fillBuffer() {
|
||||
// slide existing data to beginning
|
||||
if this.readPos > 0 {
|
||||
// copy current bytes - is this guaranteed safe?
|
||||
copy(this.buffer, this.buffer[this.readPos:this.writePos])
|
||||
|
||||
// adjust positions
|
||||
this.writePos -= this.readPos
|
||||
this.readPos = 0
|
||||
}
|
||||
|
||||
// read new data into buffer at write position
|
||||
bytesRead, err := this.source.Read(this.buffer[this.writePos:])
|
||||
|
||||
// adjust write position
|
||||
this.writePos += bytesRead
|
||||
|
||||
// track any reader error / EOF
|
||||
if err != nil {
|
||||
this.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// implement the io.Reader interface
|
||||
func (this *Reader) Read(p []byte) (n int, err error) {
|
||||
// checks for when we have no data
|
||||
for this.writePos == 0 || this.readPos == this.writePos {
|
||||
// if we have an error / EOF, just return it
|
||||
if this.err != nil {
|
||||
return n, this.err
|
||||
}
|
||||
|
||||
// else, fill our buffer
|
||||
this.fillBuffer()
|
||||
}
|
||||
|
||||
// TODO: checks for when we have less data than len(p)
|
||||
|
||||
// we should have an appropriate amount of data, convert it into the given buffer
|
||||
bytesRead, bytesWritten, err := this.converter.Convert(this.buffer[this.readPos:this.writePos], p)
|
||||
|
||||
// adjust byte counters
|
||||
this.readPos += bytesRead
|
||||
n += bytesWritten
|
||||
|
||||
// if we experienced an iconv error, check it
|
||||
if err != nil {
|
||||
// E2BIG errors can be ignored (we'll get them often) as long
|
||||
// as at least 1 byte was written. If we experienced an E2BIG
|
||||
// and no bytes were written then the buffer is too small for
|
||||
// even the next character
|
||||
if err != syscall.E2BIG || bytesWritten == 0 {
|
||||
// track anything else
|
||||
this.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// return our results
|
||||
return n, this.err
|
||||
}
|
82
vendor/github.com/djimenez/iconv-go/writer.go
generated
vendored
Normal file
82
vendor/github.com/djimenez/iconv-go/writer.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
package iconv
|
||||
|
||||
import "io"
|
||||
|
||||
type Writer struct {
|
||||
destination io.Writer
|
||||
converter *Converter
|
||||
buffer []byte
|
||||
readPos, writePos int
|
||||
err error
|
||||
}
|
||||
|
||||
func NewWriter(destination io.Writer, fromEncoding string, toEncoding string) (*Writer, error) {
|
||||
// create a converter
|
||||
converter, err := NewConverter(fromEncoding, toEncoding)
|
||||
|
||||
if err == nil {
|
||||
return NewWriterFromConverter(destination, converter), err
|
||||
}
|
||||
|
||||
// return the error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func NewWriterFromConverter(destination io.Writer, converter *Converter) (writer *Writer) {
|
||||
writer = new(Writer)
|
||||
|
||||
// copy elements
|
||||
writer.destination = destination
|
||||
writer.converter = converter
|
||||
|
||||
// create 8K buffers
|
||||
writer.buffer = make([]byte, 8*1024)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (this *Writer) emptyBuffer() {
|
||||
// write new data out of buffer
|
||||
bytesWritten, err := this.destination.Write(this.buffer[this.readPos:this.writePos])
|
||||
|
||||
// update read position
|
||||
this.readPos += bytesWritten
|
||||
|
||||
// slide existing data to beginning
|
||||
if this.readPos > 0 {
|
||||
// copy current bytes - is this guaranteed safe?
|
||||
copy(this.buffer, this.buffer[this.readPos:this.writePos])
|
||||
|
||||
// adjust positions
|
||||
this.writePos -= this.readPos
|
||||
this.readPos = 0
|
||||
}
|
||||
|
||||
// track any reader error / EOF
|
||||
if err != nil {
|
||||
this.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// implement the io.Writer interface
|
||||
func (this *Writer) Write(p []byte) (n int, err error) {
|
||||
// write data into our internal buffer
|
||||
bytesRead, bytesWritten, err := this.converter.Convert(p, this.buffer[this.writePos:])
|
||||
|
||||
// update bytes written for return
|
||||
n += bytesRead
|
||||
this.writePos += bytesWritten
|
||||
|
||||
// checks for when we have a full buffer
|
||||
for this.writePos > 0 {
|
||||
// if we have an error, just return it
|
||||
if this.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// else empty the buffer
|
||||
this.emptyBuffer()
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
4
vendor/github.com/mikkyang/id3-go/.travis.yml
generated
vendored
Normal file
4
vendor/github.com/mikkyang/id3-go/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.11
|
||||
- 1.12
|
22
vendor/github.com/mikkyang/id3-go/LICENSE
generated
vendored
Normal file
22
vendor/github.com/mikkyang/id3-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2013 Michael Yang <mikkyangg@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
85
vendor/github.com/mikkyang/id3-go/README.md
generated
vendored
Normal file
85
vendor/github.com/mikkyang/id3-go/README.md
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
# id3
|
||||
|
||||
[![build status](https://travis-ci.org/mikkyang/id3-go.svg)](https://travis-ci.org/mikkyang/id3-go)
|
||||
|
||||
ID3 library for Go.
|
||||
|
||||
Supported formats:
|
||||
|
||||
* ID3v1
|
||||
* ID3v2.2
|
||||
* ID3v2.3
|
||||
|
||||
# Install
|
||||
|
||||
The platform ($GOROOT/bin) "go get" tool is the best method to install.
|
||||
|
||||
go get github.com/mikkyang/id3-go
|
||||
|
||||
This downloads and installs the package into your $GOPATH. If you only want to
|
||||
recompile, use "go install".
|
||||
|
||||
go install github.com/mikkyang/id3-go
|
||||
|
||||
# Usage
|
||||
|
||||
An import allows access to the package.
|
||||
|
||||
import (
|
||||
id3 "github.com/mikkyang/id3-go"
|
||||
)
|
||||
|
||||
Version specific details can be accessed through the subpackages.
|
||||
|
||||
import (
|
||||
"github.com/mikkyang/id3-go/v1"
|
||||
"github.com/mikkyang/id3-go/v2"
|
||||
)
|
||||
|
||||
# Quick Start
|
||||
|
||||
To access the tag of a file, first open the file using the package's `Open`
|
||||
function.
|
||||
|
||||
mp3File, err := id3.Open("All-In.mp3")
|
||||
|
||||
It's also a good idea to ensure that the file is closed using `defer`.
|
||||
|
||||
defer mp3File.Close()
|
||||
|
||||
## Accessing Information
|
||||
|
||||
Some commonly used data have methods in the tag for easier access. These
|
||||
methods are for `Title`, `Artist`, `Album`, `Year`, `Genre`, and `Comments`.
|
||||
|
||||
mp3File.SetArtist("Okasian")
|
||||
fmt.Println(mp3File.Artist())
|
||||
|
||||
# ID3v2 Frames
|
||||
|
||||
v2 Frames can be accessed directly by using the `Frame` or `Frames` method
|
||||
of the file, which return the first frame or a slice of frames as `Framer`
|
||||
interfaces. These interfaces allow read access to general details of the file.
|
||||
|
||||
lyricsFrame := mp3File.Frame("USLT")
|
||||
lyrics := lyricsFrame.String()
|
||||
|
||||
If more specific information is needed, or frame-specific write access is
|
||||
needed, then the interface must be cast into the appropriate underlying type.
|
||||
The example provided does not check for errors, but it is recommended to do
|
||||
so.
|
||||
|
||||
lyricsFrame := mp3File.Frame("USLT").(*v2.UnsynchTextFrame)
|
||||
|
||||
## Adding Frames
|
||||
|
||||
For common fields, a frame will automatically be created with the `Set` method.
|
||||
For other frames or more fine-grained control, frames can be created with the
|
||||
corresponding constructor, usually prefixed by `New`. These constructors require
|
||||
the first argument to be a FrameType struct, which are global variables named by
|
||||
version.
|
||||
|
||||
ft := V23FrameTypeMap["TIT2"]
|
||||
text := "Hello"
|
||||
textFrame := NewTextFrame(ft, text)
|
||||
mp3File.AddFrames(textFrame)
|
87
vendor/github.com/mikkyang/id3-go/encodedbytes/reader.go
generated
vendored
Normal file
87
vendor/github.com/mikkyang/id3-go/encodedbytes/reader.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package encodedbytes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Reader is a helper for Reading frame bytes
|
||||
type Reader struct {
|
||||
data []byte
|
||||
index int // current Reading index
|
||||
}
|
||||
|
||||
func (r *Reader) Read(b []byte) (n int, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if r.index >= len(r.data) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(b, r.data[r.index:])
|
||||
r.index += n
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadByte() (b byte, err error) {
|
||||
if r.index >= len(r.data) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
b = r.data[r.index]
|
||||
r.index++
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadNumBytes(n int) ([]byte, error) {
|
||||
if n <= 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
if r.index+n > len(r.data) {
|
||||
return []byte{}, io.EOF
|
||||
}
|
||||
|
||||
b := make([]byte, n)
|
||||
_, err := r.Read(b)
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
// Read a number of bytes and cast to a string
|
||||
func (r *Reader) ReadNumBytesString(n int) (string, error) {
|
||||
b, err := r.ReadNumBytes(n)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
// Read until the end of the data
|
||||
func (r *Reader) ReadRest() ([]byte, error) {
|
||||
return r.ReadNumBytes(len(r.data) - r.index)
|
||||
}
|
||||
|
||||
// Read until the end of the data and cast to a string
|
||||
func (r *Reader) ReadRestString(encoding byte) (string, error) {
|
||||
b, err := r.ReadRest()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return Decoders[encoding].ConvertString(string(b))
|
||||
}
|
||||
|
||||
// Read a null terminated string of specified encoding
|
||||
func (r *Reader) ReadNullTermString(encoding byte) (string, error) {
|
||||
atIndex, afterIndex := nullIndex(r.data[r.index:], encoding)
|
||||
b, err := r.ReadNumBytes(afterIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if -1 == atIndex {
|
||||
return "", errors.New("could not read null terminated string")
|
||||
}
|
||||
return Decoders[encoding].ConvertString(string(b[:atIndex]))
|
||||
}
|
||||
|
||||
func NewReader(b []byte) *Reader { return &Reader{b, 0} }
|
152
vendor/github.com/mikkyang/id3-go/encodedbytes/util.go
generated
vendored
Normal file
152
vendor/github.com/mikkyang/id3-go/encodedbytes/util.go
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package encodedbytes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
iconv "github.com/djimenez/iconv-go"
|
||||
)
|
||||
|
||||
const (
|
||||
BytesPerInt = 4
|
||||
SynchByteLength = 7
|
||||
NormByteLength = 8
|
||||
NativeEncoding = 3
|
||||
)
|
||||
|
||||
type Encoding struct {
|
||||
Name string
|
||||
NullLength int
|
||||
}
|
||||
|
||||
var (
|
||||
EncodingMap = [...]Encoding{
|
||||
{Name: "ISO-8859-1", NullLength: 1},
|
||||
{Name: "UTF-16", NullLength: 2},
|
||||
{Name: "UTF-16BE", NullLength: 2},
|
||||
{Name: "UTF-8", NullLength: 1},
|
||||
}
|
||||
Decoders = make([]*iconv.Converter, len(EncodingMap))
|
||||
Encoders = make([]*iconv.Converter, len(EncodingMap))
|
||||
)
|
||||
|
||||
func init() {
|
||||
n := EncodingForIndex(NativeEncoding)
|
||||
for i, e := range EncodingMap {
|
||||
Decoders[i], _ = iconv.NewConverter(e.Name, n)
|
||||
Encoders[i], _ = iconv.NewConverter(n, e.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Form an integer from concatenated bits
|
||||
func ByteInt(buf []byte, base uint) (i uint32, err error) {
|
||||
if len(buf) > BytesPerInt {
|
||||
err = errors.New("byte integer: invalid []byte length")
|
||||
return
|
||||
}
|
||||
|
||||
for _, b := range buf {
|
||||
if base < NormByteLength && b >= (1<<base) {
|
||||
err = errors.New("byte integer: exceed max bit")
|
||||
return
|
||||
}
|
||||
|
||||
i = (i << base) | uint32(b)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func SynchInt(buf []byte) (i uint32, err error) {
|
||||
i, err = ByteInt(buf, SynchByteLength)
|
||||
return
|
||||
}
|
||||
|
||||
func NormInt(buf []byte) (i uint32, err error) {
|
||||
i, err = ByteInt(buf, NormByteLength)
|
||||
return
|
||||
}
|
||||
|
||||
// Form a byte slice from an integer
|
||||
func IntBytes(n uint32, base uint) []byte {
|
||||
mask := uint32(1<<base - 1)
|
||||
bytes := make([]byte, BytesPerInt)
|
||||
|
||||
for i, _ := range bytes {
|
||||
bytes[len(bytes)-i-1] = byte(n & mask)
|
||||
n >>= base
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
func SynchBytes(n uint32) []byte {
|
||||
return IntBytes(n, SynchByteLength)
|
||||
}
|
||||
|
||||
func NormBytes(n uint32) []byte {
|
||||
return IntBytes(n, NormByteLength)
|
||||
}
|
||||
|
||||
func EncodingForIndex(b byte) string {
|
||||
encodingIndex := int(b)
|
||||
if encodingIndex < 0 || encodingIndex > len(EncodingMap) {
|
||||
encodingIndex = 0
|
||||
}
|
||||
|
||||
return EncodingMap[encodingIndex].Name
|
||||
}
|
||||
|
||||
func EncodingNullLengthForIndex(b byte) int {
|
||||
encodingIndex := int(b)
|
||||
if encodingIndex < 0 || encodingIndex > len(EncodingMap) {
|
||||
encodingIndex = 0
|
||||
}
|
||||
|
||||
return EncodingMap[encodingIndex].NullLength
|
||||
}
|
||||
|
||||
func IndexForEncoding(e string) byte {
|
||||
for i, v := range EncodingMap {
|
||||
if v.Name == e {
|
||||
return byte(i)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func nullIndex(data []byte, encoding byte) (atIndex, afterIndex int) {
|
||||
byteCount := EncodingNullLengthForIndex(encoding)
|
||||
limit := len(data)
|
||||
null := bytes.Repeat([]byte{0x0}, byteCount)
|
||||
|
||||
for i, _ := range data[:limit/byteCount] {
|
||||
atIndex = byteCount * i
|
||||
afterIndex = atIndex + byteCount
|
||||
|
||||
if bytes.Equal(data[atIndex:afterIndex], null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
atIndex = -1
|
||||
afterIndex = -1
|
||||
return
|
||||
}
|
||||
|
||||
func EncodedDiff(newEncoding byte, newString string, oldEncoding byte, oldString string) (int, error) {
|
||||
newEncodedString, err := Encoders[newEncoding].ConvertString(newString)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
oldEncodedString, err := Encoders[oldEncoding].ConvertString(oldString)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(newEncodedString) - len(oldEncodedString), nil
|
||||
}
|
63
vendor/github.com/mikkyang/id3-go/encodedbytes/writer.go
generated
vendored
Normal file
63
vendor/github.com/mikkyang/id3-go/encodedbytes/writer.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package encodedbytes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Writer is a helper for writing frame bytes
|
||||
type Writer struct {
|
||||
data []byte
|
||||
index int // current writing index
|
||||
}
|
||||
|
||||
func (w *Writer) Write(b []byte) (n int, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if w.index >= len(w.data) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(w.data[w.index:], b)
|
||||
w.index += n
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Writer) WriteByte(b byte) (err error) {
|
||||
if w.index >= len(w.data) {
|
||||
return io.EOF
|
||||
}
|
||||
w.data[w.index] = b
|
||||
w.index++
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Writer) WriteString(s string, encoding byte) (err error) {
|
||||
encodedString, err := Encoders[encoding].ConvertString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(encodedString))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Writer) WriteNullTermString(s string, encoding byte) (err error) {
|
||||
if err = w.WriteString(s, encoding); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nullLength := EncodingNullLengthForIndex(encoding)
|
||||
_, err = w.Write(bytes.Repeat([]byte{0x0}, nullLength))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewWriter(b []byte) *Writer { return &Writer{b, 0} }
|
116
vendor/github.com/mikkyang/id3-go/id3.go
generated
vendored
Normal file
116
vendor/github.com/mikkyang/id3-go/id3.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package id3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mikkyang/id3-go/v1"
|
||||
"github.com/mikkyang/id3-go/v2"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
LatestVersion = 3
|
||||
)
|
||||
|
||||
// Tagger represents the metadata of a tag
|
||||
type Tagger interface {
|
||||
Title() string
|
||||
Artist() string
|
||||
Album() string
|
||||
Year() string
|
||||
Genre() string
|
||||
Comments() []string
|
||||
SetTitle(string)
|
||||
SetArtist(string)
|
||||
SetAlbum(string)
|
||||
SetYear(string)
|
||||
SetGenre(string)
|
||||
AllFrames() []v2.Framer
|
||||
Frames(string) []v2.Framer
|
||||
Frame(string) v2.Framer
|
||||
DeleteFrames(string) []v2.Framer
|
||||
AddFrames(...v2.Framer)
|
||||
Bytes() []byte
|
||||
Dirty() bool
|
||||
Padding() uint
|
||||
Size() int
|
||||
Version() string
|
||||
}
|
||||
|
||||
// File represents the tagged file
|
||||
type File struct {
|
||||
Tagger
|
||||
originalSize int
|
||||
file *os.File
|
||||
}
|
||||
|
||||
// Parses an open file
|
||||
func Parse(file *os.File) (*File, error) {
|
||||
res := &File{file: file}
|
||||
|
||||
if v2Tag := v2.ParseTag(file); v2Tag != nil {
|
||||
res.Tagger = v2Tag
|
||||
res.originalSize = v2Tag.Size()
|
||||
} else if v1Tag := v1.ParseTag(file); v1Tag != nil {
|
||||
res.Tagger = v1Tag
|
||||
} else {
|
||||
// Add a new tag if none exists
|
||||
res.Tagger = v2.NewTag(LatestVersion)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Opens a new tagged file
|
||||
func Open(name string) (*File, error) {
|
||||
fi, err := os.OpenFile(name, os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := Parse(fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Saves any edits to the tagged file
|
||||
func (f *File) Close() error {
|
||||
defer f.file.Close()
|
||||
|
||||
if !f.Dirty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch f.Tagger.(type) {
|
||||
case (*v1.Tag):
|
||||
if _, err := f.file.Seek(-v1.TagSize, os.SEEK_END); err != nil {
|
||||
return err
|
||||
}
|
||||
case (*v2.Tag):
|
||||
if f.Size() > f.originalSize {
|
||||
start := int64(f.originalSize + v2.HeaderSize)
|
||||
offset := int64(f.Tagger.Size() - f.originalSize)
|
||||
|
||||
if err := shiftBytesBack(f.file, start, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := f.file.Seek(0, os.SEEK_SET); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("Close: unknown tag version")
|
||||
}
|
||||
|
||||
if _, err := f.file.Write(f.Tagger.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
59
vendor/github.com/mikkyang/id3-go/util.go
generated
vendored
Normal file
59
vendor/github.com/mikkyang/id3-go/util.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package id3
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func shiftBytesBack(file *os.File, start, offset int64) error {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end := stat.Size()
|
||||
|
||||
wrBuf := make([]byte, offset)
|
||||
rdBuf := make([]byte, offset)
|
||||
|
||||
wrOffset := offset
|
||||
rdOffset := start
|
||||
|
||||
rn, err := file.ReadAt(wrBuf, rdOffset)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
rdOffset += int64(rn)
|
||||
|
||||
for {
|
||||
if rdOffset >= end {
|
||||
break
|
||||
}
|
||||
|
||||
n, err := file.ReadAt(rdBuf, rdOffset)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
if rdOffset+int64(n) > end {
|
||||
n = int(end - rdOffset)
|
||||
}
|
||||
|
||||
if _, err := file.WriteAt(wrBuf[:rn], wrOffset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rdOffset += int64(n)
|
||||
wrOffset += int64(rn)
|
||||
copy(wrBuf, rdBuf)
|
||||
rn = n
|
||||
}
|
||||
|
||||
if _, err := file.WriteAt(wrBuf[:rn], wrOffset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
148
vendor/github.com/mikkyang/id3-go/v1/id3v1.go
generated
vendored
Normal file
148
vendor/github.com/mikkyang/id3-go/v1/id3v1.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package v1
|
||||
|
||||
import (
|
||||
v2 "github.com/mikkyang/id3-go/v2"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
TagSize = 128
|
||||
)
|
||||
|
||||
var (
|
||||
Genres = []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",
|
||||
}
|
||||
)
|
||||
|
||||
// Tag represents an ID3v1 tag
|
||||
type Tag struct {
|
||||
title, artist, album, year, comment string
|
||||
genre byte
|
||||
dirty bool
|
||||
}
|
||||
|
||||
func ParseTag(readSeeker io.ReadSeeker) *Tag {
|
||||
readSeeker.Seek(-TagSize, os.SEEK_END)
|
||||
|
||||
data := make([]byte, TagSize)
|
||||
n, err := io.ReadFull(readSeeker, data)
|
||||
if n < TagSize || err != nil || string(data[:3]) != "TAG" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Tag{
|
||||
title: string(data[3:33]),
|
||||
artist: string(data[33:63]),
|
||||
album: string(data[63:93]),
|
||||
year: string(data[93:97]),
|
||||
comment: string(data[97:127]),
|
||||
genre: data[127],
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Tag) Dirty() bool {
|
||||
return t.dirty
|
||||
}
|
||||
|
||||
func (t Tag) Title() string { return t.title }
|
||||
func (t Tag) Artist() string { return t.artist }
|
||||
func (t Tag) Album() string { return t.album }
|
||||
func (t Tag) Year() string { return t.year }
|
||||
|
||||
func (t Tag) Genre() string {
|
||||
if int(t.genre) < len(Genres) {
|
||||
return Genres[t.genre]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t Tag) Comments() []string {
|
||||
return []string{t.comment}
|
||||
}
|
||||
|
||||
func (t *Tag) SetTitle(text string) {
|
||||
t.title = text
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
func (t *Tag) SetArtist(text string) {
|
||||
t.artist = text
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
func (t *Tag) SetAlbum(text string) {
|
||||
t.album = text
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
func (t *Tag) SetYear(text string) {
|
||||
t.year = text
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
func (t *Tag) SetGenre(text string) {
|
||||
t.genre = 255
|
||||
for i, genre := range Genres {
|
||||
if text == genre {
|
||||
t.genre = byte(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
func (t Tag) Bytes() []byte {
|
||||
data := make([]byte, TagSize)
|
||||
|
||||
copy(data[:3], []byte("TAG"))
|
||||
copy(data[3:33], []byte(t.title))
|
||||
copy(data[33:63], []byte(t.artist))
|
||||
copy(data[63:93], []byte(t.album))
|
||||
copy(data[93:97], []byte(t.year))
|
||||
copy(data[97:127], []byte(t.comment))
|
||||
data[127] = t.genre
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (t Tag) Size() int {
|
||||
return TagSize
|
||||
}
|
||||
|
||||
func (t Tag) Version() string {
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
// Dummy methods to satisfy Tagger interface
|
||||
func (t Tag) Padding() uint { return 0 }
|
||||
func (t Tag) AllFrames() []v2.Framer { return []v2.Framer{} }
|
||||
func (t Tag) Frame(id string) v2.Framer { return nil }
|
||||
func (t Tag) Frames(id string) []v2.Framer { return []v2.Framer{} }
|
||||
func (t Tag) DeleteFrames(id string) []v2.Framer { return []v2.Framer{} }
|
||||
func (t Tag) AddFrames(f ...v2.Framer) {}
|
585
vendor/github.com/mikkyang/id3-go/v2/frame.go
generated
vendored
Normal file
585
vendor/github.com/mikkyang/id3-go/v2/frame.go
generated
vendored
Normal file
@ -0,0 +1,585 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mikkyang/id3-go/encodedbytes"
|
||||
)
|
||||
|
||||
const (
|
||||
FrameHeaderSize = 10
|
||||
)
|
||||
|
||||
// FrameType holds frame id metadata and constructor method
|
||||
// A set number of these are created in the version specific files
|
||||
type FrameType struct {
|
||||
id string
|
||||
description string
|
||||
constructor func(FrameHead, []byte) Framer
|
||||
}
|
||||
|
||||
// Framer provides a generic interface for frames
|
||||
// This is the default type returned when creating frames
|
||||
type Framer interface {
|
||||
Id() string
|
||||
Size() uint
|
||||
StatusFlags() byte
|
||||
FormatFlags() byte
|
||||
String() string
|
||||
Bytes() []byte
|
||||
setOwner(*Tag)
|
||||
}
|
||||
|
||||
// FrameHead represents the header of each frame
|
||||
// Additional metadata is kept through the embedded frame type
|
||||
// These do not usually need to be manually created
|
||||
type FrameHead struct {
|
||||
FrameType
|
||||
statusFlags byte
|
||||
formatFlags byte
|
||||
size uint32
|
||||
owner *Tag
|
||||
}
|
||||
|
||||
func (ft FrameType) Id() string {
|
||||
return ft.id
|
||||
}
|
||||
|
||||
func (h FrameHead) Size() uint {
|
||||
return uint(h.size)
|
||||
}
|
||||
|
||||
func (h *FrameHead) changeSize(diff int) {
|
||||
if diff >= 0 {
|
||||
h.size += uint32(diff)
|
||||
} else {
|
||||
h.size -= uint32(-diff)
|
||||
}
|
||||
|
||||
if h.owner != nil {
|
||||
h.owner.changeSize(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func (h FrameHead) StatusFlags() byte {
|
||||
return h.statusFlags
|
||||
}
|
||||
|
||||
func (h FrameHead) FormatFlags() byte {
|
||||
return h.formatFlags
|
||||
}
|
||||
|
||||
func (h *FrameHead) setOwner(t *Tag) {
|
||||
h.owner = t
|
||||
}
|
||||
|
||||
// DataFrame is the default frame for binary data
|
||||
type DataFrame struct {
|
||||
FrameHead
|
||||
data []byte
|
||||
}
|
||||
|
||||
func NewDataFrame(ft FrameType, data []byte) *DataFrame {
|
||||
head := FrameHead{
|
||||
FrameType: ft,
|
||||
size: uint32(len(data)),
|
||||
}
|
||||
|
||||
return &DataFrame{head, data}
|
||||
}
|
||||
|
||||
func ParseDataFrame(head FrameHead, data []byte) Framer {
|
||||
return &DataFrame{head, data}
|
||||
}
|
||||
|
||||
func (f DataFrame) Data() []byte {
|
||||
return f.data
|
||||
}
|
||||
|
||||
func (f *DataFrame) SetData(b []byte) {
|
||||
diff := len(b) - len(f.data)
|
||||
f.changeSize(diff)
|
||||
f.data = b
|
||||
}
|
||||
|
||||
func (f DataFrame) String() string {
|
||||
return "<binary data>"
|
||||
}
|
||||
|
||||
func (f DataFrame) Bytes() []byte {
|
||||
return f.data
|
||||
}
|
||||
|
||||
// IdFrame represents identification tags
|
||||
type IdFrame struct {
|
||||
FrameHead
|
||||
ownerIdentifier string
|
||||
identifier []byte
|
||||
}
|
||||
|
||||
func NewIdFrame(ft FrameType, ownerId string, id []byte) *IdFrame {
|
||||
head := FrameHead{
|
||||
FrameType: ft,
|
||||
size: uint32(1 + len(ownerId) + len(id)),
|
||||
}
|
||||
|
||||
return &IdFrame{
|
||||
FrameHead: head,
|
||||
ownerIdentifier: ownerId,
|
||||
identifier: id,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseIdFrame(head FrameHead, data []byte) Framer {
|
||||
var err error
|
||||
f := &IdFrame{FrameHead: head}
|
||||
rd := encodedbytes.NewReader(data)
|
||||
|
||||
if f.ownerIdentifier, err = rd.ReadNullTermString(encodedbytes.NativeEncoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.identifier, err = rd.ReadRest(); len(f.identifier) > 64 || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f IdFrame) OwnerIdentifier() string {
|
||||
return f.ownerIdentifier
|
||||
}
|
||||
|
||||
func (f *IdFrame) SetOwnerIdentifier(ownerId string) {
|
||||
f.changeSize(len(ownerId) - len(f.ownerIdentifier))
|
||||
f.ownerIdentifier = ownerId
|
||||
}
|
||||
|
||||
func (f IdFrame) Identifier() []byte {
|
||||
return f.identifier
|
||||
}
|
||||
|
||||
func (f *IdFrame) SetIdentifier(id []byte) error {
|
||||
if len(id) > 64 {
|
||||
return errors.New("identifier: identifier too long")
|
||||
}
|
||||
|
||||
f.changeSize(len(id) - len(f.identifier))
|
||||
f.identifier = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f IdFrame) String() string {
|
||||
return fmt.Sprintf("%s: %v", f.ownerIdentifier, f.identifier)
|
||||
}
|
||||
|
||||
func (f IdFrame) Bytes() []byte {
|
||||
var err error
|
||||
bytes := make([]byte, f.Size())
|
||||
wr := encodedbytes.NewWriter(bytes)
|
||||
|
||||
if err = wr.WriteString(f.ownerIdentifier, encodedbytes.NativeEncoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if _, err = wr.Write(f.identifier); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
// TextFramer represents frames that contain encoded text
|
||||
type TextFramer interface {
|
||||
Framer
|
||||
Encoding() string
|
||||
SetEncoding(string) error
|
||||
Text() string
|
||||
SetText(string) error
|
||||
}
|
||||
|
||||
// TextFrame represents frames that contain encoded text
|
||||
type TextFrame struct {
|
||||
FrameHead
|
||||
encoding byte
|
||||
text string
|
||||
}
|
||||
|
||||
func NewTextFrame(ft FrameType, text string) *TextFrame {
|
||||
head := FrameHead{
|
||||
FrameType: ft,
|
||||
size: uint32(1 + len(text)),
|
||||
}
|
||||
|
||||
return &TextFrame{
|
||||
FrameHead: head,
|
||||
text: text,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseTextFrame(head FrameHead, data []byte) Framer {
|
||||
var err error
|
||||
f := &TextFrame{FrameHead: head}
|
||||
rd := encodedbytes.NewReader(data)
|
||||
|
||||
if f.encoding, err = rd.ReadByte(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.text, err = rd.ReadRestString(f.encoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f TextFrame) Encoding() string {
|
||||
return encodedbytes.EncodingForIndex(f.encoding)
|
||||
}
|
||||
|
||||
func (f *TextFrame) SetEncoding(encoding string) error {
|
||||
i := byte(encodedbytes.IndexForEncoding(encoding))
|
||||
if i < 0 {
|
||||
return errors.New("encoding: invalid encoding")
|
||||
}
|
||||
|
||||
diff, err := encodedbytes.EncodedDiff(i, f.text, f.encoding, f.text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.changeSize(diff)
|
||||
f.encoding = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f TextFrame) Text() string {
|
||||
return f.text
|
||||
}
|
||||
|
||||
func (f *TextFrame) SetText(text string) error {
|
||||
diff, err := encodedbytes.EncodedDiff(f.encoding, text, f.encoding, f.text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.changeSize(diff)
|
||||
f.text = text
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f TextFrame) String() string {
|
||||
return f.text
|
||||
}
|
||||
|
||||
func (f TextFrame) Bytes() []byte {
|
||||
var err error
|
||||
bytes := make([]byte, f.Size())
|
||||
wr := encodedbytes.NewWriter(bytes)
|
||||
|
||||
if err = wr.WriteByte(f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteString(f.text, f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
type DescTextFrame struct {
|
||||
TextFrame
|
||||
description string
|
||||
}
|
||||
|
||||
func NewDescTextFrame(ft FrameType, desc, text string) *DescTextFrame {
|
||||
f := NewTextFrame(ft, text)
|
||||
nullLength := encodedbytes.EncodingNullLengthForIndex(f.encoding)
|
||||
f.size += uint32(len(desc) + nullLength)
|
||||
|
||||
return &DescTextFrame{
|
||||
TextFrame: *f,
|
||||
description: desc,
|
||||
}
|
||||
}
|
||||
|
||||
// DescTextFrame represents frames that contain encoded text and descriptions
|
||||
func ParseDescTextFrame(head FrameHead, data []byte) Framer {
|
||||
var err error
|
||||
f := new(DescTextFrame)
|
||||
f.FrameHead = head
|
||||
rd := encodedbytes.NewReader(data)
|
||||
|
||||
if f.encoding, err = rd.ReadByte(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.description, err = rd.ReadNullTermString(f.encoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.text, err = rd.ReadRestString(f.encoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f DescTextFrame) Description() string {
|
||||
return f.description
|
||||
}
|
||||
|
||||
func (f *DescTextFrame) SetDescription(description string) error {
|
||||
diff, err := encodedbytes.EncodedDiff(f.encoding, description, f.encoding, f.description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.changeSize(diff)
|
||||
f.description = description
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *DescTextFrame) SetEncoding(encoding string) error {
|
||||
i := byte(encodedbytes.IndexForEncoding(encoding))
|
||||
if i < 0 {
|
||||
return errors.New("encoding: invalid encoding")
|
||||
}
|
||||
|
||||
descDiff, err := encodedbytes.EncodedDiff(i, f.text, f.encoding, f.text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newNullLength := encodedbytes.EncodingNullLengthForIndex(i)
|
||||
oldNullLength := encodedbytes.EncodingNullLengthForIndex(f.encoding)
|
||||
nullDiff := newNullLength - oldNullLength
|
||||
|
||||
textDiff, err := encodedbytes.EncodedDiff(i, f.description, f.encoding, f.description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.changeSize(descDiff + nullDiff + textDiff)
|
||||
f.encoding = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f DescTextFrame) String() string {
|
||||
return fmt.Sprintf("%s: %s", f.description, f.text)
|
||||
}
|
||||
|
||||
func (f DescTextFrame) Bytes() []byte {
|
||||
var err error
|
||||
bytes := make([]byte, f.Size())
|
||||
wr := encodedbytes.NewWriter(bytes)
|
||||
|
||||
if err = wr.WriteByte(f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteString(f.description, f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteString(f.text, f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
// UnsynchTextFrame represents frames that contain unsynchronized text
|
||||
type UnsynchTextFrame struct {
|
||||
DescTextFrame
|
||||
language string
|
||||
}
|
||||
|
||||
func NewUnsynchTextFrame(ft FrameType, desc, text string) *UnsynchTextFrame {
|
||||
f := NewDescTextFrame(ft, desc, text)
|
||||
f.size += uint32(3)
|
||||
|
||||
return &UnsynchTextFrame{
|
||||
DescTextFrame: *f,
|
||||
language: "eng",
|
||||
}
|
||||
}
|
||||
|
||||
func ParseUnsynchTextFrame(head FrameHead, data []byte) Framer {
|
||||
var err error
|
||||
f := new(UnsynchTextFrame)
|
||||
f.FrameHead = head
|
||||
rd := encodedbytes.NewReader(data)
|
||||
|
||||
if f.encoding, err = rd.ReadByte(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.language, err = rd.ReadNumBytesString(3); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.description, err = rd.ReadNullTermString(f.encoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.text, err = rd.ReadRestString(f.encoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f UnsynchTextFrame) Language() string {
|
||||
return f.language
|
||||
}
|
||||
|
||||
func (f *UnsynchTextFrame) SetLanguage(language string) error {
|
||||
if len(language) != 3 {
|
||||
return errors.New("language: invalid language string")
|
||||
}
|
||||
|
||||
f.language = language
|
||||
f.changeSize(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f UnsynchTextFrame) String() string {
|
||||
return fmt.Sprintf("%s\t%s:\n%s", f.language, f.description, f.text)
|
||||
}
|
||||
|
||||
func (f UnsynchTextFrame) Bytes() []byte {
|
||||
var err error
|
||||
bytes := make([]byte, f.Size())
|
||||
wr := encodedbytes.NewWriter(bytes)
|
||||
|
||||
if err = wr.WriteByte(f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteString(f.language, encodedbytes.NativeEncoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteNullTermString(f.description, f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteString(f.text, f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
// ImageFrame represent frames that have media attached
|
||||
type ImageFrame struct {
|
||||
DataFrame
|
||||
encoding byte
|
||||
mimeType string
|
||||
pictureType byte
|
||||
description string
|
||||
}
|
||||
|
||||
func ParseImageFrame(head FrameHead, data []byte) Framer {
|
||||
var err error
|
||||
f := new(ImageFrame)
|
||||
f.FrameHead = head
|
||||
rd := encodedbytes.NewReader(data)
|
||||
|
||||
if f.encoding, err = rd.ReadByte(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.mimeType, err = rd.ReadNullTermString(encodedbytes.NativeEncoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.pictureType, err = rd.ReadByte(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.description, err = rd.ReadNullTermString(f.encoding); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.data, err = rd.ReadRest(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f ImageFrame) Encoding() string {
|
||||
return encodedbytes.EncodingForIndex(f.encoding)
|
||||
}
|
||||
|
||||
func (f *ImageFrame) SetEncoding(encoding string) error {
|
||||
i := byte(encodedbytes.IndexForEncoding(encoding))
|
||||
if i < 0 {
|
||||
return errors.New("encoding: invalid encoding")
|
||||
}
|
||||
|
||||
diff, err := encodedbytes.EncodedDiff(i, f.description, f.encoding, f.description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.changeSize(diff)
|
||||
f.encoding = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f ImageFrame) MIMEType() string {
|
||||
return f.mimeType
|
||||
}
|
||||
|
||||
func (f *ImageFrame) SetMIMEType(mimeType string) {
|
||||
diff := len(mimeType) - len(f.mimeType)
|
||||
if mimeType[len(mimeType)-1] != 0 {
|
||||
nullTermBytes := append([]byte(mimeType), 0x00)
|
||||
f.mimeType = string(nullTermBytes)
|
||||
diff += 1
|
||||
} else {
|
||||
f.mimeType = mimeType
|
||||
}
|
||||
|
||||
f.changeSize(diff)
|
||||
}
|
||||
|
||||
func (f ImageFrame) String() string {
|
||||
return fmt.Sprintf("%s\t%s: <binary data>", f.mimeType, f.description)
|
||||
}
|
||||
|
||||
func (f ImageFrame) Bytes() []byte {
|
||||
var err error
|
||||
bytes := make([]byte, f.Size())
|
||||
wr := encodedbytes.NewWriter(bytes)
|
||||
|
||||
if err = wr.WriteByte(f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteString(f.mimeType, encodedbytes.NativeEncoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteByte(f.pictureType); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if err = wr.WriteString(f.description, f.encoding); err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
if n, err := wr.Write(f.data); n < len(f.data) || err != nil {
|
||||
return bytes
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
350
vendor/github.com/mikkyang/id3-go/v2/id3v2.go
generated
vendored
Normal file
350
vendor/github.com/mikkyang/id3-go/v2/id3v2.go
generated
vendored
Normal file
@ -0,0 +1,350 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mikkyang/id3-go/encodedbytes"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderSize = 10
|
||||
)
|
||||
|
||||
// Tag represents an ID3v2 tag
|
||||
type Tag struct {
|
||||
*Header
|
||||
frames map[string][]Framer
|
||||
padding uint
|
||||
commonMap map[string]FrameType
|
||||
frameHeaderSize int
|
||||
frameConstructor func(io.Reader) Framer
|
||||
frameBytesConstructor func(Framer) []byte
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// Creates a new tag
|
||||
func NewTag(version byte) *Tag {
|
||||
header := &Header{version: version}
|
||||
|
||||
t := &Tag{
|
||||
Header: header,
|
||||
frames: make(map[string][]Framer),
|
||||
dirty: false,
|
||||
}
|
||||
|
||||
switch t.version {
|
||||
case 2:
|
||||
t.commonMap = V22CommonFrame
|
||||
t.frameConstructor = ParseV22Frame
|
||||
t.frameHeaderSize = V22FrameHeaderSize
|
||||
t.frameBytesConstructor = V22Bytes
|
||||
case 3:
|
||||
t.commonMap = V23CommonFrame
|
||||
t.frameConstructor = ParseV23Frame
|
||||
t.frameHeaderSize = FrameHeaderSize
|
||||
t.frameBytesConstructor = V23Bytes
|
||||
default:
|
||||
t.commonMap = V23CommonFrame
|
||||
t.frameConstructor = ParseV23Frame
|
||||
t.frameHeaderSize = FrameHeaderSize
|
||||
t.frameBytesConstructor = V23Bytes
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Parses a new tag
|
||||
func ParseTag(readSeeker io.ReadSeeker) *Tag {
|
||||
header := ParseHeader(readSeeker)
|
||||
|
||||
if header == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := NewTag(header.version)
|
||||
t.Header = header
|
||||
|
||||
var frame Framer
|
||||
size := int(t.size)
|
||||
for size > 0 {
|
||||
frame = t.frameConstructor(readSeeker)
|
||||
|
||||
if frame == nil {
|
||||
break
|
||||
}
|
||||
|
||||
id := frame.Id()
|
||||
t.frames[id] = append(t.frames[id], frame)
|
||||
frame.setOwner(t)
|
||||
|
||||
size -= t.frameHeaderSize + int(frame.Size())
|
||||
}
|
||||
|
||||
t.padding = uint(size)
|
||||
if _, err := readSeeker.Seek(int64(HeaderSize+t.Size()), os.SEEK_SET); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Real size of the tag
|
||||
func (t Tag) RealSize() int {
|
||||
size := uint(t.size) - t.padding
|
||||
return int(size)
|
||||
}
|
||||
|
||||
func (t *Tag) changeSize(diff int) {
|
||||
if d := int(t.padding) - diff; d < 0 {
|
||||
t.padding = 0
|
||||
t.size += uint32(-d)
|
||||
} else {
|
||||
t.padding = uint(d)
|
||||
}
|
||||
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
// Modified status of the tag
|
||||
func (t Tag) Dirty() bool {
|
||||
return t.dirty
|
||||
}
|
||||
|
||||
func (t Tag) Bytes() []byte {
|
||||
data := make([]byte, t.Size())
|
||||
|
||||
index := 0
|
||||
for _, v := range t.frames {
|
||||
for _, f := range v {
|
||||
size := t.frameHeaderSize + int(f.Size())
|
||||
copy(data[index:index+size], t.frameBytesConstructor(f))
|
||||
|
||||
index += size
|
||||
}
|
||||
}
|
||||
|
||||
return append(t.Header.Bytes(), data...)
|
||||
}
|
||||
|
||||
// The amount of padding in the tag
|
||||
func (t Tag) Padding() uint {
|
||||
return t.padding
|
||||
}
|
||||
|
||||
// All frames
|
||||
func (t Tag) AllFrames() []Framer {
|
||||
// Most of the time each ID will only have one frame
|
||||
m := len(t.frames)
|
||||
frames := make([]Framer, m)
|
||||
|
||||
i := 0
|
||||
for _, frameSlice := range t.frames {
|
||||
if i >= m {
|
||||
frames = append(frames, frameSlice...)
|
||||
}
|
||||
|
||||
n := copy(frames[i:], frameSlice)
|
||||
i += n
|
||||
if n < len(frameSlice) {
|
||||
frames = append(frames, frameSlice[n:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
// All frames with specified ID
|
||||
func (t Tag) Frames(id string) []Framer {
|
||||
if frames, ok := t.frames[id]; ok && frames != nil {
|
||||
return frames
|
||||
}
|
||||
|
||||
return []Framer{}
|
||||
}
|
||||
|
||||
// First frame with specified ID
|
||||
func (t Tag) Frame(id string) Framer {
|
||||
if frames := t.Frames(id); len(frames) != 0 {
|
||||
return frames[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete and return all frames with specified ID
|
||||
func (t *Tag) DeleteFrames(id string) []Framer {
|
||||
frames := t.Frames(id)
|
||||
if frames == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
diff := 0
|
||||
for _, frame := range frames {
|
||||
frame.setOwner(nil)
|
||||
diff += t.frameHeaderSize + int(frame.Size())
|
||||
}
|
||||
t.changeSize(-diff)
|
||||
|
||||
delete(t.frames, id)
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
// Add frames
|
||||
func (t *Tag) AddFrames(frames ...Framer) {
|
||||
for _, frame := range frames {
|
||||
t.changeSize(t.frameHeaderSize + int(frame.Size()))
|
||||
|
||||
id := frame.Id()
|
||||
t.frames[id] = append(t.frames[id], frame)
|
||||
frame.setOwner(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (t Tag) Title() string {
|
||||
return t.textFrameText(t.commonMap["Title"])
|
||||
}
|
||||
|
||||
func (t Tag) Artist() string {
|
||||
return t.textFrameText(t.commonMap["Artist"])
|
||||
}
|
||||
|
||||
func (t Tag) Album() string {
|
||||
return t.textFrameText(t.commonMap["Album"])
|
||||
}
|
||||
|
||||
func (t Tag) Year() string {
|
||||
return t.textFrameText(t.commonMap["Year"])
|
||||
}
|
||||
|
||||
func (t Tag) Genre() string {
|
||||
return t.textFrameText(t.commonMap["Genre"])
|
||||
}
|
||||
|
||||
func (t Tag) Comments() []string {
|
||||
frames := t.Frames(t.commonMap["Comments"].Id())
|
||||
if frames == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
comments := make([]string, len(frames))
|
||||
for i, frame := range frames {
|
||||
comments[i] = frame.String()
|
||||
}
|
||||
|
||||
return comments
|
||||
}
|
||||
|
||||
func (t *Tag) SetTitle(text string) {
|
||||
t.setTextFrameText(t.commonMap["Title"], text)
|
||||
}
|
||||
|
||||
func (t *Tag) SetArtist(text string) {
|
||||
t.setTextFrameText(t.commonMap["Artist"], text)
|
||||
}
|
||||
|
||||
func (t *Tag) SetAlbum(text string) {
|
||||
t.setTextFrameText(t.commonMap["Album"], text)
|
||||
}
|
||||
|
||||
func (t *Tag) SetYear(text string) {
|
||||
t.setTextFrameText(t.commonMap["Year"], text)
|
||||
}
|
||||
|
||||
func (t *Tag) SetGenre(text string) {
|
||||
t.setTextFrameText(t.commonMap["Genre"], text)
|
||||
}
|
||||
|
||||
func (t *Tag) textFrame(ft FrameType) TextFramer {
|
||||
if frame := t.Frame(ft.Id()); frame != nil {
|
||||
if textFramer, ok := frame.(TextFramer); ok {
|
||||
return textFramer
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Tag) textFrameText(ft FrameType) string {
|
||||
if frame := t.textFrame(ft); frame != nil {
|
||||
return frame.Text()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *Tag) setTextFrameText(ft FrameType, text string) {
|
||||
if frame := t.textFrame(ft); frame != nil {
|
||||
frame.SetEncoding("UTF-8")
|
||||
frame.SetText(text)
|
||||
} else {
|
||||
f := NewTextFrame(ft, text)
|
||||
f.SetEncoding("UTF-8")
|
||||
t.AddFrames(f)
|
||||
}
|
||||
}
|
||||
|
||||
func ParseHeader(reader io.Reader) *Header {
|
||||
data := make([]byte, HeaderSize)
|
||||
n, err := io.ReadFull(reader, data)
|
||||
if n < HeaderSize || err != nil || string(data[:3]) != "ID3" {
|
||||
return nil
|
||||
}
|
||||
|
||||
size, err := encodedbytes.SynchInt(data[6:])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
header := &Header{
|
||||
version: data[3],
|
||||
revision: data[4],
|
||||
flags: data[5],
|
||||
size: size,
|
||||
}
|
||||
|
||||
switch header.version {
|
||||
case 2:
|
||||
header.unsynchronization = isBitSet(header.flags, 7)
|
||||
header.compression = isBitSet(header.flags, 6)
|
||||
case 3:
|
||||
header.unsynchronization = isBitSet(header.flags, 7)
|
||||
header.extendedHeader = isBitSet(header.flags, 6)
|
||||
header.experimental = isBitSet(header.flags, 5)
|
||||
}
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
// Header represents the data of the header of the entire tag
|
||||
type Header struct {
|
||||
version, revision byte
|
||||
flags byte
|
||||
unsynchronization bool
|
||||
compression bool
|
||||
experimental bool
|
||||
extendedHeader bool
|
||||
size uint32
|
||||
}
|
||||
|
||||
func (h Header) Version() string {
|
||||
return fmt.Sprintf("2.%d.%d", h.version, h.revision)
|
||||
}
|
||||
|
||||
func (h Header) Size() int {
|
||||
return int(h.size)
|
||||
}
|
||||
|
||||
func (h Header) Bytes() []byte {
|
||||
data := make([]byte, 0, HeaderSize)
|
||||
|
||||
data = append(data, "ID3"...)
|
||||
data = append(data, h.version, h.revision, h.flags)
|
||||
data = append(data, encodedbytes.SynchBytes(h.size)...)
|
||||
|
||||
return data
|
||||
}
|
131
vendor/github.com/mikkyang/id3-go/v2/id3v22.go
generated
vendored
Normal file
131
vendor/github.com/mikkyang/id3-go/v2/id3v22.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/mikkyang/id3-go/encodedbytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
V22FrameHeaderSize = 6
|
||||
)
|
||||
|
||||
var (
|
||||
// Common frame IDs
|
||||
V22CommonFrame = map[string]FrameType{
|
||||
"Title": V22FrameTypeMap["TT2"],
|
||||
"Artist": V22FrameTypeMap["TP1"],
|
||||
"Album": V22FrameTypeMap["TAL"],
|
||||
"Year": V22FrameTypeMap["TYE"],
|
||||
"Genre": V22FrameTypeMap["TCO"],
|
||||
"Comments": V22FrameTypeMap["COM"],
|
||||
}
|
||||
|
||||
// V22FrameTypeMap specifies the frame IDs and constructors allowed in ID3v2.2
|
||||
V22FrameTypeMap = map[string]FrameType{
|
||||
"BUF": FrameType{id: "BUF", description: "Recommended buffer size", constructor: ParseDataFrame},
|
||||
"CNT": FrameType{id: "CNT", description: "Play counter", constructor: ParseDataFrame},
|
||||
"COM": FrameType{id: "COM", description: "Comments", constructor: ParseUnsynchTextFrame},
|
||||
"CRA": FrameType{id: "CRA", description: "Audio encryption", constructor: ParseDataFrame},
|
||||
"CRM": FrameType{id: "CRM", description: "Encrypted meta frame", constructor: ParseDataFrame},
|
||||
"ETC": FrameType{id: "ETC", description: "Event timing codes", constructor: ParseDataFrame},
|
||||
"EQU": FrameType{id: "EQU", description: "Equalization", constructor: ParseDataFrame},
|
||||
"GEO": FrameType{id: "GEO", description: "General encapsulated object", constructor: ParseDataFrame},
|
||||
"IPL": FrameType{id: "IPL", description: "Involved people list", constructor: ParseDataFrame},
|
||||
"LNK": FrameType{id: "LNK", description: "Linked information", constructor: ParseDataFrame},
|
||||
"MCI": FrameType{id: "MCI", description: "Music CD Identifier", constructor: ParseDataFrame},
|
||||
"MLL": FrameType{id: "MLL", description: "MPEG location lookup table", constructor: ParseDataFrame},
|
||||
"PIC": FrameType{id: "PIC", description: "Attached picture", constructor: ParseDataFrame},
|
||||
"POP": FrameType{id: "POP", description: "Popularimeter", constructor: ParseDataFrame},
|
||||
"REV": FrameType{id: "REV", description: "Reverb", constructor: ParseDataFrame},
|
||||
"RVA": FrameType{id: "RVA", description: "Relative volume adjustment", constructor: ParseDataFrame},
|
||||
"SLT": FrameType{id: "SLT", description: "Synchronized lyric/text", constructor: ParseDataFrame},
|
||||
"STC": FrameType{id: "STC", description: "Synced tempo codes", constructor: ParseDataFrame},
|
||||
"TAL": FrameType{id: "TAL", description: "Album/Movie/Show title", constructor: ParseTextFrame},
|
||||
"TBP": FrameType{id: "TBP", description: "BPM (Beats Per Minute)", constructor: ParseTextFrame},
|
||||
"TCM": FrameType{id: "TCM", description: "Composer", constructor: ParseTextFrame},
|
||||
"TCO": FrameType{id: "TCO", description: "Content type", constructor: ParseTextFrame},
|
||||
"TCR": FrameType{id: "TCR", description: "Copyright message", constructor: ParseTextFrame},
|
||||
"TDA": FrameType{id: "TDA", description: "Date", constructor: ParseTextFrame},
|
||||
"TDY": FrameType{id: "TDY", description: "Playlist delay", constructor: ParseTextFrame},
|
||||
"TEN": FrameType{id: "TEN", description: "Encoded by", constructor: ParseTextFrame},
|
||||
"TFT": FrameType{id: "TFT", description: "File type", constructor: ParseTextFrame},
|
||||
"TIM": FrameType{id: "TIM", description: "Time", constructor: ParseTextFrame},
|
||||
"TKE": FrameType{id: "TKE", description: "Initial key", constructor: ParseTextFrame},
|
||||
"TLA": FrameType{id: "TLA", description: "Language(s)", constructor: ParseTextFrame},
|
||||
"TLE": FrameType{id: "TLE", description: "Length", constructor: ParseTextFrame},
|
||||
"TMT": FrameType{id: "TMT", description: "Media type", constructor: ParseTextFrame},
|
||||
"TOA": FrameType{id: "TOA", description: "Original artist(s)/performer(s)", constructor: ParseTextFrame},
|
||||
"TOF": FrameType{id: "TOF", description: "Original filename", constructor: ParseTextFrame},
|
||||
"TOL": FrameType{id: "TOL", description: "Original Lyricist(s)/text writer(s)", constructor: ParseTextFrame},
|
||||
"TOR": FrameType{id: "TOR", description: "Original release year", constructor: ParseTextFrame},
|
||||
"TOT": FrameType{id: "TOT", description: "Original album/Movie/Show title", constructor: ParseTextFrame},
|
||||
"TP1": FrameType{id: "TP1", description: "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group", constructor: ParseTextFrame},
|
||||
"TP2": FrameType{id: "TP2", description: "Band/Orchestra/Accompaniment", constructor: ParseTextFrame},
|
||||
"TP3": FrameType{id: "TP3", description: "Conductor/Performer refinement", constructor: ParseTextFrame},
|
||||
"TP4": FrameType{id: "TP4", description: "Interpreted, remixed, or otherwise modified by", constructor: ParseTextFrame},
|
||||
"TPA": FrameType{id: "TPA", description: "Part of a set", constructor: ParseTextFrame},
|
||||
"TPB": FrameType{id: "TPB", description: "Publisher", constructor: ParseTextFrame},
|
||||
"TRC": FrameType{id: "TRC", description: "ISRC (International Standard Recording Code)", constructor: ParseTextFrame},
|
||||
"TRD": FrameType{id: "TRD", description: "Recording dates", constructor: ParseTextFrame},
|
||||
"TRK": FrameType{id: "TRK", description: "Track number/Position in set", constructor: ParseTextFrame},
|
||||
"TSI": FrameType{id: "TSI", description: "Size", constructor: ParseTextFrame},
|
||||
"TSS": FrameType{id: "TSS", description: "Software/hardware and settings used for encoding", constructor: ParseTextFrame},
|
||||
"TT1": FrameType{id: "TT1", description: "Content group description", constructor: ParseTextFrame},
|
||||
"TT2": FrameType{id: "TT2", description: "Title/Songname/Content description", constructor: ParseTextFrame},
|
||||
"TT3": FrameType{id: "TT3", description: "Subtitle/Description refinement", constructor: ParseTextFrame},
|
||||
"TXT": FrameType{id: "TXT", description: "Lyricist/text writer", constructor: ParseTextFrame},
|
||||
"TXX": FrameType{id: "TXX", description: "User defined text information frame", constructor: ParseDescTextFrame},
|
||||
"TYE": FrameType{id: "TYE", description: "Year", constructor: ParseTextFrame},
|
||||
"UFI": FrameType{id: "UFI", description: "Unique file identifier", constructor: ParseDataFrame},
|
||||
"ULT": FrameType{id: "ULT", description: "Unsychronized lyric/text transcription", constructor: ParseDataFrame},
|
||||
"WAF": FrameType{id: "WAF", description: "Official audio file webpage", constructor: ParseDataFrame},
|
||||
"WAR": FrameType{id: "WAR", description: "Official artist/performer webpage", constructor: ParseDataFrame},
|
||||
"WAS": FrameType{id: "WAS", description: "Official audio source webpage", constructor: ParseDataFrame},
|
||||
"WCM": FrameType{id: "WCM", description: "Commercial information", constructor: ParseDataFrame},
|
||||
"WCP": FrameType{id: "WCP", description: "Copyright/Legal information", constructor: ParseDataFrame},
|
||||
"WPB": FrameType{id: "WPB", description: "Publishers official webpage", constructor: ParseDataFrame},
|
||||
"WXX": FrameType{id: "WXX", description: "User defined URL link frame", constructor: ParseDataFrame},
|
||||
}
|
||||
)
|
||||
|
||||
func ParseV22Frame(reader io.Reader) Framer {
|
||||
data := make([]byte, V22FrameHeaderSize)
|
||||
if n, err := io.ReadFull(reader, data); n < V22FrameHeaderSize || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
id := string(data[:3])
|
||||
t, ok := V22FrameTypeMap[id]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
size, err := encodedbytes.NormInt(data[3:6])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
h := FrameHead{
|
||||
FrameType: t,
|
||||
size: size,
|
||||
}
|
||||
|
||||
frameData := make([]byte, size)
|
||||
if n, err := io.ReadFull(reader, frameData); n < int(size) || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.constructor(h, frameData)
|
||||
}
|
||||
|
||||
func V22Bytes(f Framer) []byte {
|
||||
headBytes := make([]byte, 0, V22FrameHeaderSize)
|
||||
|
||||
headBytes = append(headBytes, f.Id()...)
|
||||
headBytes = append(headBytes, encodedbytes.NormBytes(uint32(f.Size()))[1:]...)
|
||||
|
||||
return append(headBytes, f.Bytes()...)
|
||||
}
|
161
vendor/github.com/mikkyang/id3-go/v2/id3v23.go
generated
vendored
Normal file
161
vendor/github.com/mikkyang/id3-go/v2/id3v23.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/mikkyang/id3-go/encodedbytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// Common frame IDs
|
||||
V23CommonFrame = map[string]FrameType{
|
||||
"Title": V23FrameTypeMap["TIT2"],
|
||||
"Artist": V23FrameTypeMap["TPE1"],
|
||||
"Album": V23FrameTypeMap["TALB"],
|
||||
"Year": V23FrameTypeMap["TYER"],
|
||||
"Genre": V23FrameTypeMap["TCON"],
|
||||
"Comments": V23FrameTypeMap["COMM"],
|
||||
}
|
||||
|
||||
// V23DeprecatedTypeMap contains deprecated frame IDs from ID3v2.2
|
||||
V23DeprecatedTypeMap = map[string]string{
|
||||
"BUF": "RBUF", "COM": "COMM", "CRA": "AENC", "EQU": "EQUA",
|
||||
"ETC": "ETCO", "GEO": "GEOB", "MCI": "MCDI", "MLL": "MLLT",
|
||||
"PIC": "APIC", "POP": "POPM", "REV": "RVRB", "RVA": "RVAD",
|
||||
"SLT": "SYLT", "STC": "SYTC", "TAL": "TALB", "TBP": "TBPM",
|
||||
"TCM": "TCOM", "TCO": "TCON", "TCR": "TCOP", "TDA": "TDAT",
|
||||
"TDY": "TDLY", "TEN": "TENC", "TFT": "TFLT", "TIM": "TIME",
|
||||
"TKE": "TKEY", "TLA": "TLAN", "TLE": "TLEN", "TMT": "TMED",
|
||||
"TOA": "TOPE", "TOF": "TOFN", "TOL": "TOLY", "TOR": "TORY",
|
||||
"TOT": "TOAL", "TP1": "TPE1", "TP2": "TPE2", "TP3": "TPE3",
|
||||
"TP4": "TPE4", "TPA": "TPOS", "TPB": "TPUB", "TRC": "TSRC",
|
||||
"TRD": "TRDA", "TRK": "TRCK", "TSI": "TSIZ", "TSS": "TSSE",
|
||||
"TT1": "TIT1", "TT2": "TIT2", "TT3": "TIT3", "TXT": "TEXT",
|
||||
"TXX": "TXXX", "TYE": "TYER", "UFI": "UFID", "ULT": "USLT",
|
||||
"WAF": "WOAF", "WAR": "WOAR", "WAS": "WOAS", "WCM": "WCOM",
|
||||
"WCP": "WCOP", "WPB": "WPB", "WXX": "WXXX",
|
||||
}
|
||||
|
||||
// V23FrameTypeMap specifies the frame IDs and constructors allowed in ID3v2.3
|
||||
V23FrameTypeMap = map[string]FrameType{
|
||||
"AENC": FrameType{id: "AENC", description: "Audio encryption", constructor: ParseDataFrame},
|
||||
"APIC": FrameType{id: "APIC", description: "Attached picture", constructor: ParseImageFrame},
|
||||
"COMM": FrameType{id: "COMM", description: "Comments", constructor: ParseUnsynchTextFrame},
|
||||
"COMR": FrameType{id: "COMR", description: "Commercial frame", constructor: ParseDataFrame},
|
||||
"ENCR": FrameType{id: "ENCR", description: "Encryption method registration", constructor: ParseDataFrame},
|
||||
"EQUA": FrameType{id: "EQUA", description: "Equalization", constructor: ParseDataFrame},
|
||||
"ETCO": FrameType{id: "ETCO", description: "Event timing codes", constructor: ParseDataFrame},
|
||||
"GEOB": FrameType{id: "GEOB", description: "General encapsulated object", constructor: ParseDataFrame},
|
||||
"GRID": FrameType{id: "GRID", description: "Group identification registration", constructor: ParseDataFrame},
|
||||
"IPLS": FrameType{id: "IPLS", description: "Involved people list", constructor: ParseDataFrame},
|
||||
"LINK": FrameType{id: "LINK", description: "Linked information", constructor: ParseDataFrame},
|
||||
"MCDI": FrameType{id: "MCDI", description: "Music CD identifier", constructor: ParseDataFrame},
|
||||
"MLLT": FrameType{id: "MLLT", description: "MPEG location lookup table", constructor: ParseDataFrame},
|
||||
"OWNE": FrameType{id: "OWNE", description: "Ownership frame", constructor: ParseDataFrame},
|
||||
"PRIV": FrameType{id: "PRIV", description: "Private frame", constructor: ParseDataFrame},
|
||||
"PCNT": FrameType{id: "PCNT", description: "Play counter", constructor: ParseDataFrame},
|
||||
"POPM": FrameType{id: "POPM", description: "Popularimeter", constructor: ParseDataFrame},
|
||||
"POSS": FrameType{id: "POSS", description: "Position synchronisation frame", constructor: ParseDataFrame},
|
||||
"RBUF": FrameType{id: "RBUF", description: "Recommended buffer size", constructor: ParseDataFrame},
|
||||
"RVAD": FrameType{id: "RVAD", description: "Relative volume adjustment", constructor: ParseDataFrame},
|
||||
"RVRB": FrameType{id: "RVRB", description: "Reverb", constructor: ParseDataFrame},
|
||||
"SYLT": FrameType{id: "SYLT", description: "Synchronized lyric/text", constructor: ParseDataFrame},
|
||||
"SYTC": FrameType{id: "SYTC", description: "Synchronized tempo codes", constructor: ParseDataFrame},
|
||||
"TALB": FrameType{id: "TALB", description: "Album/Movie/Show title", constructor: ParseTextFrame},
|
||||
"TBPM": FrameType{id: "TBPM", description: "BPM (beats per minute)", constructor: ParseTextFrame},
|
||||
"TCOM": FrameType{id: "TCOM", description: "Composer", constructor: ParseTextFrame},
|
||||
"TCON": FrameType{id: "TCON", description: "Content type", constructor: ParseTextFrame},
|
||||
"TCOP": FrameType{id: "TCOP", description: "Copyright message", constructor: ParseTextFrame},
|
||||
"TDAT": FrameType{id: "TDAT", description: "Date", constructor: ParseTextFrame},
|
||||
"TDLY": FrameType{id: "TDLY", description: "Playlist delay", constructor: ParseTextFrame},
|
||||
"TENC": FrameType{id: "TENC", description: "Encoded by", constructor: ParseTextFrame},
|
||||
"TEXT": FrameType{id: "TEXT", description: "Lyricist/Text writer", constructor: ParseTextFrame},
|
||||
"TFLT": FrameType{id: "TFLT", description: "File type", constructor: ParseTextFrame},
|
||||
"TIME": FrameType{id: "TIME", description: "Time", constructor: ParseTextFrame},
|
||||
"TIT1": FrameType{id: "TIT1", description: "Content group description", constructor: ParseTextFrame},
|
||||
"TIT2": FrameType{id: "TIT2", description: "Title/songname/content description", constructor: ParseTextFrame},
|
||||
"TIT3": FrameType{id: "TIT3", description: "Subtitle/Description refinement", constructor: ParseTextFrame},
|
||||
"TKEY": FrameType{id: "TKEY", description: "Initial key", constructor: ParseTextFrame},
|
||||
"TLAN": FrameType{id: "TLAN", description: "Language(s)", constructor: ParseTextFrame},
|
||||
"TLEN": FrameType{id: "TLEN", description: "Length", constructor: ParseTextFrame},
|
||||
"TMED": FrameType{id: "TMED", description: "Media type", constructor: ParseTextFrame},
|
||||
"TOAL": FrameType{id: "TOAL", description: "Original album/movie/show title", constructor: ParseTextFrame},
|
||||
"TOFN": FrameType{id: "TOFN", description: "Original filename", constructor: ParseTextFrame},
|
||||
"TOLY": FrameType{id: "TOLY", description: "Original lyricist(s)/text writer(s)", constructor: ParseTextFrame},
|
||||
"TOPE": FrameType{id: "TOPE", description: "Original artist(s)/performer(s)", constructor: ParseTextFrame},
|
||||
"TORY": FrameType{id: "TORY", description: "Original release year", constructor: ParseTextFrame},
|
||||
"TOWN": FrameType{id: "TOWN", description: "File owner/licensee", constructor: ParseTextFrame},
|
||||
"TPE1": FrameType{id: "TPE1", description: "Lead performer(s)/Soloist(s)", constructor: ParseTextFrame},
|
||||
"TPE2": FrameType{id: "TPE2", description: "Band/orchestra/accompaniment", constructor: ParseTextFrame},
|
||||
"TPE3": FrameType{id: "TPE3", description: "Conductor/performer refinement", constructor: ParseTextFrame},
|
||||
"TPE4": FrameType{id: "TPE4", description: "Interpreted, remixed, or otherwise modified by", constructor: ParseTextFrame},
|
||||
"TPOS": FrameType{id: "TPOS", description: "Part of a set", constructor: ParseTextFrame},
|
||||
"TPUB": FrameType{id: "TPUB", description: "Publisher", constructor: ParseTextFrame},
|
||||
"TRCK": FrameType{id: "TRCK", description: "Track number/Position in set", constructor: ParseTextFrame},
|
||||
"TRDA": FrameType{id: "TRDA", description: "Recording dates", constructor: ParseTextFrame},
|
||||
"TRSN": FrameType{id: "TRSN", description: "Internet radio station name", constructor: ParseTextFrame},
|
||||
"TRSO": FrameType{id: "TRSO", description: "Internet radio station owner", constructor: ParseTextFrame},
|
||||
"TSIZ": FrameType{id: "TSIZ", description: "Size", constructor: ParseTextFrame},
|
||||
"TSRC": FrameType{id: "TSRC", description: "ISRC (international standard recording code)", constructor: ParseTextFrame},
|
||||
"TSSE": FrameType{id: "TSSE", description: "Software/Hardware and settings used for encoding", constructor: ParseTextFrame},
|
||||
"TYER": FrameType{id: "TYER", description: "Year", constructor: ParseTextFrame},
|
||||
"TXXX": FrameType{id: "TXXX", description: "User defined text information frame", constructor: ParseDescTextFrame},
|
||||
"UFID": FrameType{id: "UFID", description: "Unique file identifier", constructor: ParseIdFrame},
|
||||
"USER": FrameType{id: "USER", description: "Terms of use", constructor: ParseDataFrame},
|
||||
"TCMP": FrameType{id: "TCMP", description: "Part of a compilation (iTunes extension)", constructor: ParseTextFrame},
|
||||
"USLT": FrameType{id: "USLT", description: "Unsychronized lyric/text transcription", constructor: ParseUnsynchTextFrame},
|
||||
"WCOM": FrameType{id: "WCOM", description: "Commercial information", constructor: ParseDataFrame},
|
||||
"WCOP": FrameType{id: "WCOP", description: "Copyright/Legal information", constructor: ParseDataFrame},
|
||||
"WOAF": FrameType{id: "WOAF", description: "Official audio file webpage", constructor: ParseDataFrame},
|
||||
"WOAR": FrameType{id: "WOAR", description: "Official artist/performer webpage", constructor: ParseDataFrame},
|
||||
"WOAS": FrameType{id: "WOAS", description: "Official audio source webpage", constructor: ParseDataFrame},
|
||||
"WORS": FrameType{id: "WORS", description: "Official internet radio station homepage", constructor: ParseDataFrame},
|
||||
"WPAY": FrameType{id: "WPAY", description: "Payment", constructor: ParseDataFrame},
|
||||
"WPUB": FrameType{id: "WPUB", description: "Publishers official webpage", constructor: ParseDataFrame},
|
||||
"WXXX": FrameType{id: "WXXX", description: "User defined URL link frame", constructor: ParseDataFrame},
|
||||
}
|
||||
)
|
||||
|
||||
func ParseV23Frame(reader io.Reader) Framer {
|
||||
data := make([]byte, FrameHeaderSize)
|
||||
if n, err := io.ReadFull(reader, data); n < FrameHeaderSize || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
id := string(data[:4])
|
||||
t, ok := V23FrameTypeMap[id]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
size, err := encodedbytes.NormInt(data[4:8])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
h := FrameHead{
|
||||
FrameType: t,
|
||||
statusFlags: data[8],
|
||||
formatFlags: data[9],
|
||||
size: size,
|
||||
}
|
||||
|
||||
frameData := make([]byte, size)
|
||||
if n, err := io.ReadFull(reader, frameData); n < int(size) || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.constructor(h, frameData)
|
||||
}
|
||||
|
||||
func V23Bytes(f Framer) []byte {
|
||||
headBytes := make([]byte, 0, FrameHeaderSize)
|
||||
|
||||
headBytes = append(headBytes, f.Id()...)
|
||||
headBytes = append(headBytes, encodedbytes.NormBytes(uint32(f.Size()))...)
|
||||
headBytes = append(headBytes, f.StatusFlags(), f.FormatFlags())
|
||||
|
||||
return append(headBytes, f.Bytes()...)
|
||||
}
|
8
vendor/github.com/mikkyang/id3-go/v2/util.go
generated
vendored
Normal file
8
vendor/github.com/mikkyang/id3-go/v2/util.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright 2013 Michael Yang. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package v2
|
||||
|
||||
func isBitSet(flag, index byte) bool {
|
||||
return flag&(1<<index) != 0
|
||||
}
|
9
vendor/modules.txt
vendored
9
vendor/modules.txt
vendored
@ -13,6 +13,9 @@ github.com/antchfx/xmlquery
|
||||
# github.com/antchfx/xpath v1.1.8
|
||||
## explicit
|
||||
github.com/antchfx/xpath
|
||||
# github.com/djimenez/iconv-go v0.0.0-20160305225143-8960e66bd3da
|
||||
## explicit
|
||||
github.com/djimenez/iconv-go
|
||||
# github.com/gobwas/glob v0.2.3
|
||||
## explicit
|
||||
github.com/gobwas/glob
|
||||
@ -37,6 +40,12 @@ github.com/golang/protobuf/proto
|
||||
# github.com/kennygrant/sanitize v1.2.4
|
||||
## explicit
|
||||
github.com/kennygrant/sanitize
|
||||
# github.com/mikkyang/id3-go v0.0.0-20191012064224-2c6ab3bb1fbd
|
||||
## explicit
|
||||
github.com/mikkyang/id3-go
|
||||
github.com/mikkyang/id3-go/encodedbytes
|
||||
github.com/mikkyang/id3-go/v1
|
||||
github.com/mikkyang/id3-go/v2
|
||||
# github.com/rs/zerolog v1.25.0
|
||||
## explicit; go 1.15
|
||||
github.com/rs/zerolog
|
||||
|
Loading…
Reference in New Issue
Block a user