Lua add metadata and settings reading helper module
Adds example scripts for reading track metadata + dumping albumart and rockbox settings settings are now stored as a table of strings rather than a table of tables as it saves ~15 kb of ram without adding much complexity Change-Id: I611c312b2a60ab96e595e4710b17aedbd6c0689b
This commit is contained in:
parent
a3cbd86a51
commit
267d04d2bd
6 changed files with 342 additions and 10 deletions
164
apps/plugins/lua/include_lua/rbsettings.lua
Normal file
164
apps/plugins/lua/include_lua/rbsettings.lua
Normal file
|
@ -0,0 +1,164 @@
|
|||
--[[ Lua rb settings reader
|
||||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2019 William Wilgus
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
]]
|
||||
|
||||
rb.settings = rb.settings or {}
|
||||
|
||||
local var = {offset = 1, size = 2, type = 3, fields = 3}
|
||||
|
||||
local function bytesLE_n(str)
|
||||
str = str or ""
|
||||
local tbyte={str:byte(1, -1)}
|
||||
local bpos, num = 1, 0
|
||||
for k = 1,#tbyte do -- (k = #t, 1, -1 for BE)
|
||||
num = num + tbyte[k] * bpos
|
||||
bpos = bpos * 256 --1<<8
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
local function get_var_fields(s_var)
|
||||
-- converts member string into table
|
||||
-- var = {offset, size, "type"}
|
||||
s_var = s_var or ""
|
||||
local o, s, t = string.match(s_var, "(0x%x+),%s*(%d+),%s*(.+)")
|
||||
local tvar = {o, s, t}
|
||||
|
||||
return #tvar == var.fields and tvar or nil
|
||||
end
|
||||
|
||||
local function format_val(val, var_type)
|
||||
local ret, num
|
||||
if var_type == nil then
|
||||
return nil
|
||||
elseif var_type == "str" then
|
||||
-- stop at first null byte, return nil if str doesn't exist
|
||||
return val and string.match(val, "^%Z+") or nil
|
||||
end
|
||||
|
||||
num = bytesLE_n(val)
|
||||
if string.find(var_type, "^b") then
|
||||
if(num <= 0) then
|
||||
ret = false
|
||||
else
|
||||
ret = true
|
||||
end
|
||||
elseif string.find(var_type, "^u_[cil]") then
|
||||
-- Lua integers are signed so we need to do a bit of extra processing
|
||||
ret = (string.format("%u", num))
|
||||
else
|
||||
ret = num
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
local function dump_struct(t_settings, t_struct, n_elems, t_var)
|
||||
--Internal function dumps structs
|
||||
local tdata = {}
|
||||
|
||||
local function struct_get_elem(v, elem_offset)
|
||||
local val, offset, tvar1
|
||||
tvar1 = get_var_fields(v)
|
||||
offset = t_var[var.offset] + tvar1[var.offset] + elem_offset
|
||||
val = t_settings(offset, tvar1[var.size])
|
||||
return format_val(val, tvar1[var.type])
|
||||
end
|
||||
|
||||
if n_elems > 0 then
|
||||
-- Array of structs, struct[elems];
|
||||
local elemsize = (t_var[var.size] / n_elems)
|
||||
for i = 0, n_elems - 1 do
|
||||
tdata[i] = tdata[i] or {}
|
||||
for k1, v1 in pairs(t_struct) do
|
||||
tdata[i][k1] = struct_get_elem(v1, (elemsize * i))
|
||||
end
|
||||
end
|
||||
else
|
||||
-- single struct, struct;
|
||||
for k1, v1 in pairs(t_struct) do
|
||||
tdata[k1] = struct_get_elem(v1, 0)
|
||||
end
|
||||
end
|
||||
return tdata
|
||||
end
|
||||
|
||||
local function get_array_elems(var_type)
|
||||
--extract the number of elements, returns 0 if not found
|
||||
local elems = string.match(var_type,".*%[(%d+)%]")
|
||||
return tonumber(elems) or 0
|
||||
end
|
||||
|
||||
local function get_struct_name(var_type)
|
||||
--extract the name of a struct, returns nil if not found
|
||||
return string.match(var_type,"^s_([^%[%]%s]+)")
|
||||
end
|
||||
|
||||
function rb.settings.read(s_settings, s_var, s_groupname)
|
||||
local data, val
|
||||
local tvar = get_var_fields(s_var)
|
||||
if tvar == nil then return nil end
|
||||
|
||||
local elems = get_array_elems(tvar[var.type])
|
||||
local structname = get_struct_name(tvar[var.type])
|
||||
|
||||
local tsettings = rb[s_settings]
|
||||
if not tsettings then error(s_settings .. " does not exist") end
|
||||
|
||||
if structname and rb[s_groupname] then
|
||||
return dump_struct(tsettings, rb[s_groupname][structname], elems, tvar)
|
||||
end
|
||||
|
||||
local voffset, vsize, vtype = tvar[var.offset], tvar[var.size], tvar[var.type]
|
||||
if elems > 0 then
|
||||
-- Arrays of values, val[elems];
|
||||
data = {}
|
||||
local elemsize = (vsize / elems)
|
||||
|
||||
for i = 0, elems - 1 do
|
||||
val = tsettings(voffset + (elemsize * i), elemsize)
|
||||
data[i] = format_val(val, vtype)
|
||||
end
|
||||
else
|
||||
-- Single value, val;
|
||||
if vtype == "ptr_char" then -- (**char)
|
||||
vtype = "str"
|
||||
val = tsettings(voffset, vsize, nil, true)
|
||||
else
|
||||
val = tsettings(voffset, vsize)
|
||||
end
|
||||
data = format_val(val, vtype)
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
function rb.settings.dump(s_settings, s_groupname, s_structname, t_output)
|
||||
t_output = t_output or {}
|
||||
local tgroup = rb[s_groupname]
|
||||
s_structname = s_structname or s_settings
|
||||
for k, v in pairs(tgroup[s_structname]) do
|
||||
t_output[k] = rb.settings.read(s_settings, v, s_groupname)
|
||||
end
|
||||
return t_output
|
||||
end
|
||||
|
||||
return true
|
|
@ -19,7 +19,7 @@ LUA_INCLUDEDIR := $(LUA_SRCDIR)/include_lua
|
|||
LUA_INCLUDELIST := $(addprefix $(LUA_BUILDDIR)/,audio.lua blit.lua color.lua draw.lua draw_floodfill.lua draw_poly.lua \
|
||||
draw_num.lua draw_text.lua image.lua image_save.lua lcd.lua math_ex.lua \
|
||||
print.lua timer.lua playlist.lua pcm.lua sound.lua \
|
||||
rbcompat.lua poly_points.lua printtable.lua)
|
||||
rbcompat.lua rbsettings.lua poly_points.lua printtable.lua)
|
||||
|
||||
|
||||
ifndef APP_TYPE
|
||||
|
|
|
@ -640,8 +640,12 @@ RB_WRAP(strncasecmp)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int mem_read_write(lua_State *L, uintptr_t address, size_t maxsize)
|
||||
static int mem_read_write(lua_State *L, uintptr_t address, size_t maxsize, bool isstr_p)
|
||||
{
|
||||
if(isstr_p) /*pointer to string (**char)*/
|
||||
{
|
||||
lua_settop(L, 2); /* no writes allowed */
|
||||
}
|
||||
intptr_t offset = (intptr_t) luaL_optnumber(L, 1, 0);
|
||||
size_t size = (size_t) luaL_optnumber(L, 2, maxsize);
|
||||
size_t written;
|
||||
|
@ -716,6 +720,11 @@ static int mem_read_write(lua_State *L, uintptr_t address, size_t maxsize)
|
|||
case LUA_TNIL:
|
||||
case LUA_TNONE: /* reader */
|
||||
{
|
||||
if(isstr_p && mem)
|
||||
{
|
||||
lua_pushstring (L, *(char**) mem);
|
||||
return 1;
|
||||
}
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
while(size > 0)
|
||||
|
@ -746,32 +755,51 @@ RB_WRAP(global_status)
|
|||
{
|
||||
const uintptr_t address = (uintptr_t) rb->global_status;
|
||||
const size_t maxsize = sizeof(struct system_status);
|
||||
return mem_read_write(L, address, maxsize);
|
||||
/*const bool isstr_p = lua_toboolean(L, 4);*/
|
||||
return mem_read_write(L, address, maxsize, false);
|
||||
}
|
||||
|
||||
RB_WRAP(global_settings)
|
||||
{
|
||||
const uintptr_t address = (uintptr_t) rb->global_settings;
|
||||
const size_t maxsize = sizeof(struct user_settings);
|
||||
return mem_read_write(L, address, maxsize);
|
||||
/*const bool isstr_p = lua_toboolean(L, 4);*/
|
||||
return mem_read_write(L, address, maxsize, false);
|
||||
}
|
||||
|
||||
RB_WRAP(audio_next_track)
|
||||
{
|
||||
lua_settop(L, 2); /* no writes allowed */
|
||||
|
||||
const uintptr_t address = (uintptr_t) rb->audio_next_track();
|
||||
const size_t maxsize = sizeof(struct mp3entry);
|
||||
return mem_read_write(L, address, maxsize);
|
||||
const bool isstr_p = lua_toboolean(L, 4);
|
||||
lua_settop(L, 2); /* no writes allowed */
|
||||
return mem_read_write(L, address, maxsize, isstr_p);
|
||||
}
|
||||
|
||||
RB_WRAP(audio_current_track)
|
||||
{
|
||||
lua_settop(L, 2); /* no writes allowed */
|
||||
|
||||
const uintptr_t address = (uintptr_t) rb->audio_current_track();
|
||||
const size_t maxsize = sizeof(struct mp3entry);
|
||||
return mem_read_write(L, address, maxsize);
|
||||
const bool isstr_p = lua_toboolean(L, 4);
|
||||
lua_settop(L, 2); /* no writes allowed */
|
||||
return mem_read_write(L, address, maxsize, isstr_p);
|
||||
}
|
||||
|
||||
#if 0
|
||||
RB_WRAP(read_mem)
|
||||
{
|
||||
lua_settop(L, 2); /* no writes allowed */
|
||||
const uintptr_t address = lua_tonumber(L, 1);
|
||||
const size_t maxsize = luaL_optnumber(L, 2, strlen((char *)address));
|
||||
luaL_argcheck(L, address > 0, 1, ERR_IDX_RANGE);
|
||||
lua_pushnil(L);
|
||||
lua_replace(L, -3);/* stk pos 1 is no longer offset it is starting address */
|
||||
return mem_read_write(L, address, maxsize, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
RB_WRAP(restart_lua)
|
||||
{
|
||||
/*close lua state, open a new lua state, load script @ filename */
|
||||
|
|
|
@ -293,7 +293,7 @@ sub Print_Variable {
|
|||
$type = sprintf('%s[%d]', $1, $arr);
|
||||
}
|
||||
|
||||
printf "\t%s = {0x%x, %d, \"%s\"},\n", $member, $offset, $size, $type;
|
||||
printf "\t%s = \"0x%x, %d, %s\",\n", $member, $offset, $size, $type;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
@ -303,7 +303,7 @@ if($header) #output sections to lua file [PASS 2]
|
|||
{
|
||||
print "-- Don't change this file!\n";
|
||||
printf "-- It is automatically generated %s\n", $svnrev;
|
||||
print "-- member = {offset, size, \"type\"}\n\n";
|
||||
print "-- member = \"offset, size, type\"\n\n";
|
||||
|
||||
print "--";
|
||||
foreach my $key (sort(keys %replace_type_prefix)) {
|
||||
|
@ -337,6 +337,7 @@ if($header) #output sections to lua file [PASS 2]
|
|||
}
|
||||
}
|
||||
}
|
||||
print "\nreturn false\n";
|
||||
#my ($user,$system,$cuser,$csystem) = times;
|
||||
#warn "Pass2 ".$user." ".$system." ".$cuser." ".$csystem."\n";
|
||||
exit;
|
||||
|
|
49
apps/plugins/lua_scripts/dump_rbsettings.lua
Normal file
49
apps/plugins/lua_scripts/dump_rbsettings.lua
Normal file
|
@ -0,0 +1,49 @@
|
|||
require("rbsettings")
|
||||
require("settings")
|
||||
rb.metadata = nil -- remove track metadata settings
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local function print_setting_table(t_tbl, s_sep)
|
||||
s_sep = s_sep or ""
|
||||
local str = ""
|
||||
local function pfunct(t, sep, s, n) -- recursive print function
|
||||
local vtype
|
||||
for k, v in pairs(t) do
|
||||
vtype = type(v)
|
||||
if vtype == "table" then
|
||||
local f = string.format("%s[%s]", n, k)
|
||||
s = pfunct(v, sep, s, f)
|
||||
elseif vtype == "boolean" then
|
||||
v = v and "true" or "false"
|
||||
s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep)
|
||||
elseif v then
|
||||
s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep)
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
return pfunct(t_tbl, s_sep, str, "")
|
||||
end
|
||||
|
||||
local filename = "/settings.txt"
|
||||
local file = io.open(filename, "w+") -- overwrite
|
||||
local t_settings
|
||||
|
||||
if not file then
|
||||
rb.splash(rb.HZ, "Error writing " .. filename)
|
||||
return
|
||||
end
|
||||
|
||||
t_settings = rb.settings.dump('global_settings', "system")
|
||||
file:write("global_settings:\n")
|
||||
file:write(print_setting_table(t_settings, "\n"))
|
||||
file:write("\n\n")
|
||||
|
||||
t_settings = rb.settings.dump('global_status', "system")
|
||||
file:write("global_status:\n")
|
||||
file:write(print_setting_table(t_settings, "\n"))
|
||||
file:write("\n\n")
|
||||
|
||||
file:close()
|
||||
|
||||
rb.splash(100, "rb settings dumped: " .. filename)
|
90
apps/plugins/lua_scripts/track_metadata.lua
Normal file
90
apps/plugins/lua_scripts/track_metadata.lua
Normal file
|
@ -0,0 +1,90 @@
|
|||
require("rbsettings")
|
||||
require("settings") --settings.lua
|
||||
rb.system = nil -- remove system settings
|
||||
-------------------------------------------------------------------------------
|
||||
local track_data = rb.metadata.mp3_entry
|
||||
local cur_trk = "audio_current_track"
|
||||
-------------------------------------------------------------------------------
|
||||
local trackname = rb.settings.read(cur_trk, track_data.title) or
|
||||
rb.settings.read(cur_trk, track_data.path)
|
||||
if not trackname or trackname == "" then
|
||||
os.exit(1, "No track loaded")
|
||||
else
|
||||
rb.splash(100, trackname)
|
||||
end
|
||||
-------------------------------------------------------------------------------
|
||||
local function dump_albumart(fileout)
|
||||
local t_albumart = rb.settings.read(cur_trk, track_data.albumart, "metadata")
|
||||
local t_aaext = {".bmp",".png", ".jpg"}
|
||||
local path = rb.settings.read(cur_trk, track_data.path)
|
||||
if t_albumart.pos > 0 and t_albumart.size > 0 and t_albumart.type > 0 then
|
||||
|
||||
if t_aaext[t_albumart.type] then
|
||||
local filename = "/" .. fileout .. t_aaext[t_albumart.type]
|
||||
local aa = io.open(filename, "w+") -- overwrite
|
||||
if not aa then
|
||||
rb.splash(rb.HZ, "Error writing " .. filename)
|
||||
return
|
||||
end
|
||||
|
||||
local track = io.open(path, "r")
|
||||
if not track then
|
||||
rb.splash(rb.HZ, "Error opening " .. path)
|
||||
return
|
||||
end
|
||||
track:seek("set", t_albumart.pos )
|
||||
for i = 0, t_albumart.size, 32 do
|
||||
aa:write(track:read(32))
|
||||
end
|
||||
rb.splash(rb.HZ, "Saved: " .. filename)
|
||||
track:close()
|
||||
aa:close()
|
||||
else
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function print_setting_table(t_tbl, s_sep)
|
||||
s_sep = s_sep or ""
|
||||
local str = ""
|
||||
local function pfunct(t, sep, s, n) -- recursive print function
|
||||
local vtype
|
||||
for k, v in pairs(t) do
|
||||
vtype = type(v)
|
||||
if vtype == "table" then
|
||||
local f = string.format("%s[%s]", n, k)
|
||||
s = pfunct(v, sep, s, f)
|
||||
elseif vtype == "boolean" then
|
||||
v = v and "true" or "false"
|
||||
s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep)
|
||||
elseif v then
|
||||
s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep)
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
return pfunct(t_tbl, s_sep, str, "")
|
||||
end
|
||||
|
||||
local filename = "/metadata.txt"
|
||||
local file = io.open(filename, "w+") -- overwrite
|
||||
local t_settings
|
||||
|
||||
if not file then
|
||||
rb.splash(rb.HZ, "Error writing " .. filename)
|
||||
return
|
||||
end
|
||||
|
||||
---[[
|
||||
t_settings = rb.settings.dump(cur_trk, "metadata", "mp3_entry")
|
||||
file:write(trackname .. ":\n")
|
||||
file:write(print_setting_table(t_settings, "\n"))
|
||||
file:write("\n\n")
|
||||
file:close()
|
||||
|
||||
rb.splash(100, "metadata dumped: " .. filename)
|
||||
|
||||
if rb.settings.read(cur_trk, track_data.has_embedded_albumart) then
|
||||
dump_albumart("/albumart")
|
||||
end
|
Loading…
Reference in a new issue