Page 1 of 1

Documenting cli.lua - a stripped console-only CLI

Posted: 07 Mar 2015 05:37
by nokangaroo
Pardon me if I am still in the wrong forum. Put this where it belongs if necessary. I posted an earlier version of this in "scripting
VLC with Lua", and I can put it back there.

The copyright notice is adapted from host.lua.

Code: Select all

--[==========================================================================[ shell.lua: A stripped console-only CLI for documentation --[==========================================================================[ Copyright (C) 2015 nokangaroo nokangaroo@NOSPAM.aon.at Work in progress Based on host.lua and cli.lua host.lua author: Antoine Cellerier <dionoea at videolan dot org> cli.lua authors: Antoine Cellerier <dionoea at videolan dot org> Pierre Ynard 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 program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. --]==========================================================================] running = true function quit() vlc.misc.quit() running = false end --Begin user-defined functions --[==========================================================================[ The input buffer is split into the global table "val"; val[0] is the function itself, val[1] ... val[n] is a variable number of arguments. There is no error handling (yet); you will have to restart vlc after every typo, but this script is mainly intended for receiving machine-generated input from a fifo. Test this interface with: luac5.1 <this file> mkdir -p $HOME/.local/share/vlc/lua/intf mv luac.out $HOME/.local/share/vlc/lua/intf/shell.luac vlc -I "luaintf{intf=shell}" 2>/dev/null /path/to/media or: mkfifo fifo exec 3<> fifo vlc <&3 -I "luaintf{intf=shell}" /path/to/media #Command examples: set_var input chapter +1 set_var vout crop 16:9 set_var input time +100 write_var input title /path/to/<disc_id>.dvdbookmarks echo set_var input time 123.456 > fifo set_var input title 0 #go to DVD menu --]==========================================================================] function help() local t = [[ ******************************************************************************** Available commands (the values can be float or int according to the command): set_time <value> alias for set_var input time <value> set_var <object> <variable> <value> set a variable (objects are input, aout, vout; variables are title, time, length, crop, zoom, aspect-ratio, rate) track <audio-es|spu-es> <value> set audio or subtitle track write_var <object> <variable> <path> write the value of a variable to a file write_var volume <path> volume <value> pause play help quit set_var and volume also allow incremental values (+-<value>) ******************************************************************************** ]] print(t) end function set_time() --e.g. set_time 123.456 local val = val[1] local input = vlc.object.input() local check = (vlc.var.get(input,"length") - val) if val and check > 0 then vlc.var.set(input,"time",val) end end function set_var() --e.g. set_var input time 123.456 local obj = val[1] local var = val[2] local value = val[3] --float or int depending on var local o if obj == "input" then o = vlc.object.input() elseif obj == "aout" then o = vlc.object.aout() elseif obj == "vout" then o = vlc.object.vout() end if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then local old = tonumber(vlc.var.get(o,var)) vlc.var.set(o,var,(old + tonumber(value))) else vlc.var.set(o,var,value) end end --write variables to file --[[for importing variables in shell scripts use something like this: get_var() { local i cat /dev/null > $TMP for i in "$@"; do echo "write_var input $i $TMP" > $FIFO done eval `cat $TMP` } ]] function write_var() --e.g. write_var input title <path> or write_var volume <path> local obj = val[1] local var = val[2] local path local value local o if obj == "input" then o = vlc.object.input() elseif obj == "aout" then o = vlc.object.aout() elseif obj == "vout" then o = vlc.object.vout() end if obj == "volume" then path = var var = obj value = vlc.volume.get() else path = val[3] value = vlc.var.get(o,var) end local file = io.open(tostring(path), "a+") file:write(tostring(var) .. '=' .. tostring(value) .. '\n') file:close() end --audio track and subtitle track --[[The listvalue function in cli.lua is bogus because elementary streams can change on DVDs when switching back and forth between titlesets. The order of tracks in the track list remains constant though; so we need to grep for the matching track and then set audio-es or spu-es to the value in the 1st column.]] function track() --e.g. track audio-es 3 or track spu-es 0 local var = val[1] local value = val[2] local o = vlc.object.input() if not value or tostring(value) == "0" then --mute audio, disable sub vlc.var.set(o,var,-1) --or 0 return end local str1 = string.format("Track %s", tostring(value)) local v,l = vlc.var.get_list(o,var) local val for i,val in ipairs(v) do local str = tostring(val) .. tostring(l[i]) if string.match(str,str1) then local t = string.match(str,"^%d+") vlc.var.set(o,var,t) break end end end function volume() --e.g. volume 100 or volume +10 local value = val[1] if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then vlc.volume.set(vlc.volume.get() + tonumber(value)) else vlc.volume.set(tostring(value)) end end function pause() vlc.playlist.pause() end function play() vlc.playlist.play() end --End user-defined functions --Utility: split buffer at word boundaries --Add stuff to the regex in string.gmatch if needed function split_input(str) local j = 0 val = {} for i in string.gmatch(str,"[%w_/%-%+%.:]+") do val[j] = i --num_args = j j = j+1 end end --??????????????????????????????????????? --Can someone explain what these are for? --function on_read(client) --end --function on_write(client) --end --??????????????????????????????????????? require "host" h = host.host() -- Bypass any authentication function on_password(client) client:switch_status(host.status.read) end h.status_callbacks[host.status.password] = on_password --h.status_callbacks[host.status.read] = on_read --h.status_callbacks[host.status.write] = on_write h:listen("*console") -- The main loop (cribbed from cli.lua and host.lua) while running do -- accept new connections and select active clients local write,read = h:accept_and_select() -- handle clients in write mode for _, client in pairs(write) do --this enables writing on the console: local len = client:send() client.buffer = string.sub(client.buffer,len + 1) if client.buffer == "" then client:switch_status(host.status.read) end end -- handle clients in read mode for _, client in pairs(read) do local input = client:recv(1000) --[[this seems to be the number of bytes received. If so, could probably be less for a pure console interface]] if not input then break end client.cmds = input .. '\n' --client.buffer = client.cmds --this will echo the commands client.buffer="" client:switch_status(host.status.write) split_input(client.cmds) --execute the command: _G[val[0]]() end end
Edit:
The main loop can also be written like this:

Code: Select all

while running do -- accept new connections and select active clients local write,read = h:accept_and_select() -- handle clients in write mode for _, client in pairs(write) do client:send() client:switch_status(host.status.read) end -- handle clients in read mode for _, client in pairs(read) do local input = client:recv(1000) --[[this seems to be the number of bytes received. If so, could probably be less for a pure console interface]] if not input then break end client.cmds = input --client.buffer = client.cmds --this will echo the commands --client.buffer="" --client:append(client.cmds) --can be redirected to file client:append(client.buffer) --can be redirected to file client:switch_status(host.status.write) split_input(client.cmds) --nil=play and nil=pause won't give an error: --local string = tostring(val[num_args - 1]) .. "=" .. tostring(val[num_args]) --client:append(string) --execute the command: _G[val[0]](client) end end
This enables making the script more verbose (not that I want that feature for myself):

Code: Select all

function volume(client) --e.g. volume 100 or volume +10 local value = val[1] if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then vlc.volume.set(vlc.volume.get() + tonumber(value)) else vlc.volume.set(tostring(value)) end local volume = "volume=" .. (vlc.volume.get()) client:append(volume) end

Re: Documenting cli.lua - a stripped console-only CLI

Posted: 09 Mar 2015 12:58
by nokangaroo
A version of the main loop with error handling:

Code: Select all

--GPL goes here. I hope I did not commit a crime by forgetting the copyright notice while running do -- accept new connections and select active clients local write,read = h:accept_and_select() -- handle clients in write mode for _, client in pairs(write) do client:send() client.buffer="" client:switch_status(host.status.read) end -- handle clients in read mode for _, client in pairs(read) do local input = client:recv(1000) if not input then break end client.cmds = input --client:append(client.buffer) client:switch_status(host.status.write) split_input(client.cmds) --execute the command without error handling: --_G[val[0]](client) --execute the command and catch errors: function exec() return _G[val[0]](client) end local ok,msg = pcall(exec) if not ok then client:append(msg) end end end

Re: Documenting cli.lua - a stripped console-only CLI

Posted: 11 Mar 2015 08:00
by nokangaroo
This is what I have so far. It might be a good idea to replace "set_var" with
"set" and "get_var" with "get" and to use something like "set_env" and "get_env"
for setting the environment (the most used commands should have the shortest
names).

Code: Select all

--[==========================================================================[ shell.lua: A stripped console-only CLI for documentation --[==========================================================================[ Copyright (C) 2015 nokangaroo nokangaroo@NOSPAM.aon.at Work in progress Based on host.lua and cli.lua host.lua author: Antoine Cellerier <dionoea at videolan dot org> cli.lua authors: Antoine Cellerier <dionoea at videolan dot org> Pierre Ynard 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 program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. --]==========================================================================] running = true function quit() h:broadcast("Shutting down.\n") vlc.misc.quit() running = false end function object(obj) if obj == "input" or obj == "i" or obj == "in" then return vlc.object.input() elseif obj == "aout" or obj == "a" or obj == "ao" then return vlc.object.aout() elseif obj == "vout" or obj == "v" or obj == "vo" then return vlc.object.vout() end end --Begin user-defined functions --[==========================================================================[ The input buffer is split into the global table "val"; val[0] is the function itself, val[1] ... val[n] is a variable number of arguments. Test this interface with: luac5.1 <this file> mkdir -p $HOME/.local/share/vlc/lua/intf mv luac.out $HOME/.local/share/vlc/lua/intf/shell.luac vlc -I "luaintf{intf=shell}" 2>/dev/null /path/to/media or: mkfifo fifo exec 3<> fifo vlc <&3 -I "luaintf{intf=shell}" /path/to/media #Command examples: set_var input chapter +1 set_var input chapter +0 #go to beginning of current chapter set_var input title 0 #go to DVD menu set_var vout crop 16:9 set_var vout deinterlace 1 set_var vout deinterlace-mode blend list input title #list DVD titles and lengths list input chapter #chapters for current DVD title list aout stereo-mode list vout deinterlace-mode get_var input length write_var input title /path/to/<disc_id>.dvdbookmarks echo set_var input time 123.456 > fifo --]==========================================================================] function help(client) local t = [[ ******************************************************************************** Available commands (the values can be float or int according to the command): set_time <value> alias for set_var input time <value> set_var <object> <variable> <value> set a variable (objects are input, aout, vout; variables are title, chapter, time, length, crop, stereo-mode, zoom, rate, deinterlace, deinterlace-mode, aspect-ratio) "i" and "in" can be used for "input" "a" and "ao" can be used for "aout" "v" and "vo" can be used for "vout" get_var <object> <variable> get the current value of a variable track <audio-es|spu-es> <value> set audio or subtitle track write_var <object> <variable> <path> write the value of a variable to a file write_var volume <path> list <object> <variable> [<path>] print a list of values. If a path is given, write a bash array to the output file, otherwise print a list to stdout. write_track <audio-es|spu-es> <path> write the current audio or subtitle track (NOT the es) to file. This is needed for DVD players. volume <value> pause play help quit set_var and volume also allow incremental values (+-<value>) ******************************************************************************** ]] client:append(t) end function set_time() --e.g. set_time 123.456 local val = val[1] local input = vlc.object.input() local check = (vlc.var.get(input,"length") - val) if val and check > 0 then vlc.var.set(input,"time",val) end end function set_var() --e.g. set_var input time 123.456 local obj = val[1] local var = val[2] local value = val[3] --float or int depending on var local o = object(obj) if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then local old = tonumber(vlc.var.get(o,var)) vlc.var.set(o,var,(old + tonumber(value))) else vlc.var.set(o,var,value) end end function get_var(client) --e.g. get_var input time local obj = val[1] local var = val[2] local o = object(obj) client:append(var .. "=" .. vlc.var.get(o,var)) end --write variables to file --[[for importing variables in shell scripts use something like this: get() { local i cat /dev/null > $TMP for i in "$@"; do echo "write_var input $i $TMP" > $FIFO done eval `cat $TMP` } ]] function write_var() --e.g. write_var input title <path> or write_var volume <path> local obj = val[1] local var = val[2] local path local value local sub if obj == "volume" then path = var sub = obj value = vlc.volume.get() else local o = object(obj) path = val[3] value = vlc.var.get(o,var) sub = string.gsub(var,'-','_') --shell vars can't have "-" end local file = io.open(tostring(path), "a+") file:write(tostring(sub) .. '=' .. tostring(value) .. '\n') file:close() end --audio track and subtitle track --[[The listvalue function in cli.lua is bogus because elementary streams can change on DVDs when switching back and forth between titlesets. The order of tracks in the track list remains constant though; so we need to grep for the matching track and then set audio-es or spu-es to the value in the 1st column.]] function track() --e.g. track audio-es 3 or track spu-es 0 local var = val[1] local value = val[2] local o = vlc.object.input() if not value or tostring(value) == "0" then --mute audio, disable sub vlc.var.set(o,var,-1) --or 0 return end local str1 = string.format("Track %s", tostring(value)) local v,l = vlc.var.get_list(o,var) local val for i,val in ipairs(v) do local str = tostring(val) .. tostring(l[i]) if string.match(str,str1) then local t = string.match(str,"^%d+") vlc.var.set(o,var,t) break end end end --[[List values. If a path is given, write a bash array to the output file, else pretty-print to stdout. See also next function]] function list(client) --e.g. list input audio-es [<path>] local obj = val[1] local var = val[2] local path = val[3] local o = object(obj) local val local v,l = vlc.var.get_list(o,var) if path then local file = io.open(tostring(path), "a+") local sub = string.gsub(var,'-','_') --shell vars can't have "-" file:write(sub .. '=( ') for i,val in ipairs(v) do file:write(tostring(val) .. " ") end file:write(')\n') file:close() else local c = vlc.var.get(o,var) client:append("******** " .. var .. " ********") for i,val in ipairs(v) do local mark = (val == c) and " *" or "" client:append(tostring(val) .. ' - ' .. tostring(l[i]) .. mark) end client:append("****** end " .. var .. " ******") end end --[[Write the current audio or subtitle track to the output file (Needed for DVD players. When switching between bookmarks on different titlesets, you will need to re-set the track with every switch on some DVDs)]] function write_track() --e.g. write_track audio-es <path> local o = vlc.object.input() local var = val[1] local path = val[2] local val local v,l = vlc.var.get_list(o,var) local file = io.open(tostring(path), "a+") local sub = string.gsub(var,'-es','_track') --shell vars can't have "-" local val1 = vlc.var.get(o,var) for i,val in ipairs(v) do if val1 == val then --this will keep the language information (which will sometimes --be needed): --file:write(string.gsub(l[i],"Track ",sub .. "=") .. '\n') --this gives a clean eval: file:write(sub .. "=" .. string.match(l[i],"%d+") .. '\n') break end end file:close() end function volume(client) --e.g. volume 100 or volume +10 local value = val[1] local vol = vlc.volume.get() if value then if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then vlc.volume.set(vol + tonumber(value)) else vlc.volume.set(value) end else client:append("volume=" .. vol) end end function pause() vlc.playlist.pause() end function play() vlc.playlist.play() end --End user-defined functions --Utility: split buffer at word boundaries --Add stuff to the regex in string.gmatch if needed function split_input(str) local j = 0 val = {} for i in string.gmatch(str,"[%w_/%-%+%.:]+") do val[j] = i --num_args = j j = j+1 end end require "host" h = host.host() -- Bypass any authentication and send welcome message: function on_password(client) client:send("Custom CLI initialized. Type 'help' for help.\n") client:switch_status(host.status.read) end -- Send prompt (doesn't seem to be useful for anything else): function prompt(client) client:send("> ") end --for mplayer fans: function prompt1(client) client:send("ANS_") end h.status_callbacks[host.status.password] = on_password h.status_callbacks[host.status.read] = prompt --h.status_callbacks[host.status.write] = prompt1 h:listen("*console") -- The main loop (cribbed from cli.lua and host.lua) while running do -- accept new connections and select active clients local write,read = h:accept_and_select() -- handle clients in write mode for _, client in pairs(write) do client:send() client.buffer="" client:switch_status(host.status.read) end -- handle clients in read mode for _, client in pairs(read) do local input = client:recv(1000) --buffer overflow alert if not input then break end client.cmds = input client:switch_status(host.status.write) split_input(client.cmds) --execute the command and catch errors: function exec() return _G[val[0]](client) end local ok,msg = pcall(exec) if not ok then client:append(msg) end end end

Re: Documenting cli.lua - a stripped console-only CLI

Posted: 12 Mar 2015 10:20
by nokangaroo
A stripped-down console-only version of host.lua. To use it, move it to
$HOME/.local/share/vlc/lua/intf/modules/consolehost.lua (doesn't need to be
compiled) and require it in the shell.lua script. The

h.status_callbacks[host.status.password] = on_password

line can be commented out.

Code: Select all

--[==========================================================================[ host.lua: VLC Lua interface command line host module --[==========================================================================[ Copyright (C) 2007-2012 the VideoLAN team $Id$ Authors: Antoine Cellerier <dionoea at videolan dot org> 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 program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. --]==========================================================================] module("host",package.seeall) status = { init = 0, read = 1, write = 2, startup = 3 } --no passwd for console, --but a startup status enables scripts to send messages client_type = 2 -- stdio function is_flag_set(val, flag) return (((val - (val % flag)) / flag) % 2 ~= 0) end function host() -- private data local clients = {} local listeners = {} local status_callbacks = {} -- private methods local function fd_client( client ) if client.status == status.read then return client.rfd else -- status.write return client.wfd end end local function send( client, data, len ) if len then return vlc.net.send( client.wfd, data, len ) else return vlc.net.send( client.wfd, data or client.buffer ) end end local function recv( client, len ) if len then return vlc.net.recv( client.rfd, len ) else return vlc.net.recv( client.rfd ) end end local function write( client, data ) return vlc.net.write( client.wfd, data or client.buffer ) end local function read( client, len ) if len then return vlc.net.read( client.rfd, len ) else return vlc.net.read( client.rfd ) end end local function del_client( client ) if not clients[client] then vlc.msg.err("couldn't find client to remove.") return end --h:broadcast("Shutting down.\r\n") --vlc.misc.quit() clients[client] = nil end local function switch_status( client, s ) if client.status == s then return end client.status = s if status_callbacks[s] then status_callbacks[s]( client ) end end -- append a line to a client's (output) buffer local function append( client, string ) client.buffer = client.buffer .. string .. "\r\n" end local function new_client( h, fd, wfd, t ) if fd < 0 then return end local w = write local r = read local client = { -- data rfd = fd, wfd = wfd or fd, status = status.init, buffer = "", cmds = "", type = t, -- methods fd = fd_client, send = w, recv = r, del = del_client, switch_status = switch_status, append = append, } client:send("VLC media player "..vlc.misc.version().."\nConsolehost is listening on host \"console\"\n") clients[client] = client --client:switch_status(status.startup) client:switch_status(status.read) -- no passwd for console end -- public methods local function _listen_stdio( h ) if listeners.stdio then error("Already listening on stdio") end new_client( h, 0, 1, client_type) listeners.stdio = true end local function _accept_and_select( h ) local wclients = {} local rclients = {} local function filter_client( fds, status, event ) for _, client in pairs(clients) do if client.status == status then fds[client:fd()] = event end end end local pollfds = {} filter_client( pollfds, status.read, vlc.net.POLLIN ) --filter_client( pollfds, status.password, vlc.net.POLLIN ) filter_client( pollfds, status.write, vlc.net.POLLOUT ) local ret = vlc.net.poll( pollfds ) if ret > 0 then for _, client in pairs(clients) do if is_flag_set(pollfds[client:fd()], vlc.net.POLLIN) then table.insert(rclients, client) elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLERR) or is_flag_set(pollfds[client:fd()], vlc.net.POLLHUP) or is_flag_set(pollfds[client:fd()], vlc.net.POLLNVAL) then client:del() elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLOUT) then table.insert(wclients, client) end end end return wclients, rclients end local function destructor( h ) for _,client in pairs(clients) do if client.type ~= 2 then client:del() end end end local function _broadcast( h, msg ) for _,client in pairs(clients) do client:send( msg ) end end if setfenv then -- We're running Lua 5.1 -- See http://lua-users.org/wiki/HiddenFeatures for more info. local proxy = newproxy(true) getmetatable(proxy).__gc = destructor destructor = proxy end -- the instance local h = setmetatable( { -- data status_callbacks = status_callbacks, -- methods listen = _listen_stdio, accept_and_select = _accept_and_select, broadcast = _broadcast, }, { -- metatable __gc = destructor, -- Should work in Lua 5.2 without the new proxytrick as __gc is also called on tables (needs to be tested) __metatable = "", }) return h end

Re: Documenting cli.lua - a stripped console-only CLI

Posted: 14 Mar 2015 19:21
by nokangaroo
A hostless version of the CLI with the needed parts of host.lua inserted (to
have a single file for studying). To avoid too much repetition, I left out
some of the functions.

Unfortunately, polling (which is required) seems to be undocumented. This
should be easier.

Code: Select all

--[==========================================================================[ shell.lua: A stripped console-only CLI for documentation --[==========================================================================[ Copyright (C) 2015 nokangaroo nokangaroo@NOSPAM.aon.at Work in progress Based on host.lua and cli.lua host.lua author: Antoine Cellerier <dionoea at videolan dot org> cli.lua authors: Antoine Cellerier <dionoea at videolan dot org> Pierre Ynard 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 program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. --]==========================================================================] running = true function quit() h:broadcast("Shutting down.\n") vlc.misc.quit() running = false end function object(obj) if obj == "input" or obj == "i" or obj == "in" then return vlc.object.input() elseif obj == "aout" or obj == "a" or obj == "ao" then return vlc.object.aout() elseif obj == "vout" or obj == "v" or obj == "vo" then return vlc.object.vout() end end --Begin user-defined functions function set() --e.g. set input time 123.456 local obj = val[1] local var = val[2] local value = val[3] --float or int depending on var local o = object(obj) if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then local old = tonumber(vlc.var.get(o,var)) vlc.var.set(o,var,(old + tonumber(value))) else vlc.var.set(o,var,value) end end function get(client) --e.g. get input time local obj = val[1] local var = val[2] local o = object(obj) client:append(var .. "=" .. vlc.var.get(o,var)) end function volume(client) --e.g. volume 100 or volume +10 local value = val[1] local vol = vlc.volume.get() if value then if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then vlc.volume.set(vol + tonumber(value)) else vlc.volume.set(value) end else client:append("volume=" .. vol) end end function pause() vlc.playlist.pause() end function play() vlc.playlist.play() end --End user-defined functions --Utility: split buffer at word boundaries --Add stuff to the regex in string.gmatch if needed function split_input(str) local j = 0 val = {} for i in string.gmatch(str,"[%w_/%-%+%.:]+") do val[j] = i --num_args = j j = j+1 end end --the required stuff from host.lua inserted here: status = { init = 0, read = 1, write = 2 } function is_flag_set(val, flag) return (((val - (val % flag)) / flag) % 2 ~= 0) end function host() -- private data local clients = {} local listeners = {} local status_callbacks = {} -- private methods local function fd_client(client) if client.status == status.read then return client.rfd else -- status.write return client.wfd end end local function send(client, data, len) if len then return vlc.net.send(client.wfd, data, len) else return vlc.net.send(client.wfd, data or client.buffer) end end local function recv(client, len) if len then return vlc.net.recv(client.rfd, len) else return vlc.net.recv(client.rfd) end end local function write(client, data) return vlc.net.write(client.wfd, data or client.buffer) end local function read(client, len) if len then return vlc.net.read(client.rfd, len) else return vlc.net.read(client.rfd) end end local function switch_status(client, s) if client.status == s then return end client.status = s if status_callbacks[s] then status_callbacks[s](client) end end -- append a line to a client's (output) buffer local function append(client, string) client.buffer = client.buffer .. string .. "\n" end local function new_client(h, fd, wfd) if fd < 0 then return end local w = write local r = read local client = { -- data rfd = fd, wfd = wfd or fd, status = status.init, buffer = "", cmds = "", -- methods fd = fd_client, send = w, recv = r, switch_status = switch_status, append = append, } client:send("VLC media player " .. vlc.misc.version() .. "\nHostless CLI initialized\n") clients[client] = client client:switch_status(status.read) -- no passwd for console end -- public methods local function _listen_stdio(h) if listeners.stdio then error("Already listening on stdio") end new_client(h, 0, 1) listeners.stdio = true end local function _accept_and_select(h) local wclients = {} local rclients = {} local function filter_client(fds, status, event) for _, client in pairs(clients) do if client.status == status then fds[client:fd()] = event end end end local pollfds = {} filter_client(pollfds, status.read, vlc.net.POLLIN) filter_client(pollfds, status.write, vlc.net.POLLOUT) local ret = vlc.net.poll(pollfds) if ret > 0 then for _, client in pairs(clients) do if is_flag_set(pollfds[client:fd()], vlc.net.POLLIN) then table.insert(rclients, client) elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLERR) or is_flag_set(pollfds[client:fd()], vlc.net.POLLHUP) then elseif is_flag_set(pollfds[client:fd()], vlc.net.POLLOUT) then table.insert(wclients, client) end end end return wclients, rclients end local function _broadcast(h, msg) for _,client in pairs(clients) do client:send(msg) end end -- the instance local h = { -- data status_callbacks = status_callbacks, -- methods listen = _listen_stdio, accept_and_select = _accept_and_select, broadcast = _broadcast, } return h end --End inserted host h = host() -- Send prompt (doesn't seem to be useful for anything else): function prompt(client) client:send("> ") end h.status_callbacks[status.read] = prompt h:listen("*console") while running do -- accept new connections and select active clients local write,read = h:accept_and_select() -- handle clients in write mode for _, client in pairs(write) do client:send() client.buffer="" client:switch_status(status.read) end -- handle clients in read mode for _, client in pairs(read) do local input = client:recv(1000) if not input then break end client.cmds = input client:switch_status(status.write) split_input(client.cmds) --execute the command and catch errors: function exec() return _G[val[0]](client) end local ok,msg = pcall(exec) if not ok then client:append(msg) end end end

Re: Documenting cli.lua - a stripped console-only CLI

Posted: 16 Mar 2015 19:46
by nokangaroo
A minimal console-only host module. I can't reduce it any further (and I don't
think it would make sense to try).

vlc.net.POLLIN = 1 and vlc.net.POLLOUT = 4, at least on my box.

To use it, move it to
$HOME/.local/share/vlc/lua/intf/modules/minimalhost.lua and require it in the
shell.lua script. The

h.status_callbacks[host.status.password] = on_password

line can be commented out (or "host.status.password" changed to "host.status.init").
h:listen("*console") can be replaced by h:listen().

Code: Select all

--[==========================================================================[ minimalhost.lua: VLC Lua interface console-only host module for documentation --[==========================================================================[ Copyright (C) 2015 nokangaroo nokangaroo@NOSPAM.aon.at Copyright (C) 2007-2012 the VideoLAN team $Id$ Based on host.lua host.lua author: Antoine Cellerier <dionoea at videolan dot org> 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 program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. --]==========================================================================] module("host",package.seeall) status = { init = 0; read = 1, write = 2 } --init is optional function host() -- private data local clients = {} local listeners = {} local status_callbacks = {} -- private methods local function write(client, data) return vlc.net.write(client.wfd, data or client.buffer) end local function read(client, len) if len then return vlc.net.read(client.rfd, len) else return vlc.net.read(client.rfd) end end local function switch_status(client, s) if client.status == s then return end client.status = s if status_callbacks[s] then status_callbacks[s](client) end end -- append a line to a client's (output) buffer local function append(client, string) client.buffer = client.buffer .. string .. "\n" end local function new_client(fd) if fd < 0 then return end local client = { -- data rfd = fd, wfd = fd, --or fd + 1 status = "", buffer = "", cmds = "", -- methods send = write, recv = read, switch_status = switch_status, append = append, } client:send("VLC media player " .. vlc.misc.version() .. "\n*** console initialized ***\n") clients[client] = client client:switch_status(status.init) --for clients to send welcome messages client:switch_status(status.read) end -- public methods local function _listen_stdio(h) if listeners.stdio then error("Already listening on stdio") end new_client(0) listeners.stdio = true end local function _accept_and_select(h) local wclients = {} local rclients = {} local pollfds = {} for i, client in pairs(clients) do -- _ has no special meaning if client.status == status.read then pollfds[client.rfd] = vlc.net.POLLIN elseif client.status == status.write then pollfds[client.wfd] = vlc.net.POLLOUT end end local ret = vlc.net.poll(pollfds) if ret > 0 then for i, client in pairs(clients) do table.insert(rclients, client) table.insert(wclients, client) end end return wclients, rclients end local function _broadcast(h, msg) for i, client in pairs(clients) do client:send(msg) end end -- the instance local h = { -- data status_callbacks = status_callbacks, -- methods listen = _listen_stdio, accept_and_select = _accept_and_select, broadcast = _broadcast, } return h end

Re: Documenting cli.lua - a stripped console-only CLI

Posted: 23 Mar 2015 16:02
by nokangaroo
I was wrong - it can be further reduced. And I am having trouble believing this
myself. Not sure if this code proves anything - I wanted to find out about polling,
but it turns out it's not even required. Anyway, for local commandline control
of VLC this seems to be the way to go (3 cheers for good old imperative programming).
As before, I took out most of the functions for brevity.

Code: Select all

--[==========================================================================[ hostless.lua: A stripped console-only CLI for documentation --[==========================================================================[ Copyright (C) 2015 nokangaroo nokangaroo@NOSPAM.aon.at Work in progress Based on host.lua and cli.lua host.lua author: Antoine Cellerier <dionoea at videolan dot org> cli.lua authors: Antoine Cellerier <dionoea at videolan dot org> Pierre Ynard 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 program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. --]==========================================================================] running = true function quit() send("Shutting down.\n") vlc.misc.quit() running = false end function object(obj) if obj == "input" or obj == "i" or obj == "in" then return vlc.object.input() elseif obj == "aout" or obj == "a" or obj == "ao" then return vlc.object.aout() elseif obj == "vout" or obj == "v" or obj == "vo" then return vlc.object.vout() end end --Begin user-defined functions function set() --e.g. set input time 123.456 local obj = val[1] local var = val[2] local value = val[3] --float or int depending on var local o = object(obj) if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then local old = tonumber(vlc.var.get(o,var)) vlc.var.set(o,var,(old + tonumber(value))) else vlc.var.set(o,var,value) end end function get() --e.g. get input time local obj = val[1] local var = val[2] local o = object(obj) send(var .. "=" .. vlc.var.get(o,var) .. '\n') end --[[List values. If a path is given, write a bash array to the output file, else pretty-print to stdout. See also next function]] function list() --e.g. list input audio-es [<path>] local obj = val[1] local var = val[2] local path = val[3] local o = object(obj) local val local v,l = vlc.var.get_list(o,var) if path then local file = io.open(tostring(path), "a+") local sub = string.gsub(var,'-','_') --shell vars can't have "-" file:write(sub .. '=( ') for i,val in ipairs(v) do file:write(tostring(val) .. " ") end file:write(')\n') file:close() else local c = vlc.var.get(o,var) send("******** " .. var .. " ********\n") for i,val in ipairs(v) do local mark = (val == c) and " *" or "" send(tostring(val) .. ' - ' .. tostring(l[i]) .. mark .. '\n') end send("****** end " .. var .. " ******\n") end end function volume() --e.g. volume 100 or volume +10 local value = val[1] local vol = vlc.volume.get() if value then if type(value) == "string" and string.sub(value,1,1) == "+" or string.sub(value,1,1) == "-" then vlc.volume.set(vol + tonumber(value)) else vlc.volume.set(value) end else send("volume=" .. vol .. '\n') end end function pause() vlc.playlist.pause() end function play() vlc.playlist.play() end --End user-defined functions --Utility: split buffer at word boundaries --Add stuff to the regex in string.gmatch if needed function split_input(str) local j = 0 val = {} for i in string.gmatch(str,"[%w_/%-%+%.:]+") do val[j] = i j = j+1 end end -- Send prompt: function prompt() send("> ") end --Begin commandline host: function send(data) return vlc.net.write(1, data) end local client send("VLC media player " .. vlc.misc.version() .. "\n*** hostless CLI initialized ***\n") --End commandline host --The main loop: while running do prompt() client = vlc.net.read(0,1000) if not client then break end split_input(client) client="" --execute the command and catch errors: function exec() return _G[val[0]]() end local ok,msg = pcall(exec) if not ok then send(msg .. '\n') end end