Standard library · Encoding and data

audio/tag

Reading and writing the tags on a song: title, artist, album and cover art.

import audio.tag

A sound file carries more than sound. This package reads and writes the part that says what the music is: the title, who played it, which album it came from, and the picture on the cover.

Three tag formats cover almost everything in use. ID3v1 is the 128 bytes at the end of an MP3. ID3v2 sits at the front and is what modern files really use, in any of four text encodings. Vorbis comments are what FLAC and Ogg keep their tags in. ReadTags works out which one a file has and reads it, so most callers never name a format at all.

Things to know

  • Every string in a Tags is owned by it. Release the whole set with FreeTags; an unset field is empty and costs nothing.
  • Text comes back as UTF-8 whatever the file used, so Persian and Arabic titles survive an ID3v2 round trip.
  • Writing produces ID3v2.4 with UTF-8, which every current player reads.
  • Cover art comes back as a byte buffer still in its own format, so hand it straight to image to decode.
  • AudioStart and AudioEnd say where the sound really is, which is what a decoder needs in order to skip the tags.

Types

Tags

struct Tags
What a file says about the music in it: who made it, what it is called, and a picture.
NameTypeDescription
titlestr = ""The track title.
artiststr = ""Who performed it.
albumstr = ""The album it belongs to.
albumartiststr = ""Who the album is credited to, which a compilation needs.
composerstr = ""Who wrote it.
genrestr = ""What kind of music it is.
commentstr = ""A free note.
lyricsstr = ""The words.
art_mimestr = ""The media type of the cover art, such as image/jpeg.
yearint = 0The year, or 0 when there is none.
trackint = 0Which track this is.
tracksint = 0How many tracks the album has.
discint = 0Which disc this is.
bpmint = 0Beats per minute, as the tag claims.
artcompress.ByteBufThe cover art itself, still in whatever format it was stored in. Hand it to image.Decode.
tag-Id3.salam
import audio.tag
import compress
import str

func main:
    mut t := tag.NewTags()
    println tag.HasTags(t)
    t.title = str.Clone("Shiraz Nights")
    t.artist = str.Clone("Darya")
    t.album = str.Clone("Zagros")
    t.genre = str.Clone("Folk")
    t.year = 2026
    t.track = 3
    t.tracks = 12
    println tag.HasTags(t)

    v1 := tag.WriteID3v1(t)
    println compress.BufLen(v1)
    mut file1 := compress.NewByteBuf()
    mut pad := 0
    until pad < 32:
        compress.BufPush(file1, 0)
        pad += 1
    end
    compress.BufPushBuf(file1, v1)
    println tag.HasID3v1(file1.data, file1.len), tag.AudioEnd(file1.data, file1.len)
    mut g1 := tag.NewTags()
    println tag.ReadID3v1(file1.data, file1.len, g1)
    println g1.title, g1.artist, g1.album, g1.year, g1.track

    v2 := tag.WriteID3v2(t)
    println tag.HasID3v2(v2.data, v2.len), tag.ID3v2Version(v2.data, v2.len)
    println tag.ID3v2Size(v2.data, v2.len) == compress.BufLen(v2)
    mut g2 := tag.NewTags()
    println tag.ReadID3v2(v2.data, v2.len, g2)
    println g2.title, g2.artist, g2.album, g2.genre
    println g2.year, g2.track, g2.tracks

    mut fa := tag.NewTags()
    fa.title = str.Clone("شب‌های شیراز")
    fav2 := tag.WriteID3v2(fa)
    mut gfa := tag.NewTags()
    tag.ReadID3v2(fav2.data, fav2.len, gfa)
    println gfa.title

    mut full := compress.NewByteBuf()
    compress.BufPushBuf(full, v2)
    mut body := 0
    until body < 40:
        compress.BufPush(full, 65)
        body += 1
    end
    compress.BufPushBuf(full, v1)
    println tag.AudioStart(full.data, full.len) == compress.BufLen(v2)
    bare := tag.StripTags(full.data, full.len)
    println compress.BufLen(bare), compress.BufGet(bare, 0)

    println tag.GenreName(17), tag.GenreName(8), tag.GenreName(200) == ""
    mut aok := false
    mut auto := tag.ReadTags(v2.data, v2.len, aok)
    println aok, auto.title

    tag.FreeTags(t)
    tag.FreeTags(g1)
    tag.FreeTags(g2)
    tag.FreeTags(fa)
    tag.FreeTags(gfa)
    tag.FreeTags(auto)
    compress.BufFree(v1)
    compress.BufFree(v2)
    compress.BufFree(fav2)
    compress.BufFree(file1)
    compress.BufFree(full)
    compress.BufFree(bare)
end
Outputfalse true 128 true 32 true Shiraz Nights Darya Zagros 2026 3 true 4 true true Shiraz Nights Darya Zagros Folk 2026 3 12 شب‌های شیراز true 40 65 Rock Jazz true true Shiraz Nights

Block

struct Block
One FLAC metadata block, as the tag reader walks them.
NameTypeDescription
kindint = 0Which Block number it is.
offsetint = 0Where the body starts.
sizeint = 0How many bytes it holds.
lastbool = falseTrue on the final block.
tag-Flac.salam
import audio.tag
import compress
import str

func main:
    mut t := tag.NewTags()
    t.title = str.Clone("Zagros")
    t.artist = str.Clone("Darya")
    t.year = 2026
    t.track = 2

    vc := tag.WriteVorbisComment(t)
    println compress.BufLen(vc) > 8
    mut g := tag.NewTags()
    println tag.ReadVorbisComment(vc.data, 0, vc.len, g)
    println g.title, g.artist, g.year, g.track

    mut fl := compress.NewByteBuf()
    compress.BufPush(fl, 102)
    compress.BufPush(fl, 76)
    compress.BufPush(fl, 97)
    compress.BufPush(fl, 67)
    compress.BufPush(fl, 128 | tag.BlockVorbisComment)
    n := compress.BufLen(vc)
    compress.BufPush(fl, (n >> 16) & 255)
    compress.BufPush(fl, (n >> 8) & 255)
    compress.BufPush(fl, n & 255)
    compress.BufPushBuf(fl, vc)

    println tag.IsFlacTagged(fl.data, fl.len)
    blocks := tag.FlacBlocks(fl.data, fl.len)
    println blocks.len(), blocks.get(0).kind == tag.BlockVorbisComment, blocks.get(0).last
    println blocks.get(0).size == n

    mut gf := tag.NewTags()
    println tag.ReadFlacTags(fl.data, fl.len, gf)
    println gf.title, gf.artist

    mut aok := false
    mut auto := tag.ReadTags(fl.data, fl.len, aok)
    println aok, auto.title

    println tag.EncUtf8, tag.EncLatin1, tag.EncUtf16, tag.EncUtf16BE

    blocks.free()
    tag.FreeTags(t)
    tag.FreeTags(g)
    tag.FreeTags(gf)
    tag.FreeTags(auto)
    compress.BufFree(vc)
    compress.BufFree(fl)
end
Outputtrue true Zagros Darya 2026 2 true 1 true true true true Zagros Darya true Zagros 3 0 1 2

Constants and variables

Text encodings Enc…

Which encoding an ID3v2 text frame declares. Reading handles all four; writing always uses UTF-8.
NameValue
EncLatin10
EncUtf161
EncUtf16BE2
EncUtf83

The example for this item is shown with the ones above: tag-Id3.salam

FLAC block numbers Block…

The FLAC metadata blocks this reader walks past looking for tags.
NameValue
BlockStreamInfo0
BlockPadding1
BlockSeekTable3
BlockVorbisComment4
BlockPicture6

The example for this item is shown with the ones above: tag-Flac.salam

Functions

GenreName

func GenreName(id: int): str
The name of an ID3v1 genre number, or an empty string for one this package does not name.

The example for this item is shown with the ones above: tag-Id3.salam

NewTags

func NewTags(): Tags
An empty set of tags.

The example for this item is shown with the ones above: tag-Id3.salam

FreeTags

func FreeTags(t &: Tags)
Releases every string and the artwork a set of tags holds.

The example for this item is shown with the ones above: tag-Id3.salam

HasTags

func HasTags(t: Tags): bool
True when anything worth showing was found.

The example for this item is shown with the ones above: tag-Id3.salam

HasID3v1

func HasID3v1(p: void*, n: int): bool
True when the last 128 bytes are an ID3v1 tag.

The example for this item is shown with the ones above: tag-Id3.salam

ReadID3v1

func ReadID3v1(p: void*, n: int, out &: Tags): bool
Reads an ID3v1 tag from the end of the file, including the v1.1 track number.

The example for this item is shown with the ones above: tag-Id3.salam

WriteID3v1

func WriteID3v1(t: Tags): compress.ByteBuf
Builds the 128 bytes of an ID3v1 tag.

The example for this item is shown with the ones above: tag-Id3.salam

ReadTags

func ReadTags(p: void*, n: int, ok &: bool): Tags
Reads whatever tags a file has, picking the reader from the file itself: ID3v2, then a FLAC comment, then ID3v1.

The example for this item is shown with the ones above: tag-Id3.salam

ReadTagsFile

func ReadTagsFile(path: str, ok &: bool): Tags
The same from a path.

The example for this item is shown with the ones above: tag-Id3.salam

StripTags

func StripTags(p: void*, n: int): compress.ByteBuf
The file with its tags taken off, leaving only the audio.

The example for this item is shown with the ones above: tag-Id3.salam

AudioStart

func AudioStart(p: void*, n: int): int
Where the audio starts, past any ID3v2 tag.

The example for this item is shown with the ones above: tag-Id3.salam

AudioEnd

func AudioEnd(p: void*, n: int): int
Where the audio stops, before any ID3v1 tag.

The example for this item is shown with the ones above: tag-Id3.salam

HasID3v2

func HasID3v2(p: void*, n: int): bool
True when the file starts with an ID3v2 tag.

The example for this item is shown with the ones above: tag-Id3.salam

ID3v2Version

func ID3v2Version(p: void*, n: int): int
Which major version the ID3v2 tag is, 2, 3 or 4.

The example for this item is shown with the ones above: tag-Id3.salam

ID3v2Size

func ID3v2Size(p: void*, n: int): int
How many bytes the whole ID3v2 tag takes, which is where the audio starts.

The example for this item is shown with the ones above: tag-Id3.salam

ReadID3v2

func ReadID3v2(p: void*, n: int, out &: Tags): bool
Reads an ID3v2 tag, versions 2.2 through 2.4, in any of the four text encodings, including the cover art.

The example for this item is shown with the ones above: tag-Id3.salam

WriteID3v2

func WriteID3v2(t: Tags): compress.ByteBuf
Builds an ID3v2.4 tag with UTF-8 text.

The example for this item is shown with the ones above: tag-Id3.salam

IsFlacTagged

func IsFlacTagged(p: void*, n: int): bool
True when these bytes are a FLAC stream, whose tags this package can read.

The example for this item is shown with the ones above: tag-Flac.salam

FlacBlocks

func FlacBlocks(p: void*, n: int): Vector<Block>
Every FLAC metadata block. Free the vector when you are done.

The example for this item is shown with the ones above: tag-Flac.salam

ReadVorbisComment

func ReadVorbisComment(p: void*, off: int, len: int, out &: Tags): bool
Reads a Vorbis comment block, which is what FLAC and Ogg both use for tags.

The example for this item is shown with the ones above: tag-Flac.salam

ReadFlacTags

func ReadFlacTags(p: void*, n: int, out &: Tags): bool
Reads the Vorbis comment and the cover art out of a FLAC stream.

The example for this item is shown with the ones above: tag-Flac.salam

WriteVorbisComment

func WriteVorbisComment(t: Tags): compress.ByteBuf
Builds a Vorbis comment block.

The example for this item is shown with the ones above: tag-Flac.salam