Lua bindings to TagLib.
Compatible with TagLib 1.0 through 2.0.2.
Only requires C++ 98 to compile for TagLib 1.0 through 1.13, C++11 for TagLib 2.0 (note: TagLib itself may require a newer version of C++ compared to this binding).
If you have Lua and TagLib, cmake should find everything you need.
You can specify a particular Lua version by setting
LUA_VERSION. Alternatively, you can specify the
directory for Lua headers by setting LUA_INCLUDE_DIR.
If you do this - then on Windows systems you'll
also need to set LUA_LIBRARY.
Similarly you can specify the include/library folders for TagLib
by setting TAGLIB_INCLUDE_DIR and TAGLIB_LIBRARY.
If you're linking against a static version of TagLib, you
probably need to link against zlib - you can define
TAGLIB_USE_ZLIB, it will try to find and link with
zlib. And like with Lua and TagLib - you can define
ZLIB_INCLUDE_DIR and ZLIB_LIBRARY to specify where to
find Zlib headers and the library.
For example, if you have a static build of TagLib at
/opt/taglib-static you could probably build with:
cmake -B build -DTAGLIB_INCLUDE_DIR=/opt/taglib-static/include/taglib -DTAGLIB_LIBRARY=/opt/taglib-static/lib/libtag.a -DTAGLIB_USE_ZLIB=ON
make -C build
This module is available as a LuaRock. Building the rock requires
cmake, and it accepts the TAGLIB_INCLUDE_DIR, TAGLIB_LIBRARY,
TAGLIB_USE_ZLIB, ZLIB_INCLUDE_DIR, and ZLIB_LIBRARY arguments that cmake uses.
For example - if you want to install with LuaRocks and have a static
build of TagLib at /opt/taglib-static:
luarocks install TagLib TAGLIB_INCLUDE_DIR=/opt/taglib-static/include/taglib TAGLIB_LIBRARY=/opt/taglib-static/lib/libtag.a TAGLIB_USE_ZLIB=ON
I tried to mimic using TagLib in C++ - so namespaces and classes are
generally represented as modules. You can require individual
namespaces and modules, or you can just require the main TagLib
module and access everything from there.
For example, in TagLib with C++, if you wanted to open a FileRef and get the title tag, you could do something like:
TagLib::FileRef f = TagLib::FileRef("/path/to/file.mp3");
TagLib::String artist = f.tag()->artist();In Lua, this would be:
local TagLib = require'TagLib'
local f = TagLib.FileRef('/path/to/file.mp3')
local artist = f:tag():artist()
-- artist is a UTF-8 encoded Lua stringor:
local FileRef = require'TagLib.FileRef'
local f = FileRef('/path/to/file.mp3')
local artist = f:tag():artist()
-- artist is a UTF-8 encoded Lua stringMost objects are represented as Lua userdata, with metatables set to access C++ methods.
You can manually cast objects into different types,
every class has a cast function you can use for this.
If the casting fails because you're trying to make an
impossible cast (like a TagLib::File into a TagLib::Tag) - that
will throw a Lua error since that's a programming
error. If the cast fails for some other reason, it will
return nil.
Example, if you want to do things with an ID3v2 AttachedPicture Frame:
local TagLib = require'TagLib'
local mp3 = TagLib.MPEG.File('/path/to/mp3')
local frames = mp3:frameList('APIC')
local pictureFrame = TagLib.ID3v2.AttachedPictureFrame.cast(frames[1])
local pictureType = pictureFrame:type()Objects will hold a reference to their "parent" object to prevent early garbage-collection. Meaning it's safe to do things like:
local TagLib = require'TagLib'
local function getPictures(path)
local mp3 = TagLib.MPEG.File(path)
local pictureFrames = {}
for i,f in ipairs(mp3:ID3v2Tag():frameList('APIC')) do
pictureFrames[i] = TagLib.ID3v2.AttachedPictureFrame.cast(f)
end
return pictureFrames
end
local pictures = getPictures('/path/to/mp3')
-- variables declared in getPictures are now out-of-scope but
-- won't be garbage-collected until after the "pictures" variable
-- is garbage-collected.
This library tries to follow TagLib pretty closely so you can follow along with the TagLib docs but there's a few special cases to be aware of:
TagLib::String objects are always
converted to UTF-8, and then returned to Lua as a Lua string.
Similarly - any Lua string passed is assumed to be UTF-8,and will
be converted to a TagLib::String.
If you have a string in some other encoding - you'll need to convert it to UTF-8 before calling any TagLib method that accepts a string.
TagLib::ByteVector objects
are converted to a Lua string (as-is, no encoding conversions).
And similarly - any Lua string can be used as a TagLib::ByteVector.
If there is a function that can accept either a TagLib::String or a TagLib::ByteVector,
TagLib::String wins.
You can explicitly convert a Lua string into a TagLib::ByteVector, should you need to:
local TagLib = require'TagLib'
local bv = TagLib.ByteVector('Some String')There's only two places I know of where you distinguish between TagLib::String and TagLib::ByteVector:
- Creating a
TagLib::ASF::Attribute - Creating a
TagLib::MP4::Item
TagLib lists and maps can be represented as Lua tables, or as userdata.
For functions that just return a list or map, like any function that
returns a TagLib::StringList or TagLib::PropertyMap - those will
be returned as regular Lua tables. Lists will have integer keys
and can be iterated with ipairs, maps will have string keys.
For functions that return a list or map reference, like
TagLib::ID3v2::frameListMap - that will be a userdata, with
appropriate metamethods to iterate and access the table (__index,
__pairs,__len (for lists), __ipairs (for lists)).
The reasoning behind this is, the reference can be updated
(you could add a new TagLib::ID3v2::Frame to a tag, for example),
so using the userdata allows us to hold on to the reference
and always have the latest values.
One thing to note is anytime you access one of these map/list references, you're creating a new userdata object. So if you were to do something like:
local title1 = frameListMap['TIT2']
local title2 = frameListMap['TIT2']
Then title1 and title2 will be two different objects.
If you're using Lua 5.1 or LuaJIT - these don't honor the __pairs
and __ipairs metamethods. I have compatible pairs and ipairs
functions you can use:
local pairs = require'TagLib.pairs'
local ipairs = require'TagLib.ipairs'You could remove local if you want to override the global pairs/ipairs but,
I wouldn't really recommend that.
It's safe to use these compatibility functions on Lua 5.2+ - they'll
just return the default pairs and ipairs.
It's also pretty easy to make your own 5.2-compatible functions in Lua. I think in theory that can have better performance in LuaJIT but it's probably negligible, since you'll be hitting the C API pretty often anyway.
local raw_pairs = pairs
local pairs = function(t)
local metatable = debug.getmetatable(t)
if metatable and metatable.__pairs then
return metatable.__pairs(t)
end
return raw_pairs(t)
endVarious setter functions (that return void in C++) will instead return the
userdata. This allows you to chain calls, like:
local mp3 = TagLib.MPEG.File('/path/to/mp3')
mp3:tag():setTitle('Some Title'):setArtist('SomeArtist')
mp3:save()You could do some pretty complex things like, if you wanted to create a list of ID3v2 chapter frames and a table of contents frame:
local mp3 = TagLib.MPEG.File('/path/to/mp3')
local tag = mp3:ID3v2Tag()
for _,frame in ipairs({
TagLib.ID3v2.ChapterFrame(
'chap01',
0, 1000,
0xffffffff, 0xffffffff,
{
TagLib.ID3v2.TextIdentificationFrame('TIT2',TagLib.String.Type.UTF8):setText('Chapter 1')
}
),
TagLib.ID3v2.ChapterFrame(
'chap01',
2000, 3000,
0xffffffff, 0xffffffff,
{
TagLib.ID3v2.TextIdentificationFrame('TIT2',TagLib.String.Type.UTF8):setText('Chapter 2')
}
),
TagLib.ID3v2.TableOfContentsFrame(
'toc',
{ 'chap01', 'chap02' }
)
}) do
tag:addFrame(frame)
end
mp3:save()In TagLib editions prior to 2.0, the order of parameters to create FLAC::File or MPEG::File object with an IOStream is different. This library only presents the 2.0+ ordering.
The library currently relies on using LUA_REGISTRYINDEX to store metatables,
I believe this may limit the usefulness of the library with Lua Lanes and
other multithreaded solutions. I may consider changing this, but it would
be a lot of effort.
It's possible to create Lua-backed IOStream objects and load files, but if you're looking to integrate this into say, OpenResty or cqueues (or anything else that uses coroutines to have non-blocking I/O) - that likely won't work.
I'm unsure how to tackle this - newer versions of
Lua do have a mechanism for creating continuation functions, but
at the point that I'm using lua_call to call Lua functions in
an IOStream object - I'm pretty deep in the call stack. Said
call stack is lost when a Lua coroutine yields, because
lua_callk doesn't return when coroutine.yield is called.
I'm not sure if this will ever be supported.
MIT. See file LICENSE.