⬥ ID3 encoding/decoding library in Go ⬥
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
6.2 KiB

package v2
import (
"fmt"
"io"
"strings"
"github.com/Unbewohnte/id3ed/util"
)
var ErrGotPadding error = fmt.Errorf("got padding")
var ErrBiggerThanSize error = fmt.Errorf("frame size is bigger than size of the whole tag")
var ErrInvalidFHeaderSize error = fmt.Errorf("frame header must be 6 or 10 bytes long")
var ErrInvalidID error = fmt.Errorf("invalid identifier")
type FrameFlags struct {
TagAlterPreservation bool
FileAlterPreservation bool
ReadOnly bool
Compressed bool
Encrypted bool
InGroup bool
Unsyrchronised bool
HasDataLengthIndicator bool
}
type FrameHeader struct {
ID string
Size uint32
Flags FrameFlags
}
type Frame struct {
Header FrameHeader
Contents []byte
}
// Checks if provided frame identifier is valid by
// its length and presence of invalid characters.
func isValidFrameID(frameID []byte) bool {
if len(frameID) != 3 && len(frameID) != 4 {
return false
}
str := strings.ToValidUTF8(string(frameID), "invalidChar")
if len(str) != 3 && len(str) != 4 {
return false
}
return true
}
func getV22FrameHeader(fHeaderbytes []byte) (FrameHeader, error) {
var header FrameHeader
if !isValidFrameID(fHeaderbytes[0:3]) {
return FrameHeader{}, ErrInvalidID
}
header.ID = string(fHeaderbytes[0:3])
framesizeBytes := util.BytesToIntSynchsafe(fHeaderbytes[3:6])
header.Size = framesizeBytes
return header, nil
}
func getV23FrameHeader(fHeaderbytes []byte) (FrameHeader, error) {
var header FrameHeader
// ID
if !isValidFrameID(fHeaderbytes[0:4]) {
return FrameHeader{}, ErrInvalidID
}
header.ID = string(fHeaderbytes[0:4])
// Size
framesizeBytes := fHeaderbytes[4:8]
framesize := util.BytesToIntSynchsafe(framesizeBytes)
header.Size = framesize
// Flags
frameFlags1 := fHeaderbytes[8]
frameFlags2 := fHeaderbytes[9]
var flags FrameFlags
if util.GetBit(frameFlags1, 1) {
flags.TagAlterPreservation = true
} else {
flags.TagAlterPreservation = false
}
if util.GetBit(frameFlags1, 2) {
flags.FileAlterPreservation = true
} else {
flags.FileAlterPreservation = false
}
if util.GetBit(frameFlags1, 3) {
flags.ReadOnly = true
} else {
flags.ReadOnly = false
}
if util.GetBit(frameFlags2, 1) {
flags.Compressed = true
} else {
flags.Compressed = false
}
if util.GetBit(frameFlags2, 1) {
flags.Encrypted = true
} else {
flags.Encrypted = false
}
if util.GetBit(frameFlags2, 1) {
flags.InGroup = true
} else {
flags.InGroup = false
}
header.Flags = flags
return header, nil
}
func getV24FrameHeader(fHeaderbytes []byte) (FrameHeader, error) {
var header FrameHeader
// ID
if !isValidFrameID(fHeaderbytes[0:4]) {
return FrameHeader{}, ErrInvalidID
}
header.ID = string(fHeaderbytes[0:4])
// Size
framesizeBytes := fHeaderbytes[4:8]
framesize := util.BytesToIntSynchsafe(framesizeBytes)
header.Size = framesize
// Flags
frameFlags1 := fHeaderbytes[8]
frameFlags2 := fHeaderbytes[9]
var flags FrameFlags
if util.GetBit(frameFlags1, 2) {
flags.TagAlterPreservation = true
} else {
flags.TagAlterPreservation = false
}
if util.GetBit(frameFlags1, 3) {
flags.FileAlterPreservation = true
} else {
flags.FileAlterPreservation = false
}
if util.GetBit(frameFlags1, 4) {
flags.ReadOnly = true
} else {
flags.ReadOnly = false
}
if util.GetBit(frameFlags2, 2) {
flags.InGroup = true
} else {
flags.InGroup = false
}
if util.GetBit(frameFlags2, 5) {
flags.Compressed = true
} else {
flags.Compressed = false
}
if util.GetBit(frameFlags2, 6) {
flags.Encrypted = true
} else {
flags.Encrypted = false
}
if util.GetBit(frameFlags2, 7) {
flags.Unsyrchronised = true
} else {
flags.Unsyrchronised = false
}
if util.GetBit(frameFlags2, 8) {
flags.HasDataLengthIndicator = true
} else {
flags.HasDataLengthIndicator = false
}
header.Flags = flags
return header, nil
}
// Structuralises frame header from given bytes. For versions see: constants.
func getFrameHeader(fHeaderbytes []byte, version string) (FrameHeader, error) {
// validation check
if int(len(fHeaderbytes)) != int(10) && int(len(fHeaderbytes)) != int(6) {
return FrameHeader{}, ErrInvalidFHeaderSize
}
var header FrameHeader
var err error
switch version {
case V2_2:
header, err = getV22FrameHeader(fHeaderbytes)
if err != nil {
return FrameHeader{}, err
}
case V2_3:
header, err = getV23FrameHeader(fHeaderbytes)
if err != nil {
return FrameHeader{}, err
}
case V2_4:
header, err = getV24FrameHeader(fHeaderbytes)
if err != nil {
return FrameHeader{}, err
}
}
return header, nil
}
// Reads ID3v2.3.0 or ID3v2.4.0 frame from given frame bytes.
// Returns a blank Frame struct if encountered an error, amount of
// bytes read from io.Reader.
func readNextFrame(r io.Reader, h Header) (Frame, uint64, error) {
var frame Frame
var read uint64 = 0
// Frame header
headerBytes, err := util.Read(r, uint64(HEADERSIZE))
if err != nil {
return Frame{}, 0, err
}
read += uint64(HEADERSIZE)
frameHeader, err := getFrameHeader(headerBytes, h.Version)
if err == ErrGotPadding || err == ErrInvalidID {
return Frame{}, read, err
} else if err != nil {
return Frame{}, read, fmt.Errorf("could not get header of a frame: %s", err)
}
frame.Header = frameHeader
// Contents
contents, err := util.Read(r, uint64(frameHeader.Size))
if err != nil {
return Frame{}, read, err
}
frame.Contents = contents
read += uint64(frameHeader.Size)
return frame, read, err
}
// Returns decoded string from f.Contents.
// Note that it can and probably will return
// corrupted data if you use it on non-text frames such as APIC
// for such cases please deal with raw []byte
func (f *Frame) Text() string {
return util.DecodeText(f.Contents)
}
// Converts frame to ready-to-write bytes
// func (f *Frame) ToBytes() []byte {
// buff := new(bytes.Buffer)
// // identifier
// buff.Write([]byte(f.Header.ID))
// // size
// buff.Write(util.IntToBytesSynchsafe(f.Header.Size))
// // flags
// return buff.Bytes()
// }
// Returns bytes of the frame that can be
// written into a file.
// func (f *Frame) Bytes() ([]byte, error) {
// header := f.Header
// contents := f.Contents
// var headerbytes []byte
// identifierBytes := []byte(header.ID)
// // sizeBytes
// return nil, nil
// }