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
Tagsis owned by it. Release the whole set withFreeTags; 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.
AudioStartandAudioEndsay where the sound really is, which is what a decoder needs in order to skip the tags.
Types
Tags
struct Tags| Name | Type | Description |
|---|---|---|
title | str = "" | The track title. |
artist | str = "" | Who performed it. |
album | str = "" | The album it belongs to. |
albumartist | str = "" | Who the album is credited to, which a compilation needs. |
composer | str = "" | Who wrote it. |
genre | str = "" | What kind of music it is. |
comment | str = "" | A free note. |
lyrics | str = "" | The words. |
art_mime | str = "" | The media type of the cover art, such as image/jpeg. |
year | int = 0 | The year, or 0 when there is none. |
track | int = 0 | Which track this is. |
tracks | int = 0 | How many tracks the album has. |
disc | int = 0 | Which disc this is. |
bpm | int = 0 | Beats per minute, as the tag claims. |
art | compress.ByteBuf | The cover art itself, still in whatever format it was stored in. Hand it to image.Decode. |
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)
endBlock
struct Block| Name | Type | Description |
|---|---|---|
kind | int = 0 | Which Block number it is. |
offset | int = 0 | Where the body starts. |
size | int = 0 | How many bytes it holds. |
last | bool = false | True on the final block. |
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)
endConstants and variables
Text encodings Enc…
| Name | Value |
|---|---|
EncLatin1 | 0 |
EncUtf16 | 1 |
EncUtf16BE | 2 |
EncUtf8 | 3 |
The example for this item is shown with the ones above: tag-Id3.salam
FLAC block numbers Block…
| Name | Value |
|---|---|
BlockStreamInfo | 0 |
BlockPadding | 1 |
BlockSeekTable | 3 |
BlockVorbisComment | 4 |
BlockPicture | 6 |
The example for this item is shown with the ones above: tag-Flac.salam
Functions
GenreName
func GenreName(id: int): strThe example for this item is shown with the ones above: tag-Id3.salam
NewTags
func NewTags(): TagsThe example for this item is shown with the ones above: tag-Id3.salam
FreeTags
func FreeTags(t &: Tags)The example for this item is shown with the ones above: tag-Id3.salam
HasTags
func HasTags(t: Tags): boolThe example for this item is shown with the ones above: tag-Id3.salam
HasID3v1
func HasID3v1(p: void*, n: int): boolThe example for this item is shown with the ones above: tag-Id3.salam
ReadID3v1
func ReadID3v1(p: void*, n: int, out &: Tags): boolThe example for this item is shown with the ones above: tag-Id3.salam
WriteID3v1
func WriteID3v1(t: Tags): compress.ByteBufThe example for this item is shown with the ones above: tag-Id3.salam
ReadTags
func ReadTags(p: void*, n: int, ok &: bool): TagsThe example for this item is shown with the ones above: tag-Id3.salam
ReadTagsFile
func ReadTagsFile(path: str, ok &: bool): TagsThe example for this item is shown with the ones above: tag-Id3.salam
StripTags
func StripTags(p: void*, n: int): compress.ByteBufThe example for this item is shown with the ones above: tag-Id3.salam
AudioStart
func AudioStart(p: void*, n: int): intThe example for this item is shown with the ones above: tag-Id3.salam
AudioEnd
func AudioEnd(p: void*, n: int): intThe example for this item is shown with the ones above: tag-Id3.salam
HasID3v2
func HasID3v2(p: void*, n: int): boolThe example for this item is shown with the ones above: tag-Id3.salam
ID3v2Version
func ID3v2Version(p: void*, n: int): intThe example for this item is shown with the ones above: tag-Id3.salam
ID3v2Size
func ID3v2Size(p: void*, n: int): intThe example for this item is shown with the ones above: tag-Id3.salam
ReadID3v2
func ReadID3v2(p: void*, n: int, out &: Tags): boolThe example for this item is shown with the ones above: tag-Id3.salam
WriteID3v2
func WriteID3v2(t: Tags): compress.ByteBufThe example for this item is shown with the ones above: tag-Id3.salam
IsFlacTagged
func IsFlacTagged(p: void*, n: int): boolThe example for this item is shown with the ones above: tag-Flac.salam
FlacBlocks
func FlacBlocks(p: void*, n: int): Vector<Block>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): boolThe example for this item is shown with the ones above: tag-Flac.salam
ReadFlacTags
func ReadFlacTags(p: void*, n: int, out &: Tags): boolThe example for this item is shown with the ones above: tag-Flac.salam
WriteVorbisComment
func WriteVorbisComment(t: Tags): compress.ByteBufThe example for this item is shown with the ones above: tag-Flac.salam