#!/usr/bin/lua local hasuci,uci = pcall(require,"uci") require("socket") json=require("json") function url_decode(str) if not str then return nil end str = string.gsub (str, "+", " ") str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end) str = string.gsub (str, "\r\n", "\n") return str end function url_encode(str) --Ensure all newlines are in CRLF form str = string.gsub (str, "\r?\n", "\r\n") --Percent-encode all non-unreserved characters --as per RFC 3986, Section 2.3 --(except for space, which gets plus-encoded) str = string.gsub (str, "([^%w%-%.%_%~:/])", function (c) return string.format ("%%%02X", string.byte(c)) end) --Convert spaces to plus signs str = string.gsub (str, " ", "+") return str end function split(s, delimiter) local result = {}; for match in (s..delimiter):gmatch("(.-)"..delimiter) do result[#result+1] = match; end return result; end function string.starts(String,Start) return string.sub(String,1,string.len(Start))==Start end function filename(path) local path_elems=split(path,'/') return path_elems[#path_elems] end function process_playlist(playlist) local res={} local rec={} for _,record in pairs(playlist) do if record=="OK" then break end local splitted = split(record,": ") local fieldname = string.lower(splitted[1]) local fieldvalue = splitted[2] rec[fieldname] = fieldvalue if fieldname=="id" then local title = rec['title'] if title then local filtered_title=string.gsub(string.lower(title),'track','') local filtered_title=string.gsub(filtered_title,'[ _.-]','') else filtered_title="" end if not filtered_title or string.len(filtered_title)<4 then if title then rec['title']=title..' - '..filename(rec['file']) else rec['title']=filename(rec['file']) end end res[#res+1] = rec rec={} end end return res end function find_by_id(playlist,id) for key,record in pairs(playlist) do if record['id']==id then return key end end return nil end function process_playlists(playlists) local res={} for _,record in pairs(playlists) do local splitted = split(record,": ") if splitted[1]=="playlist" then res[#res+1] = splitted[2] end end return res end function process_directory(directory) local res={} for _,record in pairs(directory) do local splitted = split(record,": ") if splitted[1]=="directory" or splitted[1]=="file" then local rec={} rec["type"] = splitted[1] rec["name"] = splitted[2] res[#res+1] = rec end end return res end function mpd_new(settings) local client = {} if settings == nil then settings = {} end client.hostname = settings.hostname or "localhost" client.port = settings.port or 6600 client.desc = settings.desc or client.hostname client.password = settings.password client.timeout = settings.timeout or 1 client.retry = settings.retry or 60 return client end function mpd_send(mpd,action,raw) local command = string.format("%s\n", action) local values = {} -- connect to MPD server if not already done. if not mpd.connected then local now = os.time(); if not mpd.last_try or (now - mpd.last_try) > mpd.retry then mpd.socket = socket.tcp() mpd.socket:settimeout(mpd.timeout, 't') mpd.last_try = os.time() mpd.connected = mpd.socket:connect(mpd.hostname, mpd.port) if not mpd.connected then return { errormsg = "could not connect" } end mpd.last_error = nil -- Read the server's hello message local line = mpd.socket:receive("*l") if not line:match("^OK MPD") then -- Invalid hello message? mpd.connected = false return { errormsg = string.format("invalid hello message: %s", line) } else _, _, mpd.version = string.find(line, "^OK MPD ([0-9.]+)") end -- send the password if needed if mpd.password then local rsp = mpd_send(mpd,string.format("password %s", mpd.password)) if rsp.errormsg then return rsp end end else local retry_sec = mpd.retry - (now - mpd.last_try) return { errormsg = string.format("%s (retrying in %d sec)", mpd.last_error, retry_sec) } end end mpd.socket:send(command) local line = ""; err=0 while not line:match("^OK$") do line, err = mpd.socket:receive("*l") if not line then -- closed,timeout (mpd killed?) mpd.last_error = err mpd.connected = false mpd.socket:close() return mpd_send(mpd,action) end if line:match("^ACK") then return { errormsg = line } end if not raw then local pattern = string.format("(%s)", ": ") local i = string.find (line, pattern, 0) if i ~= nil then local key=string.sub(line,1,i-1) local value=string.sub(line,i+2,-1) values[string.lower(key)] = value end else values[#values+1]=line end end return values end if hasuci then x = uci.cursor() settings = {} settings['host'] = x.get("mpd","server","host") or "localhost" settings['port'] = x.get("mpd","server","port") or 6600 settings['timeout'] = x.get("mpd","server","timeout") or 1 volstep = x.get("mpd","control","volume_step") or 3 password = x.get("mpd","server","password") else config="/etc/mpd-lua.json" settings={} local open = io.open file = open(config, "r") if file then content = file:read "*a" file:close() settings=json.decode(content) end settings['host'] = settings['host'] or "localhost" settings['port'] = settings["port"] or 6600 settings['timeout'] = settings["timeout"] or 1 volstep = settings["volstep"] or 3 end if password then settings["password"] = password end m = mpd_new(settings) command = url_decode(os.getenv('QUERY_STRING')) if not command or command=="" then command="idle" end if command=="play" or command=="pause" or command=="stop" then res=mpd_send(m,command) elseif command=="previous" or command=="next" then res=mpd_send(m,"play") res=mpd_send(m,command) elseif command=="idle" then m.timeout=30 res=mpd_send(m,command) elseif command=="vold" then status=mpd_send(m,"status") volume=tonumber(status["volume"]) res=mpd_send(m,"setvol "..(volume-volstep)) elseif command=="volu" then status=mpd_send(m,"status") volume=tonumber(status["volume"]) res=mpd_send(m,"setvol "..(volume+volstep)) elseif string.starts(command,"fastfwd") then cmd=split(command,"|") skip=tonumber(cmd[2]) if not skip then skip=15 end status=mpd_send(m,"status") rec_time=status["time"] song=status["songid"] if song then if rec_time then rec_time=split(rec_time,":") cur_time=tonumber(rec_time[1]) track_time=tonumber(rec_time[2]) if track_time then cur_time=cur_time+skip if cur_time>track_time then cur_time=track_time end end mpd_send(m,"seekid "..song.." "..cur_time) else mpd_send(m,"play") end end res={} elseif string.starts(command,"rewind") then cmd=split(command,"|") skip=tonumber(cmd[2]) if not skip then skip=15 end status=mpd_send(m,"status") rec_time=status["time"] song=status["songid"] if song then if rec_time then rec_time=split(rec_time,":") cur_time=tonumber(rec_time[1]) cur_time=cur_time-skip if cur_time<0 then cur_time=0 end print(song) print(cur_time) mpd_send(m,"seekid "..song.." "..cur_time) else mpd_send(m,"play") mpd_send(m,"previous") end end res={} elseif command=="status" then res=mpd_send(m,"status") song=tonumber(res["songid"]) if song then playlist=mpd_send(m,"playlistid "..song,1) pl=process_playlist(playlist) res['current_playing']=pl[1]['title'] else res['current_playing']="---" end elseif command=="playlist" then playlist=mpd_send(m,"playlistinfo",1) res=process_playlist(playlist) elseif command=="repeat" then status=mpd_send(m,"status") rep=1-status["repeat"] res=mpd_send(m,"repeat "..rep) elseif string.starts(command,"cpl") then cmd=split(command,"|") id=cmd[3] command=cmd[2] if command=="playitem" then command="playid "..id res=mpd_send(m,command) end if command=="clear" then res=mpd_send(m,"clear") end if command=="remove" then command="deleteid "..id res=mpd_send(m,command) end if command=="moveup" then playlist=mpd_send(m,"playlistinfo ",1) pl=process_playlist(playlist) idx=find_by_id(pl,id) if idx>1 then command="swap "..(idx-1).." "..(idx-2) end res=mpd_send(m,command) end if command=="movedown" then playlist=mpd_send(m,"playlistinfo ",1) pl=process_playlist(playlist) idx=find_by_id(pl,id) if idx<#pl then command="swap "..(idx-1).." "..(idx) end res=mpd_send(m,command) end elseif string.starts(command,"lists") then cmd=split(command,"|") command=cmd[2] if command=="load" then if not cmd[3] then lists=mpd_send(m,"listplaylists",1) res=process_playlists(lists) else res=mpd_send(m,"load \""..cmd[3].."\"",1) end end if command=="save" and cmd[3] then res=mpd_send(m,"save \""..cmd[3].."\"",1) end if command=="delete" and cmd[3] then res=mpd_send(m,"rm \""..cmd[3].."\"",1) end if command=="edit" then if cmd[3] then dir=mpd_send(m,"lsinfo \""..cmd[3].."\"",1) else dir=mpd_send(m,"lsinfo",1) end res=process_directory(dir) end if command=="add" then res={} if cmd[3] then res=mpd_send(m,"add \""..cmd[3].."\"") if (res['errormsg']) then path=url_encode(cmd[3]) res=mpd_send(m,"add \""..path.."\"") end end end end if not res then print("Content-Type: text/plain\r\n") print("MPD server - unknown command "..command) elseif (res['errormsg']) then print("Content-Type: text/plain\r\n") print("MPD server connection error: "..res['errormsg']) else print "Content-Type: text/plain\r\n" print(json.encode(res)) end