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 { // Identifier string 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 } // 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() }