if not modules then modules = { } end modules ['mtx-flac'] = {
version = 1.001,
comment = "companion to mtxrun.lua",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
local sub, match, byte, lower = string.sub, string.match, string.byte, string.lower
local readstring, readnumber = io.readstring, io.readnumber
local concat, sortedpairs = table.concat, table.sortedpairs
local tonumber = tonumber
local tobitstring = number.tobitstring
local lpegmatch = lpeg.match
local p_escaped = lpeg.patterns.xml.escaped
-- rather silly: pack info in bits while a flac file is large anyway
flac = flac or { }
flac.report = string.format
local splitter = lpeg.splitat("=")
local readers = { }
readers[0] = function(f,size,target) -- not yet ok .. todo: use bit32 lib
local info = { }
target.info = info
info.minimum_block_size = readnumber(f,-2)
info.maximum_block_size = readnumber(f,-2)
info.minimum_frame_size = readnumber(f,-3)
info.maximum_frame_size = readnumber(f,-3)
local buffer = { }
for i=1,8 do
buffer[i] = tobitstring(readnumber(f,1))
end
local bytes = concat(buffer)
info.sample_rate_in_hz = tonumber(sub(bytes, 1,20),2) -- 20
info.number_of_channels = tonumber(sub(bytes,21,23),2) -- 3
info.bits_per_sample = tonumber(sub(bytes,24,28),2) -- 5
info.samples_in_stream = tonumber(sub(bytes,29,64),2) -- 36
info.md5_signature = readstring(f,16) -- 128
end
readers[4] = function(f,size,target,banner)
local tags = { }
target.tags = tags
target.vendor = readstring(f,readnumber(f,-4))
for i=1,readnumber(f,-4) do
local key, value = lpeg.match(splitter,readstring(f,readnumber(f,-4)))
tags[lower(key)] = value
end
end
readers.default = function(f,size,target)
f:seek("cur",size)
end
function flac.getmetadata(filename)
local f = io.open(filename, "rb")
if f then
local banner = readstring(f,4)
if banner == "fLaC" then
local data = {
banner = banner,
filename = filename,
filesize = lfs.attributes(filename,"size"),
}
while true do
local flag = readnumber(f,1)
local size = readnumber(f,3)
local last = flag > 127
if last then
flag = flag - 128
end
local reader = readers[flag] or readers.default
reader(f,size,data,banner)
if last then
f:close()
return data
end
end
else
flac.report("no flac file: %s (%s)",filename,banner)
end
f:close()
else
flac.report("no file: %s",filename)
end
end
function flac.savecollection(pattern,filename)
pattern = (pattern ~= "" and pattern) or "**/*.flac"
filename = (filename ~= "" and filename) or "music-collection.xml"
flac.report("identifying files using pattern %q" ,pattern)
local files = dir.glob(pattern)
flac.report("%s files found, analyzing files",#files)
local music = { }
for i=1,#files do
local data = flac.getmetadata(files[i])
if data then
local tags = data.tags
local info = data.info
local artist = tags.artist
local album = tags.album
local albums = music[artist]
if not albums then
albums = { }
music[artist] = albums
end
local albumx = albums[album]
if not albumx then
albumx = {
year = tags.date,
tracks = { },
}
albums[album] = albumx
end
albumx.tracks[tonumber(tags.tracknumber) or 0] = {
title = tags.title,
length = math.round((info.samples_in_stream/info.sample_rate_in_hz)),
}
end
end
-- inspect(music)
local nofartists, nofalbums, noftracks, noferrors = 0, 0, 0, 0
local f = io.open(filename,"wb")
if f then
flac.report("saving data in file %q",filename)
f:write("\n\n")
f:write("\n")
for artist, albums in sortedpairs(music) do
nofartists = nofartists + 1
f:write("\t\n")
f:write("\t\t",lpegmatch(p_escaped,artist),"\n")
f:write("\t\t\n")
for album, data in sortedpairs(albums) do
nofalbums = nofalbums + 1
f:write("\t\t\t\n")
f:write("\t\t\t\t",lpegmatch(p_escaped,album),"\n")
f:write("\t\t\t\t\n")
local tracks = data.tracks
for i=1,#tracks do
local track = tracks[i]
if track then
noftracks = noftracks + 1
f:write("\t\t\t\t\t\n")
else
noferrors = noferrors + 1
flac.report("error in album: %q of %q, no track %s",album,artist,i)
f:write("\t\t\t\t\t\n")
end
end
f:write("\t\t\t\t\n")
f:write("\t\t\t\n")
end
f:write("\t\t\n")
f:write("\t\n")
end
f:write("\n")
f:close()
flac.report("%s tracks of %s albums of %s artists saved in %q (%s errors)",noftracks,nofalbums,nofartists,filename,noferrors)
else
flac.report("unable to save data in file %q",filename)
end
end
--
local helpinfo = [[
mtx-flac
ConTeXt Flac Helpers
0.10
collect albums in xml file
Example
mtxrun --script flac --collect somename.flac
mtxrun --script flac --collect --pattern="m:/music/**")
]]
local application = logs.application {
name = "mtx-flac",
banner = "ConTeXt Flac Helpers 0.10",
helpinfo = helpinfo,
}
flac.report = application.report
-- script code
scripts = scripts or { }
scripts.flac = scripts.flac or { }
function scripts.flac.collect()
local files = environment.files
local pattern = environment.arguments.pattern
if #files > 0 then
for i=1,#files do
local filename = files[1]
if file.suffix(filename) == "flac" then
flac.savecollection(filename,file.replacesuffix(filename,"xml"))
elseif lfs.isdir(filename) then
local pattern = filename .. "/**.flac"
flac.savecollection(pattern,file.addsuffix(file.basename(filename),"xml"))
else
flac.savecollection(file.replacesuffix(filename,"flac"),file.replacesuffix(filename,"xml"))
end
end
elseif pattern then
flac.savecollection(file.addsuffix(pattern,"flac"),"music-collection.xml")
else
flac.report("no file(s) or pattern given" )
end
end
if environment.argument("collect") then
scripts.flac.collect()
elseif environment.argument("exporthelp") then
application.export(environment.argument("exporthelp"),environment.files[1])
else
application.help()
end