|
|
|
package v2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"unicode"
|
|
|
|
|
|
|
|
"github.com/Unbewohnte/id3ed/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// getters
|
|
|
|
|
|
|
|
func (fh *FrameHeader) ID() string {
|
|
|
|
return fh.id
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fh *FrameHeader) Size() uint32 {
|
|
|
|
return fh.size
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fh *FrameHeader) Flags() FrameFlags {
|
|
|
|
return fh.flags
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if given identifier is valid by specification
|
|
|
|
func isValidID(frameID string) bool {
|
|
|
|
// check if id is in ASCII table
|
|
|
|
if !util.InASCII(frameID) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if id is in upper case
|
|
|
|
for _, char := range frameID {
|
|
|
|
if !unicode.IsUpper(char) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func getV22FrameHeader(fHeaderbytes []byte) FrameHeader {
|
|
|
|
var header FrameHeader
|
|
|
|
|
|
|
|
header.id = string(fHeaderbytes[0:3])
|
|
|
|
|
|
|
|
framesizeBytes := util.BytesToIntSynchsafe(fHeaderbytes[3:6])
|
|
|
|
header.size = framesizeBytes
|
|
|
|
|
|
|
|
return header
|
|
|
|
}
|
|
|
|
|
|
|
|
func getV23FrameHeader(fHeaderbytes []byte) FrameHeader {
|
|
|
|
var header FrameHeader
|
|
|
|
|
|
|
|
// ID
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func getV24FrameHeader(fHeaderbytes []byte) FrameHeader {
|
|
|
|
var header FrameHeader
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Structuralises frame header from given bytes. For versions see: constants.
|
|
|
|
func getFrameHeader(fHeaderbytes []byte, version string) FrameHeader {
|
|
|
|
var header FrameHeader
|
|
|
|
|
|
|
|
switch version {
|
|
|
|
case V2_2:
|
|
|
|
header = getV22FrameHeader(fHeaderbytes)
|
|
|
|
|
|
|
|
case V2_3:
|
|
|
|
header = getV23FrameHeader(fHeaderbytes)
|
|
|
|
|
|
|
|
case V2_4:
|
|
|
|
header = getV24FrameHeader(fHeaderbytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
return header
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reads a frame from r.
|
|
|
|
// Returns a blank Frame struct if encountered an error.
|
|
|
|
func readNextFrame(r io.Reader, version string) (Frame, error) {
|
|
|
|
var frame Frame
|
|
|
|
|
|
|
|
// Frame header
|
|
|
|
var headerBytes []byte
|
|
|
|
var err error
|
|
|
|
switch version {
|
|
|
|
case V2_2:
|
|
|
|
headerBytes, err = util.Read(r, uint64(V2_2FrameHeaderSize))
|
|
|
|
if err != nil {
|
|
|
|
return Frame{}, err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
headerBytes, err = util.Read(r, uint64(V2_3FrameHeaderSize))
|
|
|
|
if err != nil {
|
|
|
|
return Frame{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for padding and validate ID characters
|
|
|
|
if bytes.Contains(headerBytes[0:3], []byte{0}) {
|
|
|
|
return Frame{}, ErrGotPadding
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isValidID(string(headerBytes[0:3])) {
|
|
|
|
return Frame{}, ErrInvalidID
|
|
|
|
}
|
|
|
|
|
|
|
|
frameHeader := getFrameHeader(headerBytes, version)
|
|
|
|
frame.Header = frameHeader
|
|
|
|
|
|
|
|
// Contents
|
|
|
|
contents, err := util.Read(r, uint64(frameHeader.size))
|
|
|
|
if err != nil {
|
|
|
|
return Frame{}, err
|
|
|
|
}
|
|
|
|
frame.Contents = contents
|
|
|
|
|
|
|
|
return frame, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func frameFlagsToBytes(ff FrameFlags, version string) []byte {
|
|
|
|
var flagBytes = []byte{0, 0}
|
|
|
|
|
|
|
|
switch version {
|
|
|
|
case V2_2:
|
|
|
|
return nil
|
|
|
|
|
|
|
|
case V2_3:
|
|
|
|
if ff.TagAlterPreservation {
|
|
|
|
flagBytes[0] = util.SetBit(flagBytes[0], 8)
|
|
|
|
}
|
|
|
|
if ff.FileAlterPreservation {
|
|
|
|
flagBytes[0] = util.SetBit(flagBytes[0], 7)
|
|
|
|
}
|
|
|
|
if ff.ReadOnly {
|
|
|
|
flagBytes[0] = util.SetBit(flagBytes[0], 6)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ff.Compressed {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 8)
|
|
|
|
}
|
|
|
|
if ff.Encrypted {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 7)
|
|
|
|
}
|
|
|
|
if ff.InGroup {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 6)
|
|
|
|
}
|
|
|
|
return flagBytes
|
|
|
|
|
|
|
|
case V2_4:
|
|
|
|
if ff.TagAlterPreservation {
|
|
|
|
flagBytes[0] = util.SetBit(flagBytes[0], 7)
|
|
|
|
}
|
|
|
|
if ff.FileAlterPreservation {
|
|
|
|
flagBytes[0] = util.SetBit(flagBytes[0], 6)
|
|
|
|
}
|
|
|
|
if ff.ReadOnly {
|
|
|
|
flagBytes[0] = util.SetBit(flagBytes[0], 5)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ff.InGroup {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 7)
|
|
|
|
}
|
|
|
|
if ff.Compressed {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 4)
|
|
|
|
}
|
|
|
|
if ff.Encrypted {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 3)
|
|
|
|
}
|
|
|
|
if ff.Unsyrchronised {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 2)
|
|
|
|
}
|
|
|
|
if ff.HasDataLengthIndicator {
|
|
|
|
flagBytes[1] = util.SetBit(flagBytes[1], 1)
|
|
|
|
}
|
|
|
|
return flagBytes
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
var version string
|
|
|
|
if len(f.Header.id) == 4 {
|
|
|
|
version = V2_4
|
|
|
|
} else {
|
|
|
|
version = V2_2
|
|
|
|
}
|
|
|
|
|
|
|
|
flagBytes := frameFlagsToBytes(f.Header.flags, version)
|
|
|
|
if flagBytes != nil {
|
|
|
|
buff.Write(flagBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// contents
|
|
|
|
buff.Write(f.Contents)
|
|
|
|
|
|
|
|
return buff.Bytes()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constructs a new frame from provided information.
|
|
|
|
// isTextFrame must be set to true if gContents are not a binary data.
|
|
|
|
// Returns an error if provided id`s len is neither 3 or 4
|
|
|
|
func NewFrame(id string, gContents []byte, isTextframe bool) (*Frame, error) {
|
|
|
|
if len(id) != 3 && len(id) != 4 {
|
|
|
|
return nil, fmt.Errorf("frame identifier`s length must be 3 or 4")
|
|
|
|
}
|
|
|
|
|
|
|
|
var fheader FrameHeader
|
|
|
|
|
|
|
|
// contents
|
|
|
|
var contents []byte
|
|
|
|
if isTextframe {
|
|
|
|
// add UTF-8 encoding byte
|
|
|
|
contents = []byte{util.EncodingUTF8}
|
|
|
|
contents = append(contents, gContents...)
|
|
|
|
} else {
|
|
|
|
contents = gContents
|
|
|
|
}
|
|
|
|
|
|
|
|
// id
|
|
|
|
fheader.id = id
|
|
|
|
|
|
|
|
// size
|
|
|
|
fheader.size = uint32(len(contents))
|
|
|
|
|
|
|
|
// flags (all false)
|
|
|
|
fheader.flags = FrameFlags{}
|
|
|
|
|
|
|
|
return &Frame{
|
|
|
|
Header: fheader,
|
|
|
|
Contents: contents,
|
|
|
|
}, nil
|
|
|
|
}
|