ID3v2.3 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.3 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.3 tag for .mp3 files

id3v2_3.nim

import kaitai_struct_nim_runtime
import options

type
  Id3v23* = ref object of KaitaiStruct
    `tag`*: Id3v23_Tag
    `parent`*: KaitaiStruct
  Id3v23_U1beSynchsafe* = ref object of KaitaiStruct
    `padding`*: bool
    `value`*: uint64
    `parent`*: Id3v23_U2beSynchsafe
  Id3v23_U2beSynchsafe* = ref object of KaitaiStruct
    `byte0`*: Id3v23_U1beSynchsafe
    `byte1`*: Id3v23_U1beSynchsafe
    `parent`*: Id3v23_U4beSynchsafe
    `valueInst`: int
    `valueInstFlag`: bool
  Id3v23_Tag* = ref object of KaitaiStruct
    `header`*: Id3v23_Header
    `headerEx`*: Id3v23_HeaderEx
    `frames`*: seq[Id3v23_Frame]
    `padding`*: seq[byte]
    `parent`*: Id3v23
  Id3v23_U4beSynchsafe* = ref object of KaitaiStruct
    `short0`*: Id3v23_U2beSynchsafe
    `short1`*: Id3v23_U2beSynchsafe
    `parent`*: Id3v23_Header
    `valueInst`: int
    `valueInstFlag`: bool
  Id3v23_Frame* = ref object of KaitaiStruct
    `id`*: string
    `size`*: uint32
    `flags`*: Id3v23_Frame_Flags
    `data`*: seq[byte]
    `parent`*: Id3v23_Tag
    `isInvalidInst`: bool
    `isInvalidInstFlag`: bool
  Id3v23_Frame_Flags* = ref object of KaitaiStruct
    `flagDiscardAlterTag`*: bool
    `flagDiscardAlterFile`*: bool
    `flagReadOnly`*: bool
    `reserved1`*: uint64
    `flagCompressed`*: bool
    `flagEncrypted`*: bool
    `flagGrouping`*: bool
    `reserved2`*: uint64
    `parent`*: Id3v23_Frame
  Id3v23_HeaderEx* = ref object of KaitaiStruct
    `size`*: uint32
    `flagsEx`*: Id3v23_HeaderEx_FlagsEx
    `paddingSize`*: uint32
    `crc`*: uint32
    `parent`*: Id3v23_Tag
  Id3v23_HeaderEx_FlagsEx* = ref object of KaitaiStruct
    `flagCrc`*: bool
    `reserved`*: uint64
    `parent`*: Id3v23_HeaderEx
  Id3v23_Header* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `versionMajor`*: uint8
    `versionRevision`*: uint8
    `flags`*: Id3v23_Header_Flags
    `size`*: Id3v23_U4beSynchsafe
    `parent`*: Id3v23_Tag
  Id3v23_Header_Flags* = ref object of KaitaiStruct
    `flagUnsynchronization`*: bool
    `flagHeaderex`*: bool
    `flagExperimental`*: bool
    `reserved`*: uint64
    `parent`*: Id3v23_Header

proc read*(_: typedesc[Id3v23], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Id3v23
proc read*(_: typedesc[Id3v23_U1beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_U2beSynchsafe): Id3v23_U1beSynchsafe
proc read*(_: typedesc[Id3v23_U2beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_U4beSynchsafe): Id3v23_U2beSynchsafe
proc read*(_: typedesc[Id3v23_Tag], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23): Id3v23_Tag
proc read*(_: typedesc[Id3v23_U4beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Header): Id3v23_U4beSynchsafe
proc read*(_: typedesc[Id3v23_Frame], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Tag): Id3v23_Frame
proc read*(_: typedesc[Id3v23_Frame_Flags], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Frame): Id3v23_Frame_Flags
proc read*(_: typedesc[Id3v23_HeaderEx], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Tag): Id3v23_HeaderEx
proc read*(_: typedesc[Id3v23_HeaderEx_FlagsEx], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_HeaderEx): Id3v23_HeaderEx_FlagsEx
proc read*(_: typedesc[Id3v23_Header], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Tag): Id3v23_Header
proc read*(_: typedesc[Id3v23_Header_Flags], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Header): Id3v23_Header_Flags

proc value*(this: Id3v23_U2beSynchsafe): int
proc value*(this: Id3v23_U4beSynchsafe): int
proc isInvalid*(this: Id3v23_Frame): bool


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

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

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

proc read*(_: typedesc[Id3v23_U1beSynchsafe], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_U2beSynchsafe): Id3v23_U1beSynchsafe =
  template this: untyped = result
  this = new(Id3v23_U1beSynchsafe)
  let root = if root == nil: cast[Id3v23](this) else: cast[Id3v23](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[Id3v23_U1beSynchsafe], filename: string): Id3v23_U1beSynchsafe =
  Id3v23_U1beSynchsafe.read(newKaitaiFileStream(filename), nil, nil)

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

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

proc value(this: Id3v23_U2beSynchsafe): int = 
  if this.valueInstFlag:
    return this.valueInst
  let valueInstExpr = int(((this.byte0.value shl 7) or this.byte1.value))
  this.valueInst = valueInstExpr
  this.valueInstFlag = true
  return this.valueInst

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


##[
@see "Section 3. ID3v2 overview"
]##
proc read*(_: typedesc[Id3v23_Tag], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23): Id3v23_Tag =
  template this: untyped = result
  this = new(Id3v23_Tag)
  let root = if root == nil: cast[Id3v23](this) else: cast[Id3v23](root)
  this.io = io
  this.root = root
  this.parent = parent

  let headerExpr = Id3v23_Header.read(this.io, this.root, this)
  this.header = headerExpr
  if this.header.flags.flagHeaderex:
    let headerExExpr = Id3v23_HeaderEx.read(this.io, this.root, this)
    this.headerEx = headerExExpr
  block:
    var i: int
    while true:
      let it = Id3v23_Frame.read(this.io, this.root, this)
      this.frames.add(it)
      if  (((this.io.pos + it.size) > this.header.size.value) or (it.isInvalid)) :
        break
      inc i
  if this.header.flags.flagHeaderex:
    let paddingExpr = this.io.readBytes(int((this.headerEx.paddingSize - this.io.pos)))
    this.padding = paddingExpr

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

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

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

proc value(this: Id3v23_U4beSynchsafe): int = 
  if this.valueInstFlag:
    return this.valueInst
  let valueInstExpr = int(((this.short0.value shl 14) or this.short1.value))
  this.valueInst = valueInstExpr
  this.valueInstFlag = true
  return this.valueInst

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


##[
@see "Section 3.3. ID3v2 frame overview"
]##
proc read*(_: typedesc[Id3v23_Frame], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Tag): Id3v23_Frame =
  template this: untyped = result
  this = new(Id3v23_Frame)
  let root = if root == nil: cast[Id3v23](this) else: cast[Id3v23](root)
  this.io = io
  this.root = root
  this.parent = parent

  let idExpr = encode(this.io.readBytes(int(4)), "ASCII")
  this.id = idExpr
  let sizeExpr = this.io.readU4be()
  this.size = sizeExpr
  let flagsExpr = Id3v23_Frame_Flags.read(this.io, this.root, this)
  this.flags = flagsExpr
  let dataExpr = this.io.readBytes(int(this.size))
  this.data = dataExpr

proc isInvalid(this: Id3v23_Frame): bool = 
  if this.isInvalidInstFlag:
    return this.isInvalidInst
  let isInvalidInstExpr = bool(this.id == "\000\000\000\000")
  this.isInvalidInst = isInvalidInstExpr
  this.isInvalidInstFlag = true
  return this.isInvalidInst

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

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

  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 reserved1Expr = this.io.readBitsIntBe(5)
  this.reserved1 = reserved1Expr
  let flagCompressedExpr = this.io.readBitsIntBe(1) != 0
  this.flagCompressed = flagCompressedExpr
  let flagEncryptedExpr = this.io.readBitsIntBe(1) != 0
  this.flagEncrypted = flagEncryptedExpr
  let flagGroupingExpr = this.io.readBitsIntBe(1) != 0
  this.flagGrouping = flagGroupingExpr
  let reserved2Expr = this.io.readBitsIntBe(5)
  this.reserved2 = reserved2Expr

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


##[
ID3v2 extended header
@see "Section 3.2. ID3v2 extended header"
]##
proc read*(_: typedesc[Id3v23_HeaderEx], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Tag): Id3v23_HeaderEx =
  template this: untyped = result
  this = new(Id3v23_HeaderEx)
  let root = if root == nil: cast[Id3v23](this) else: cast[Id3v23](root)
  this.io = io
  this.root = root
  this.parent = parent

  let sizeExpr = this.io.readU4be()
  this.size = sizeExpr
  let flagsExExpr = Id3v23_HeaderEx_FlagsEx.read(this.io, this.root, this)
  this.flagsEx = flagsExExpr
  let paddingSizeExpr = this.io.readU4be()
  this.paddingSize = paddingSizeExpr
  if this.flagsEx.flagCrc:
    let crcExpr = this.io.readU4be()
    this.crc = crcExpr

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

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

  let flagCrcExpr = this.io.readBitsIntBe(1) != 0
  this.flagCrc = flagCrcExpr
  let reservedExpr = this.io.readBitsIntBe(15)
  this.reserved = reservedExpr

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


##[
ID3v2 fixed header
@see "Section 3.1. ID3v2 header"
]##
proc read*(_: typedesc[Id3v23_Header], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Tag): Id3v23_Header =
  template this: untyped = result
  this = new(Id3v23_Header)
  let root = if root == nil: cast[Id3v23](this) else: cast[Id3v23](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 = Id3v23_Header_Flags.read(this.io, this.root, this)
  this.flags = flagsExpr
  let sizeExpr = Id3v23_U4beSynchsafe.read(this.io, this.root, this)
  this.size = sizeExpr

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

proc read*(_: typedesc[Id3v23_Header_Flags], io: KaitaiStream, root: KaitaiStruct, parent: Id3v23_Header): Id3v23_Header_Flags =
  template this: untyped = result
  this = new(Id3v23_Header_Flags)
  let root = if root == nil: cast[Id3v23](this) else: cast[Id3v23](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 reservedExpr = this.io.readBitsIntBe(5)
  this.reserved = reservedExpr

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