ID3v2.4 tag for .mp3 files: Nim parsing library

File extension

mp3

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of ID3v2.4 tag for .mp3 files using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Nim source code to parse ID3v2.4 tag for .mp3 files

id3v2_4.nim

import kaitai_struct_nim_runtime
import options

type
  Id3v24* = ref object of KaitaiStruct
    `tag`*: Id3v24_Tag
    `parent`*: KaitaiStruct
  Id3v24_U1beSynchsafe* = ref object of KaitaiStruct
    `padding`*: bool
    `value`*: uint64
    `parent`*: Id3v24_U2beSynchsafe
  Id3v24_U2beSynchsafe* = ref object of KaitaiStruct
    `byte0`*: Id3v24_U1beSynchsafe
    `byte1`*: Id3v24_U1beSynchsafe
    `parent`*: Id3v24_U4beSynchsafe
    `valueInst`*: int
  Id3v24_Tag* = ref object of KaitaiStruct
    `header`*: Id3v24_Header
    `headerEx`*: Id3v24_HeaderEx
    `frames`*: seq[Id3v24_Frame]
    `padding`*: Id3v24_Padding
    `footer`*: Id3v24_Footer
    `parent`*: Id3v24
  Id3v24_U4beSynchsafe* = ref object of KaitaiStruct
    `short0`*: Id3v24_U2beSynchsafe
    `short1`*: Id3v24_U2beSynchsafe
    `parent`*: KaitaiStruct
    `valueInst`*: int
  Id3v24_Frame* = ref object of KaitaiStruct
    `id`*: string
    `size`*: Id3v24_U4beSynchsafe
    `flagsStatus`*: Id3v24_Frame_FlagsStatus
    `flagsFormat`*: Id3v24_Frame_FlagsFormat
    `data`*: seq[byte]
    `parent`*: Id3v24_Tag
    `isInvalidInst`*: bool
  Id3v24_Frame_FlagsStatus* = ref object of KaitaiStruct
    `reserved1`*: bool
    `flagDiscardAlterTag`*: bool
    `flagDiscardAlterFile`*: bool
    `flagReadOnly`*: bool
    `reserved2`*: uint64
    `parent`*: Id3v24_Frame
  Id3v24_Frame_FlagsFormat* = ref object of KaitaiStruct
    `reserved1`*: bool
    `flagGrouping`*: bool
    `reserved2`*: uint64
    `flagCompressed`*: bool
    `flagEncrypted`*: bool
    `flagUnsynchronisated`*: bool
    `flagIndicator`*: bool
    `parent`*: Id3v24_Frame
  Id3v24_HeaderEx* = ref object of KaitaiStruct
    `size`*: Id3v24_U4beSynchsafe
    `flagsEx`*: Id3v24_HeaderEx_FlagsEx
    `data`*: seq[byte]
    `parent`*: Id3v24_Tag
  Id3v24_HeaderEx_FlagsEx* = ref object of KaitaiStruct
    `reserved1`*: bool
    `flagUpdate`*: bool
    `flagCrc`*: bool
    `flagRestrictions`*: bool
    `reserved2`*: uint64
    `parent`*: Id3v24_HeaderEx
  Id3v24_Header* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `versionMajor`*: uint8
    `versionRevision`*: uint8
    `flags`*: Id3v24_Header_Flags
    `size`*: Id3v24_U4beSynchsafe
    `parent`*: Id3v24_Tag
  Id3v24_Header_Flags* = ref object of KaitaiStruct
    `flagUnsynchronization`*: bool
    `flagHeaderex`*: bool
    `flagExperimental`*: bool
    `flagFooter`*: bool
    `reserved`*: uint64
    `parent`*: Id3v24_Header
  Id3v24_Padding* = ref object of KaitaiStruct
    `padding`*: seq[byte]
    `parent`*: Id3v24_Tag
  Id3v24_Footer* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `versionMajor`*: uint8
    `versionRevision`*: uint8
    `flags`*: Id3v24_Footer_Flags
    `size`*: Id3v24_U4beSynchsafe
    `parent`*: Id3v24_Tag
  Id3v24_Footer_Flags* = ref object of KaitaiStruct
    `flagUnsynchronization`*: bool
    `flagHeaderex`*: bool
    `flagExperimental`*: bool
    `flagFooter`*: bool
    `reserved`*: uint64
    `parent`*: Id3v24_Footer

proc read*(_: typedesc[Id3v24], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Id3v24
proc read*(_: typedesc[Id3v24_U1beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_U2beSynchsafe): Id3v24_U1beSynchsafe
proc read*(_: typedesc[Id3v24_U2beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_U4beSynchsafe): Id3v24_U2beSynchsafe
proc read*(_: typedesc[Id3v24_Tag], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24): Id3v24_Tag
proc read*(_: typedesc[Id3v24_U4beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Id3v24_U4beSynchsafe
proc read*(_: typedesc[Id3v24_Frame], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Frame
proc read*(_: typedesc[Id3v24_Frame_FlagsStatus], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Frame): Id3v24_Frame_FlagsStatus
proc read*(_: typedesc[Id3v24_Frame_FlagsFormat], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Frame): Id3v24_Frame_FlagsFormat
proc read*(_: typedesc[Id3v24_HeaderEx], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_HeaderEx
proc read*(_: typedesc[Id3v24_HeaderEx_FlagsEx], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_HeaderEx): Id3v24_HeaderEx_FlagsEx
proc read*(_: typedesc[Id3v24_Header], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Header
proc read*(_: typedesc[Id3v24_Header_Flags], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Header): Id3v24_Header_Flags
proc read*(_: typedesc[Id3v24_Padding], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Padding
proc read*(_: typedesc[Id3v24_Footer], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Footer
proc read*(_: typedesc[Id3v24_Footer_Flags], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Footer): Id3v24_Footer_Flags

proc value*(this: Id3v24_U2beSynchsafe): int
proc value*(this: Id3v24_U4beSynchsafe): int
proc isInvalid*(this: Id3v24_Frame): bool


##[
@see <a href="http://id3.org/id3v2.4.0-structure">Source</a>
@see <a href="http://id3.org/id3v2.4.0-frames">Source</a>
]##
proc read*(_: typedesc[Id3v24], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Id3v24 =
  template this: untyped = result
  this = new(Id3v24)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let tagExpr = Id3v24_Tag.read(this.io, this.root, this)
  this.tag = tagExpr

proc fromFile*(_: typedesc[Id3v24], filename: string): Id3v24 =
  Id3v24.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_U1beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_U2beSynchsafe): Id3v24_U1beSynchsafe =
  template this: untyped = result
  this = new(Id3v24_U1beSynchsafe)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let paddingExpr = this.io.readBitsIntBe(1) != 0
  this.padding = paddingExpr
  let valueExpr = this.io.readBitsIntBe(7)
  this.value = valueExpr

proc fromFile*(_: typedesc[Id3v24_U1beSynchsafe], filename: string): Id3v24_U1beSynchsafe =
  Id3v24_U1beSynchsafe.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_U2beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_U4beSynchsafe): Id3v24_U2beSynchsafe =
  template this: untyped = result
  this = new(Id3v24_U2beSynchsafe)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let byte0Expr = Id3v24_U1beSynchsafe.read(this.io, this.root, this)
  this.byte0 = byte0Expr
  let byte1Expr = Id3v24_U1beSynchsafe.read(this.io, this.root, this)
  this.byte1 = byte1Expr

proc value(this: Id3v24_U2beSynchsafe): int = 
  if this.valueInst != nil:
    return this.valueInst
  let valueInstExpr = int(((this.byte0.value shl 7) or this.byte1.value))
  this.valueInst = valueInstExpr
  if this.valueInst != nil:
    return this.valueInst

proc fromFile*(_: typedesc[Id3v24_U2beSynchsafe], filename: string): Id3v24_U2beSynchsafe =
  Id3v24_U2beSynchsafe.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Tag], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24): Id3v24_Tag =
  template this: untyped = result
  this = new(Id3v24_Tag)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let headerExpr = Id3v24_Header.read(this.io, this.root, this)
  this.header = headerExpr
  if this.header.flags.flagHeaderex:
    let headerExExpr = Id3v24_HeaderEx.read(this.io, this.root, this)
    this.headerEx = headerExExpr
  block:
    var i: int
    while true:
      let it = Id3v24_Frame.read(this.io, this.root, this)
      this.frames.add(it)
      if  (((this.io.pos + it.size.value) > this.header.size.value) or (it.isInvalid)) :
        break
      inc i
  if not(this.header.flags.flagFooter):
    let paddingExpr = Id3v24_Padding.read(this.io, this.root, this)
    this.padding = paddingExpr
  if this.header.flags.flagFooter:
    let footerExpr = Id3v24_Footer.read(this.io, this.root, this)
    this.footer = footerExpr

proc fromFile*(_: typedesc[Id3v24_Tag], filename: string): Id3v24_Tag =
  Id3v24_Tag.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_U4beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Id3v24_U4beSynchsafe =
  template this: untyped = result
  this = new(Id3v24_U4beSynchsafe)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let short0Expr = Id3v24_U2beSynchsafe.read(this.io, this.root, this)
  this.short0 = short0Expr
  let short1Expr = Id3v24_U2beSynchsafe.read(this.io, this.root, this)
  this.short1 = short1Expr

proc value(this: Id3v24_U4beSynchsafe): int = 
  if this.valueInst != nil:
    return this.valueInst
  let valueInstExpr = int(((this.short0.value shl 14) or this.short1.value))
  this.valueInst = valueInstExpr
  if this.valueInst != nil:
    return this.valueInst

proc fromFile*(_: typedesc[Id3v24_U4beSynchsafe], filename: string): Id3v24_U4beSynchsafe =
  Id3v24_U4beSynchsafe.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Frame], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Frame =
  template this: untyped = result
  this = new(Id3v24_Frame)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let idExpr = encode(this.io.readBytes(int(4)), "ASCII")
  this.id = idExpr
  let sizeExpr = Id3v24_U4beSynchsafe.read(this.io, this.root, this)
  this.size = sizeExpr
  let flagsStatusExpr = Id3v24_Frame_FlagsStatus.read(this.io, this.root, this)
  this.flagsStatus = flagsStatusExpr
  let flagsFormatExpr = Id3v24_Frame_FlagsFormat.read(this.io, this.root, this)
  this.flagsFormat = flagsFormatExpr
  let dataExpr = this.io.readBytes(int(this.size.value))
  this.data = dataExpr

proc isInvalid(this: Id3v24_Frame): bool = 
  if this.isInvalidInst != nil:
    return this.isInvalidInst
  let isInvalidInstExpr = bool(this.id == "\000\000\000\000")
  this.isInvalidInst = isInvalidInstExpr
  if this.isInvalidInst != nil:
    return this.isInvalidInst

proc fromFile*(_: typedesc[Id3v24_Frame], filename: string): Id3v24_Frame =
  Id3v24_Frame.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Frame_FlagsStatus], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Frame): Id3v24_Frame_FlagsStatus =
  template this: untyped = result
  this = new(Id3v24_Frame_FlagsStatus)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let reserved1Expr = this.io.readBitsIntBe(1) != 0
  this.reserved1 = reserved1Expr
  let flagDiscardAlterTagExpr = this.io.readBitsIntBe(1) != 0
  this.flagDiscardAlterTag = flagDiscardAlterTagExpr
  let flagDiscardAlterFileExpr = this.io.readBitsIntBe(1) != 0
  this.flagDiscardAlterFile = flagDiscardAlterFileExpr
  let flagReadOnlyExpr = this.io.readBitsIntBe(1) != 0
  this.flagReadOnly = flagReadOnlyExpr
  let reserved2Expr = this.io.readBitsIntBe(4)
  this.reserved2 = reserved2Expr

proc fromFile*(_: typedesc[Id3v24_Frame_FlagsStatus], filename: string): Id3v24_Frame_FlagsStatus =
  Id3v24_Frame_FlagsStatus.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Frame_FlagsFormat], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Frame): Id3v24_Frame_FlagsFormat =
  template this: untyped = result
  this = new(Id3v24_Frame_FlagsFormat)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let reserved1Expr = this.io.readBitsIntBe(1) != 0
  this.reserved1 = reserved1Expr
  let flagGroupingExpr = this.io.readBitsIntBe(1) != 0
  this.flagGrouping = flagGroupingExpr
  let reserved2Expr = this.io.readBitsIntBe(2)
  this.reserved2 = reserved2Expr
  let flagCompressedExpr = this.io.readBitsIntBe(1) != 0
  this.flagCompressed = flagCompressedExpr
  let flagEncryptedExpr = this.io.readBitsIntBe(1) != 0
  this.flagEncrypted = flagEncryptedExpr
  let flagUnsynchronisatedExpr = this.io.readBitsIntBe(1) != 0
  this.flagUnsynchronisated = flagUnsynchronisatedExpr
  let flagIndicatorExpr = this.io.readBitsIntBe(1) != 0
  this.flagIndicator = flagIndicatorExpr

proc fromFile*(_: typedesc[Id3v24_Frame_FlagsFormat], filename: string): Id3v24_Frame_FlagsFormat =
  Id3v24_Frame_FlagsFormat.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_HeaderEx], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_HeaderEx =
  template this: untyped = result
  this = new(Id3v24_HeaderEx)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let sizeExpr = Id3v24_U4beSynchsafe.read(this.io, this.root, this)
  this.size = sizeExpr
  let flagsExExpr = Id3v24_HeaderEx_FlagsEx.read(this.io, this.root, this)
  this.flagsEx = flagsExExpr
  let dataExpr = this.io.readBytes(int((this.size.value - 5)))
  this.data = dataExpr

proc fromFile*(_: typedesc[Id3v24_HeaderEx], filename: string): Id3v24_HeaderEx =
  Id3v24_HeaderEx.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_HeaderEx_FlagsEx], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_HeaderEx): Id3v24_HeaderEx_FlagsEx =
  template this: untyped = result
  this = new(Id3v24_HeaderEx_FlagsEx)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let reserved1Expr = this.io.readBitsIntBe(1) != 0
  this.reserved1 = reserved1Expr
  let flagUpdateExpr = this.io.readBitsIntBe(1) != 0
  this.flagUpdate = flagUpdateExpr
  let flagCrcExpr = this.io.readBitsIntBe(1) != 0
  this.flagCrc = flagCrcExpr
  let flagRestrictionsExpr = this.io.readBitsIntBe(1) != 0
  this.flagRestrictions = flagRestrictionsExpr
  let reserved2Expr = this.io.readBitsIntBe(4)
  this.reserved2 = reserved2Expr

proc fromFile*(_: typedesc[Id3v24_HeaderEx_FlagsEx], filename: string): Id3v24_HeaderEx_FlagsEx =
  Id3v24_HeaderEx_FlagsEx.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Header], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Header =
  template this: untyped = result
  this = new(Id3v24_Header)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = this.io.readBytes(int(3))
  this.magic = magicExpr
  let versionMajorExpr = this.io.readU1()
  this.versionMajor = versionMajorExpr
  let versionRevisionExpr = this.io.readU1()
  this.versionRevision = versionRevisionExpr
  let flagsExpr = Id3v24_Header_Flags.read(this.io, this.root, this)
  this.flags = flagsExpr
  let sizeExpr = Id3v24_U4beSynchsafe.read(this.io, this.root, this)
  this.size = sizeExpr

proc fromFile*(_: typedesc[Id3v24_Header], filename: string): Id3v24_Header =
  Id3v24_Header.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Header_Flags], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Header): Id3v24_Header_Flags =
  template this: untyped = result
  this = new(Id3v24_Header_Flags)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let flagUnsynchronizationExpr = this.io.readBitsIntBe(1) != 0
  this.flagUnsynchronization = flagUnsynchronizationExpr
  let flagHeaderexExpr = this.io.readBitsIntBe(1) != 0
  this.flagHeaderex = flagHeaderexExpr
  let flagExperimentalExpr = this.io.readBitsIntBe(1) != 0
  this.flagExperimental = flagExperimentalExpr
  let flagFooterExpr = this.io.readBitsIntBe(1) != 0
  this.flagFooter = flagFooterExpr
  let reservedExpr = this.io.readBitsIntBe(4)
  this.reserved = reservedExpr

proc fromFile*(_: typedesc[Id3v24_Header_Flags], filename: string): Id3v24_Header_Flags =
  Id3v24_Header_Flags.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Padding], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Padding =
  template this: untyped = result
  this = new(Id3v24_Padding)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let paddingExpr = this.io.readBytes(int((Id3v24(this.root).tag.header.size.value - this.io.pos)))
  this.padding = paddingExpr

proc fromFile*(_: typedesc[Id3v24_Padding], filename: string): Id3v24_Padding =
  Id3v24_Padding.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Footer], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Tag): Id3v24_Footer =
  template this: untyped = result
  this = new(Id3v24_Footer)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = this.io.readBytes(int(3))
  this.magic = magicExpr
  let versionMajorExpr = this.io.readU1()
  this.versionMajor = versionMajorExpr
  let versionRevisionExpr = this.io.readU1()
  this.versionRevision = versionRevisionExpr
  let flagsExpr = Id3v24_Footer_Flags.read(this.io, this.root, this)
  this.flags = flagsExpr
  let sizeExpr = Id3v24_U4beSynchsafe.read(this.io, this.root, this)
  this.size = sizeExpr

proc fromFile*(_: typedesc[Id3v24_Footer], filename: string): Id3v24_Footer =
  Id3v24_Footer.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Id3v24_Footer_Flags], io: KaitaiStream, root: KaitaiStruct, parent: Id3v24_Footer): Id3v24_Footer_Flags =
  template this: untyped = result
  this = new(Id3v24_Footer_Flags)
  let root = if root == nil: cast[Id3v24](this) else: cast[Id3v24](root)
  this.io = io
  this.root = root
  this.parent = parent

  let flagUnsynchronizationExpr = this.io.readBitsIntBe(1) != 0
  this.flagUnsynchronization = flagUnsynchronizationExpr
  let flagHeaderexExpr = this.io.readBitsIntBe(1) != 0
  this.flagHeaderex = flagHeaderexExpr
  let flagExperimentalExpr = this.io.readBitsIntBe(1) != 0
  this.flagExperimental = flagExperimentalExpr
  let flagFooterExpr = this.io.readBitsIntBe(1) != 0
  this.flagFooter = flagFooterExpr
  let reservedExpr = this.io.readBitsIntBe(4)
  this.reserved = reservedExpr

proc fromFile*(_: typedesc[Id3v24_Footer_Flags], filename: string): Id3v24_Footer_Flags =
  Id3v24_Footer_Flags.read(newKaitaiFileStream(filename), nil, nil)