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

484 lines
10 KiB

package v2
import (
"bytes"
"fmt"
"io"
"github.com/Unbewohnte/id3ed/util"
)
// Main header`s flags
type HeaderFlags struct {
Unsynchronised bool
Compressed bool
HasExtendedHeader bool
Experimental bool
FooterPresent bool
}
// ID3v2.x`s main header structure
type Header struct {
flags HeaderFlags
version string
size uint32
extendedHeader ExtendedHeader
}
// Extended header`s flags
type ExtendedHeaderFlags struct {
UpdateTag bool
CRCpresent bool
HasRestrictions bool
Restrictions byte // a `lazy` approach :), just for now, maybe...
}
type ExtendedHeader struct {
size uint32
flags ExtendedHeaderFlags
paddingSize uint32
crcData []byte
}
// using ONLY getters on header, because
// header MUST NOT be changed manually
// from the outside of the package
func (h *Header) Version() string {
return h.version
}
func (h *Header) Flags() HeaderFlags {
return h.flags
}
func (h *Header) Size() uint32 {
return h.size
}
func (h *Header) ExtendedHeader() *ExtendedHeader {
if h.flags.HasExtendedHeader {
return &h.extendedHeader
}
return nil
}
// Reads and structuralises extended header. Must
// be called AFTER the main header has beeen read (does not seek).
// ALSO ISN`T TESTED !!!
func (h *Header) readExtendedHeader(r io.Reader) error {
// h.ExtendedHeader = ExtendedHeader{}
if !h.Flags().HasExtendedHeader {
return nil
}
var extended ExtendedHeader
// extended size
extendedSize, err := util.Read(r, 4)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
switch h.Version() {
case V2_3:
extended.size = util.BytesToInt(extendedSize)
case V2_4:
extended.size = util.BytesToIntSynchsafe(extendedSize)
}
// extended flags
switch h.Version() {
case V2_3:
extendedFlag, err := util.Read(r, 2) // reading flag byte and a null-byte after
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
if util.GetBit(extendedFlag[0], 1) {
extended.flags.CRCpresent = true
} else {
extended.flags.CRCpresent = false
}
case V2_4:
// skipping `Number of flag bytes` because it`s always `1`
_, err := util.Read(r, 1)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
flagByte, err := util.Read(r, 1)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
if util.GetBit(flagByte[0], 2) {
extended.flags.UpdateTag = true
} else {
extended.flags.UpdateTag = false
}
if util.GetBit(flagByte[0], 3) {
extended.flags.CRCpresent = true
} else {
extended.flags.CRCpresent = false
}
if util.GetBit(flagByte[0], 4) {
extended.flags.HasRestrictions = true
} else {
extended.flags.HasRestrictions = false
}
}
// extracting data given by flags
switch h.Version() {
case V2_3:
if extended.flags.CRCpresent {
crcData, err := util.Read(r, 4)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
extended.crcData = crcData
}
case V2_4:
// `Each flag that is set in the extended header has data attached`
if extended.flags.UpdateTag {
// skipping null-byte length of `UpdateTag`
_, err := util.Read(r, 1)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
}
if extended.flags.CRCpresent {
crclen, err := util.Read(r, 1)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
crcData, err := util.Read(r, uint64(crclen[0]))
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
extended.crcData = crcData
}
if extended.flags.HasRestrictions {
// skipping one-byte length of `Restrictions`, because it`s always `1`
_, err := util.Read(r, 1)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
restrictionsByte, err := util.Read(r, 1)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
// a `lazy` approach :), just for now
extended.flags.Restrictions = restrictionsByte[0]
}
}
// extracting other version-dependent header data
// padding if V2_3
if h.Version() == V2_3 {
paddingSizeBytes, err := util.Read(r, 4)
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
paddingSize := util.BytesToInt(paddingSizeBytes)
extended.paddingSize = paddingSize
}
// finally `attaching` parsed extended header to the main *Header
h.extendedHeader = extended
return nil
}
// Reads and structuralises ID3v2 header and if present - extended header.
// Returns a blank header struct if encountered an error
func readHeader(rs io.ReadSeeker) (Header, error) {
_, err := rs.Seek(0, io.SeekStart)
if err != nil {
return Header{}, fmt.Errorf("could not seek: %s", err)
}
hBytes, err := util.Read(rs, uint64(HEADERSIZE))
if err != nil {
return Header{}, fmt.Errorf("could not read from reader: %s", err)
}
var header Header
identifier := hBytes[0:3]
// check if has ID3v2 identifier
if !bytes.Equal([]byte(HEADERIDENTIFIER), identifier) {
return Header{}, ErrDoesNotUseID3v2
}
// version
majorVersion := int(hBytes[3])
revisionNumber := int(hBytes[4])
switch majorVersion {
case 2:
header.version = V2_2
case 3:
header.version = V2_3
case 4:
header.version = V2_4
default:
return Header{}, fmt.Errorf("ID3v2.%d.%d is not supported or invalid", majorVersion, revisionNumber)
}
// flags
flags := hBytes[5]
// v3.0 and v4.0 have different amount of flags
switch header.Version() {
case V2_2:
if util.GetBit(flags, 1) {
header.flags.Unsynchronised = true
} else {
header.flags.Unsynchronised = false
}
if util.GetBit(flags, 2) {
header.flags.Compressed = true
} else {
header.flags.Compressed = false
}
case V2_3:
if util.GetBit(flags, 1) {
header.flags.Unsynchronised = true
} else {
header.flags.Unsynchronised = false
}
if util.GetBit(flags, 2) {
header.flags.HasExtendedHeader = true
} else {
header.flags.HasExtendedHeader = false
}
if util.GetBit(flags, 3) {
header.flags.Experimental = true
} else {
header.flags.Experimental = false
}
// always false, because ID3v2.3.0 does not support footers
header.flags.FooterPresent = false
case V2_4:
if util.GetBit(flags, 1) {
header.flags.Unsynchronised = true
} else {
header.flags.Unsynchronised = false
}
if util.GetBit(flags, 2) {
header.flags.HasExtendedHeader = true
} else {
header.flags.HasExtendedHeader = false
}
if util.GetBit(flags, 3) {
header.flags.Experimental = true
} else {
header.flags.Experimental = false
}
if util.GetBit(flags, 4) {
header.flags.FooterPresent = true
} else {
header.flags.FooterPresent = false
}
}
// size
sizeBytes := hBytes[6:]
size := util.BytesToIntSynchsafe(sizeBytes)
header.size = size
if header.flags.HasExtendedHeader {
err = header.readExtendedHeader(rs)
if err != nil {
return header, err
}
}
return header, nil
}
// Converts given HeaderFlags struct into ready-to-write byte
// containing flags
func headerFlagsToByte(hf HeaderFlags, version string) byte {
var flagsByte byte = 0
switch version {
case V2_2:
if hf.Unsynchronised {
flagsByte = util.SetBit(flagsByte, 8)
}
if hf.Compressed {
flagsByte = util.SetBit(flagsByte, 7)
}
case V2_3:
if hf.Unsynchronised {
flagsByte = util.SetBit(flagsByte, 8)
}
if hf.HasExtendedHeader {
flagsByte = util.SetBit(flagsByte, 7)
}
if hf.Experimental {
flagsByte = util.SetBit(flagsByte, 6)
}
case V2_4:
if hf.Unsynchronised {
flagsByte = util.SetBit(flagsByte, 8)
}
if hf.HasExtendedHeader {
flagsByte = util.SetBit(flagsByte, 7)
}
if hf.Experimental {
flagsByte = util.SetBit(flagsByte, 6)
}
if hf.FooterPresent {
flagsByte = util.SetBit(flagsByte, 5)
}
}
return flagsByte
}
// Converts given header into ready-to-write bytes
func (h *Header) toBytes() []byte {
buff := new(bytes.Buffer)
// id
buff.Write([]byte(HEADERIDENTIFIER))
// version
version := []byte{0, 0}
switch h.Version() {
case V2_2:
version = []byte{2, 0}
case V2_3:
version = []byte{3, 0}
case V2_4:
version = []byte{4, 0}
}
buff.Write(version)
// flags
flagByte := headerFlagsToByte(h.flags, h.version)
buff.WriteByte(flagByte)
// size
tagSize := util.IntToBytesSynchsafe(h.size)
buff.Write(tagSize)
// extended header
if !h.flags.HasExtendedHeader {
return buff.Bytes()
}
// double check for possible errors
if h.Version() == V2_2 {
return buff.Bytes()
}
// size
extSize := util.IntToBytes(h.extendedHeader.size)
buff.Write(extSize)
// flags and other version specific fields
switch h.Version() {
case V2_3:
// flags
flagBytes := []byte{0, 0}
if h.extendedHeader.flags.CRCpresent {
flagBytes[0] = util.SetBit(flagBytes[0], 8)
}
buff.Write(flagBytes)
// crc data
if h.extendedHeader.flags.CRCpresent {
buff.Write(h.extendedHeader.crcData)
}
// padding size
paddingSize := util.IntToBytes(h.extendedHeader.paddingSize)
buff.Write(paddingSize)
case V2_4:
numberOfFlagBytes := byte(1)
buff.WriteByte(numberOfFlagBytes)
extFlags := byte(0)
if h.extendedHeader.flags.UpdateTag {
extFlags = util.SetBit(extFlags, 7)
}
if h.extendedHeader.flags.CRCpresent {
extFlags = util.SetBit(extFlags, 6)
}
if h.extendedHeader.flags.HasRestrictions {
extFlags = util.SetBit(extFlags, 5)
}
buff.WriteByte(extFlags)
// writing data, provided by flags
if h.extendedHeader.flags.UpdateTag {
// data len
buff.WriteByte(0)
}
if h.extendedHeader.flags.CRCpresent {
// data len
buff.WriteByte(5)
// data
buff.Write(h.extendedHeader.crcData)
}
if h.extendedHeader.flags.HasRestrictions {
// data len
buff.WriteByte(1)
// data
buff.WriteByte(h.extendedHeader.flags.Restrictions)
}
}
return buff.Bytes()
}
// Creates an appropriate header based on
// provided frames
func newHeader(frames []Frame) *Header {
var header Header
// flags (all false)
header.flags = HeaderFlags{}
// version
if len(frames[0].Header.ID()) == 4 {
header.version = V2_4
} else {
header.version = V2_2
}
// size
var framesSize uint32 = 0
for _, frame := range frames {
framesSize += uint32(len(frame.toBytes()))
}
header.size = framesSize
// extended header (not used)
header.extendedHeader = ExtendedHeader{}
return &header
}