⬥ 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.

389 lines
7.5 KiB

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
}