From 267d04d2bd2a7681c8bbcfbb655612101440b765 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 6 Sep 2019 19:24:26 -0500 Subject: [PATCH] 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 --- apps/plugins/lua/include_lua/rbsettings.lua | 164 +++++++++++++++++++ apps/plugins/lua/lua.make | 2 +- apps/plugins/lua/rocklib.c | 42 ++++- apps/plugins/lua/settings_helper.pl | 5 +- apps/plugins/lua_scripts/dump_rbsettings.lua | 49 ++++++ apps/plugins/lua_scripts/track_metadata.lua | 90 ++++++++++ 6 files changed, 342 insertions(+), 10 deletions(-) create mode 100644 apps/plugins/lua/include_lua/rbsettings.lua create mode 100644 apps/plugins/lua_scripts/dump_rbsettings.lua create mode 100644 apps/plugins/lua_scripts/track_metadata.lua diff --git a/apps/plugins/lua/include_lua/rbsettings.lua b/apps/plugins/lua/include_lua/rbsettings.lua new file mode 100644 index 0000000000..defdb11b77 --- /dev/null +++ b/apps/plugins/lua/include_lua/rbsettings.lua @@ -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 diff --git a/apps/plugins/lua/lua.make b/apps/plugins/lua/lua.make index 66c1e67855..5e46692f3f 100644 --- a/apps/plugins/lua/lua.make +++ b/apps/plugins/lua/lua.make @@ -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 diff --git a/apps/plugins/lua/rocklib.c b/apps/plugins/lua/rocklib.c index 3c38440850..033975d7fe 100644 --- a/apps/plugins/lua/rocklib.c +++ b/apps/plugins/lua/rocklib.c @@ -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 */ diff --git a/apps/plugins/lua/settings_helper.pl b/apps/plugins/lua/settings_helper.pl index 2945afd354..4a280e60c7 100755 --- a/apps/plugins/lua/settings_helper.pl +++ b/apps/plugins/lua/settings_helper.pl @@ -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; diff --git a/apps/plugins/lua_scripts/dump_rbsettings.lua b/apps/plugins/lua_scripts/dump_rbsettings.lua new file mode 100644 index 0000000000..2811d27487 --- /dev/null +++ b/apps/plugins/lua_scripts/dump_rbsettings.lua @@ -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) diff --git a/apps/plugins/lua_scripts/track_metadata.lua b/apps/plugins/lua_scripts/track_metadata.lua new file mode 100644 index 0000000000..08c1c865f3 --- /dev/null +++ b/apps/plugins/lua_scripts/track_metadata.lua @@ -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