#!/usr/bin/lua 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 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 process_playlist(playlist) local res={} for _,record in pairs(playlist) do local splitted = split(record,": ") local name = splitted[2] local splitted = split(splitted[1],":") local id = splitted[1] local rectype = splitted[2] local rec = {} if not (id == "OK") then rec["id"] = id rec["type"] = rectype rec["name"] = name res[id] = rec end end return res 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 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") 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["song"] if song then if rec_time then rec_time=split(rec_time,":") cur_time=tonumber(rec_time[1]) track_time=tonumber(rec_time[2]) cur_time=cur_time+skip if cur_time>track_time then cur_time=track_time end mpd_send(m,"seek "..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["song"] if song then if rec_time then rec_time=split(rec_time,":") cur_time=tonumber(rec_time[1]) track_time=tonumber(rec_time[2]) cur_time=cur_time-skip if cur_time<0 then cur_time=0 end mpd_send(m,"seek "..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=res["song"] playlist=mpd_send(m,"playlist",1) pl=process_playlist(playlist) if song then res['current_playing']=pl[song]['name'] else res['song']="--" res['current_playing']="---" end elseif command=="playlist" then playlist=mpd_send(m,"playlist",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="play "..id res=mpd_send(m,command) end if command=="clear" then res=mpd_send(m,"clear") end if command=="remove" then command="delete "..id res=mpd_send(m,command) end if command=="moveup" then command="swap "..id.." "..(id-1) res=mpd_send(m,command) end if command=="movedown" then command="swap "..id.." "..(id+1) 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].."\"") end end end if not res then print("Content-Type: text/plain\r\n") print("MPD server - unknown command "..command) elseif (res['error_msg']) then print("Content-Type: text/plain\r\n") print("MPD server connection error: "..res['error_msg']) else print "Content-Type: text/plain\r\n" print(json.encode(res)) end