feat: mp3 tagging

This commit is contained in:
Marvin Preuss 2021-10-25 09:21:00 +02:00
parent c4606a55e2
commit 9efd333e79
24 changed files with 2545 additions and 4 deletions

2
go.mod
View File

@ -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
View File

@ -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
View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
language: go
go:
- 1.11
- 1.12

22
vendor/github.com/mikkyang/id3-go/LICENSE generated vendored Normal file
View 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
View 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)

View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -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