A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

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

+342 -10
+164
apps/plugins/lua/include_lua/rbsettings.lua
··· 1 + --[[ Lua rb settings reader 2 + /*************************************************************************** 3 + * __________ __ ___. 4 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 5 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 6 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 7 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 8 + * \/ \/ \/ \/ \/ 9 + * $Id$ 10 + * 11 + * Copyright (C) 2019 William Wilgus 12 + * 13 + * This program is free software; you can redistribute it and/or 14 + * modify it under the terms of the GNU General Public License 15 + * as published by the Free Software Foundation; either version 2 16 + * of the License, or (at your option) any later version. 17 + * 18 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 + * KIND, either express or implied. 20 + * 21 + ****************************************************************************/ 22 + ]] 23 + 24 + rb.settings = rb.settings or {} 25 + 26 + local var = {offset = 1, size = 2, type = 3, fields = 3} 27 + 28 + local function bytesLE_n(str) 29 + str = str or "" 30 + local tbyte={str:byte(1, -1)} 31 + local bpos, num = 1, 0 32 + for k = 1,#tbyte do -- (k = #t, 1, -1 for BE) 33 + num = num + tbyte[k] * bpos 34 + bpos = bpos * 256 --1<<8 35 + end 36 + return num 37 + end 38 + 39 + local function get_var_fields(s_var) 40 + -- converts member string into table 41 + -- var = {offset, size, "type"} 42 + s_var = s_var or "" 43 + local o, s, t = string.match(s_var, "(0x%x+),%s*(%d+),%s*(.+)") 44 + local tvar = {o, s, t} 45 + 46 + return #tvar == var.fields and tvar or nil 47 + end 48 + 49 + local function format_val(val, var_type) 50 + local ret, num 51 + if var_type == nil then 52 + return nil 53 + elseif var_type == "str" then 54 + -- stop at first null byte, return nil if str doesn't exist 55 + return val and string.match(val, "^%Z+") or nil 56 + end 57 + 58 + num = bytesLE_n(val) 59 + if string.find(var_type, "^b") then 60 + if(num <= 0) then 61 + ret = false 62 + else 63 + ret = true 64 + end 65 + elseif string.find(var_type, "^u_[cil]") then 66 + -- Lua integers are signed so we need to do a bit of extra processing 67 + ret = (string.format("%u", num)) 68 + else 69 + ret = num 70 + end 71 + 72 + return ret 73 + end 74 + 75 + local function dump_struct(t_settings, t_struct, n_elems, t_var) 76 + --Internal function dumps structs 77 + local tdata = {} 78 + 79 + local function struct_get_elem(v, elem_offset) 80 + local val, offset, tvar1 81 + tvar1 = get_var_fields(v) 82 + offset = t_var[var.offset] + tvar1[var.offset] + elem_offset 83 + val = t_settings(offset, tvar1[var.size]) 84 + return format_val(val, tvar1[var.type]) 85 + end 86 + 87 + if n_elems > 0 then 88 + -- Array of structs, struct[elems]; 89 + local elemsize = (t_var[var.size] / n_elems) 90 + for i = 0, n_elems - 1 do 91 + tdata[i] = tdata[i] or {} 92 + for k1, v1 in pairs(t_struct) do 93 + tdata[i][k1] = struct_get_elem(v1, (elemsize * i)) 94 + end 95 + end 96 + else 97 + -- single struct, struct; 98 + for k1, v1 in pairs(t_struct) do 99 + tdata[k1] = struct_get_elem(v1, 0) 100 + end 101 + end 102 + return tdata 103 + end 104 + 105 + local function get_array_elems(var_type) 106 + --extract the number of elements, returns 0 if not found 107 + local elems = string.match(var_type,".*%[(%d+)%]") 108 + return tonumber(elems) or 0 109 + end 110 + 111 + local function get_struct_name(var_type) 112 + --extract the name of a struct, returns nil if not found 113 + return string.match(var_type,"^s_([^%[%]%s]+)") 114 + end 115 + 116 + function rb.settings.read(s_settings, s_var, s_groupname) 117 + local data, val 118 + local tvar = get_var_fields(s_var) 119 + if tvar == nil then return nil end 120 + 121 + local elems = get_array_elems(tvar[var.type]) 122 + local structname = get_struct_name(tvar[var.type]) 123 + 124 + local tsettings = rb[s_settings] 125 + if not tsettings then error(s_settings .. " does not exist") end 126 + 127 + if structname and rb[s_groupname] then 128 + return dump_struct(tsettings, rb[s_groupname][structname], elems, tvar) 129 + end 130 + 131 + local voffset, vsize, vtype = tvar[var.offset], tvar[var.size], tvar[var.type] 132 + if elems > 0 then 133 + -- Arrays of values, val[elems]; 134 + data = {} 135 + local elemsize = (vsize / elems) 136 + 137 + for i = 0, elems - 1 do 138 + val = tsettings(voffset + (elemsize * i), elemsize) 139 + data[i] = format_val(val, vtype) 140 + end 141 + else 142 + -- Single value, val; 143 + if vtype == "ptr_char" then -- (**char) 144 + vtype = "str" 145 + val = tsettings(voffset, vsize, nil, true) 146 + else 147 + val = tsettings(voffset, vsize) 148 + end 149 + data = format_val(val, vtype) 150 + end 151 + return data 152 + end 153 + 154 + function rb.settings.dump(s_settings, s_groupname, s_structname, t_output) 155 + t_output = t_output or {} 156 + local tgroup = rb[s_groupname] 157 + s_structname = s_structname or s_settings 158 + for k, v in pairs(tgroup[s_structname]) do 159 + t_output[k] = rb.settings.read(s_settings, v, s_groupname) 160 + end 161 + return t_output 162 + end 163 + 164 + return true
+1 -1
apps/plugins/lua/lua.make
··· 19 19 LUA_INCLUDELIST := $(addprefix $(LUA_BUILDDIR)/,audio.lua blit.lua color.lua draw.lua draw_floodfill.lua draw_poly.lua \ 20 20 draw_num.lua draw_text.lua image.lua image_save.lua lcd.lua math_ex.lua \ 21 21 print.lua timer.lua playlist.lua pcm.lua sound.lua \ 22 - rbcompat.lua poly_points.lua printtable.lua) 22 + rbcompat.lua rbsettings.lua poly_points.lua printtable.lua) 23 23 24 24 25 25 ifndef APP_TYPE
+35 -7
apps/plugins/lua/rocklib.c
··· 640 640 return 1; 641 641 } 642 642 643 - static int mem_read_write(lua_State *L, uintptr_t address, size_t maxsize) 643 + static int mem_read_write(lua_State *L, uintptr_t address, size_t maxsize, bool isstr_p) 644 644 { 645 + if(isstr_p) /*pointer to string (**char)*/ 646 + { 647 + lua_settop(L, 2); /* no writes allowed */ 648 + } 645 649 intptr_t offset = (intptr_t) luaL_optnumber(L, 1, 0); 646 650 size_t size = (size_t) luaL_optnumber(L, 2, maxsize); 647 651 size_t written; ··· 716 720 case LUA_TNIL: 717 721 case LUA_TNONE: /* reader */ 718 722 { 723 + if(isstr_p && mem) 724 + { 725 + lua_pushstring (L, *(char**) mem); 726 + return 1; 727 + } 719 728 luaL_Buffer b; 720 729 luaL_buffinit(L, &b); 721 730 while(size > 0) ··· 746 755 { 747 756 const uintptr_t address = (uintptr_t) rb->global_status; 748 757 const size_t maxsize = sizeof(struct system_status); 749 - return mem_read_write(L, address, maxsize); 758 + /*const bool isstr_p = lua_toboolean(L, 4);*/ 759 + return mem_read_write(L, address, maxsize, false); 750 760 } 751 761 752 762 RB_WRAP(global_settings) 753 763 { 754 764 const uintptr_t address = (uintptr_t) rb->global_settings; 755 765 const size_t maxsize = sizeof(struct user_settings); 756 - return mem_read_write(L, address, maxsize); 766 + /*const bool isstr_p = lua_toboolean(L, 4);*/ 767 + return mem_read_write(L, address, maxsize, false); 757 768 } 758 769 759 770 RB_WRAP(audio_next_track) 760 771 { 761 - lua_settop(L, 2); /* no writes allowed */ 772 + 762 773 const uintptr_t address = (uintptr_t) rb->audio_next_track(); 763 774 const size_t maxsize = sizeof(struct mp3entry); 764 - return mem_read_write(L, address, maxsize); 775 + const bool isstr_p = lua_toboolean(L, 4); 776 + lua_settop(L, 2); /* no writes allowed */ 777 + return mem_read_write(L, address, maxsize, isstr_p); 765 778 } 766 779 767 780 RB_WRAP(audio_current_track) 768 781 { 769 - lua_settop(L, 2); /* no writes allowed */ 782 + 770 783 const uintptr_t address = (uintptr_t) rb->audio_current_track(); 771 784 const size_t maxsize = sizeof(struct mp3entry); 772 - return mem_read_write(L, address, maxsize); 785 + const bool isstr_p = lua_toboolean(L, 4); 786 + lua_settop(L, 2); /* no writes allowed */ 787 + return mem_read_write(L, address, maxsize, isstr_p); 788 + } 789 + 790 + #if 0 791 + RB_WRAP(read_mem) 792 + { 793 + lua_settop(L, 2); /* no writes allowed */ 794 + const uintptr_t address = lua_tonumber(L, 1); 795 + const size_t maxsize = luaL_optnumber(L, 2, strlen((char *)address)); 796 + luaL_argcheck(L, address > 0, 1, ERR_IDX_RANGE); 797 + lua_pushnil(L); 798 + lua_replace(L, -3);/* stk pos 1 is no longer offset it is starting address */ 799 + return mem_read_write(L, address, maxsize, false); 773 800 } 801 + #endif 774 802 775 803 RB_WRAP(restart_lua) 776 804 {
+3 -2
apps/plugins/lua/settings_helper.pl
··· 293 293 $type = sprintf('%s[%d]', $1, $arr); 294 294 } 295 295 296 - printf "\t%s = {0x%x, %d, \"%s\"},\n", $member, $offset, $size, $type; 296 + printf "\t%s = \"0x%x, %d, %s\",\n", $member, $offset, $size, $type; 297 297 return 1; 298 298 } 299 299 return 0; ··· 303 303 { 304 304 print "-- Don't change this file!\n"; 305 305 printf "-- It is automatically generated %s\n", $svnrev; 306 - print "-- member = {offset, size, \"type\"}\n\n"; 306 + print "-- member = \"offset, size, type\"\n\n"; 307 307 308 308 print "--"; 309 309 foreach my $key (sort(keys %replace_type_prefix)) { ··· 337 337 } 338 338 } 339 339 } 340 + print "\nreturn false\n"; 340 341 #my ($user,$system,$cuser,$csystem) = times; 341 342 #warn "Pass2 ".$user." ".$system." ".$cuser." ".$csystem."\n"; 342 343 exit;
+49
apps/plugins/lua_scripts/dump_rbsettings.lua
··· 1 + require("rbsettings") 2 + require("settings") 3 + rb.metadata = nil -- remove track metadata settings 4 + ------------------------------------------------------------------------------- 5 + 6 + local function print_setting_table(t_tbl, s_sep) 7 + s_sep = s_sep or "" 8 + local str = "" 9 + local function pfunct(t, sep, s, n) -- recursive print function 10 + local vtype 11 + for k, v in pairs(t) do 12 + vtype = type(v) 13 + if vtype == "table" then 14 + local f = string.format("%s[%s]", n, k) 15 + s = pfunct(v, sep, s, f) 16 + elseif vtype == "boolean" then 17 + v = v and "true" or "false" 18 + s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep) 19 + elseif v then 20 + s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep) 21 + end 22 + end 23 + return s 24 + end 25 + return pfunct(t_tbl, s_sep, str, "") 26 + end 27 + 28 + local filename = "/settings.txt" 29 + local file = io.open(filename, "w+") -- overwrite 30 + local t_settings 31 + 32 + if not file then 33 + rb.splash(rb.HZ, "Error writing " .. filename) 34 + return 35 + end 36 + 37 + t_settings = rb.settings.dump('global_settings', "system") 38 + file:write("global_settings:\n") 39 + file:write(print_setting_table(t_settings, "\n")) 40 + file:write("\n\n") 41 + 42 + t_settings = rb.settings.dump('global_status', "system") 43 + file:write("global_status:\n") 44 + file:write(print_setting_table(t_settings, "\n")) 45 + file:write("\n\n") 46 + 47 + file:close() 48 + 49 + rb.splash(100, "rb settings dumped: " .. filename)
+90
apps/plugins/lua_scripts/track_metadata.lua
··· 1 + require("rbsettings") 2 + require("settings") --settings.lua 3 + rb.system = nil -- remove system settings 4 + ------------------------------------------------------------------------------- 5 + local track_data = rb.metadata.mp3_entry 6 + local cur_trk = "audio_current_track" 7 + ------------------------------------------------------------------------------- 8 + local trackname = rb.settings.read(cur_trk, track_data.title) or 9 + rb.settings.read(cur_trk, track_data.path) 10 + if not trackname or trackname == "" then 11 + os.exit(1, "No track loaded") 12 + else 13 + rb.splash(100, trackname) 14 + end 15 + ------------------------------------------------------------------------------- 16 + local function dump_albumart(fileout) 17 + local t_albumart = rb.settings.read(cur_trk, track_data.albumart, "metadata") 18 + local t_aaext = {".bmp",".png", ".jpg"} 19 + local path = rb.settings.read(cur_trk, track_data.path) 20 + if t_albumart.pos > 0 and t_albumart.size > 0 and t_albumart.type > 0 then 21 + 22 + if t_aaext[t_albumart.type] then 23 + local filename = "/" .. fileout .. t_aaext[t_albumart.type] 24 + local aa = io.open(filename, "w+") -- overwrite 25 + if not aa then 26 + rb.splash(rb.HZ, "Error writing " .. filename) 27 + return 28 + end 29 + 30 + local track = io.open(path, "r") 31 + if not track then 32 + rb.splash(rb.HZ, "Error opening " .. path) 33 + return 34 + end 35 + track:seek("set", t_albumart.pos ) 36 + for i = 0, t_albumart.size, 32 do 37 + aa:write(track:read(32)) 38 + end 39 + rb.splash(rb.HZ, "Saved: " .. filename) 40 + track:close() 41 + aa:close() 42 + else 43 + 44 + end 45 + end 46 + end 47 + 48 + local function print_setting_table(t_tbl, s_sep) 49 + s_sep = s_sep or "" 50 + local str = "" 51 + local function pfunct(t, sep, s, n) -- recursive print function 52 + local vtype 53 + for k, v in pairs(t) do 54 + vtype = type(v) 55 + if vtype == "table" then 56 + local f = string.format("%s[%s]", n, k) 57 + s = pfunct(v, sep, s, f) 58 + elseif vtype == "boolean" then 59 + v = v and "true" or "false" 60 + s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep) 61 + elseif v then 62 + s = string.format("%s%s[%s] = %s%s", s, n, k, v, sep) 63 + end 64 + end 65 + return s 66 + end 67 + return pfunct(t_tbl, s_sep, str, "") 68 + end 69 + 70 + local filename = "/metadata.txt" 71 + local file = io.open(filename, "w+") -- overwrite 72 + local t_settings 73 + 74 + if not file then 75 + rb.splash(rb.HZ, "Error writing " .. filename) 76 + return 77 + end 78 + 79 + ---[[ 80 + t_settings = rb.settings.dump(cur_trk, "metadata", "mp3_entry") 81 + file:write(trackname .. ":\n") 82 + file:write(print_setting_table(t_settings, "\n")) 83 + file:write("\n\n") 84 + file:close() 85 + 86 + rb.splash(100, "metadata dumped: " .. filename) 87 + 88 + if rb.settings.read(cur_trk, track_data.has_embedded_albumart) then 89 + dump_albumart("/albumart") 90 + end