3 local hasuci,uci = pcall(require,"uci")
8 function url_decode(str)
9 if not str then return nil end
10 str = string.gsub (str, "+", " ")
11 str = string.gsub (str, "%%(%x%x)", function(h) return
12 string.char(tonumber(h,16)) end)
13 str = string.gsub (str, "\r\n", "\n")
17 function url_encode(str)
19 --Ensure all newlines are in CRLF form
20 str = string.gsub (str, "\r?\n", "\r\n")
22 --Percent-encode all non-unreserved characters
23 --as per RFC 3986, Section 2.3
24 --(except for space, which gets plus-encoded)
25 str = string.gsub (str, "([^%w%-%.%_%~:/])",
26 function (c) return string.format ("%%%02X", string.byte(c)) end)
28 --Convert spaces to plus signs
29 str = string.gsub (str, " ", "+")
34 function split(s, delimiter)
36 for match in (s..delimiter):gmatch("(.-)"..delimiter) do
37 result[#result+1] = match;
42 function string.starts(String,Start)
43 return string.sub(String,1,string.len(Start))==Start
46 function filename(path)
47 local path_elems=split(path,'/')
48 return path_elems[#path_elems]
51 function process_playlist(playlist)
54 for _,record in pairs(playlist) do
58 local splitted = split(record,": ")
59 local fieldname = string.lower(splitted[1])
60 local fieldvalue = splitted[2]
61 rec[fieldname] = fieldvalue
62 if fieldname=="id" then
63 local title = rec['title']
65 local filtered_title=string.gsub(string.lower(title),'track','')
66 local filtered_title=string.gsub(filtered_title,'[ _.-]','')
70 if not filtered_title or string.len(filtered_title)<4 then
72 rec['title']=title..' - '..filename(rec['file'])
74 rec['title']=filename(rec['file'])
84 function find_by_id(playlist,id)
85 for key,record in pairs(playlist) do
86 if record['id']==id then
93 function process_playlists(playlists)
95 for _,record in pairs(playlists) do
96 local splitted = split(record,": ")
97 if splitted[1]=="playlist" then
98 res[#res+1] = splitted[2]
104 function process_directory(directory)
106 for _,record in pairs(directory) do
107 local splitted = split(record,": ")
108 if splitted[1]=="directory" or splitted[1]=="file" then
110 rec["type"] = splitted[1]
111 rec["name"] = splitted[2]
118 function mpd_new(settings)
121 if settings == nil then settings = {} end
123 client.hostname = settings.hostname or "localhost"
124 client.port = settings.port or 6600
125 client.desc = settings.desc or client.hostname
126 client.password = settings.password
127 client.timeout = settings.timeout or 1
128 client.retry = settings.retry or 60
133 function mpd_send(mpd,action,raw)
135 local command = string.format("%s\n", action)
138 -- connect to MPD server if not already done.
139 if not mpd.connected then
140 local now = os.time();
141 if not mpd.last_try or (now - mpd.last_try) > mpd.retry then
142 mpd.socket = socket.tcp()
143 mpd.socket:settimeout(mpd.timeout, 't')
144 mpd.last_try = os.time()
145 mpd.connected = mpd.socket:connect(mpd.hostname, mpd.port)
146 if not mpd.connected then
147 return { errormsg = "could not connect" }
151 -- Read the server's hello message
152 local line = mpd.socket:receive("*l")
153 if not line:match("^OK MPD") then -- Invalid hello message?
154 mpd.connected = false
155 return { errormsg = string.format("invalid hello message: %s", line) }
157 _, _, mpd.version = string.find(line, "^OK MPD ([0-9.]+)")
160 -- send the password if needed
162 local rsp = mpd_send(mpd,string.format("password %s", mpd.password))
168 local retry_sec = mpd.retry - (now - mpd.last_try)
169 return { errormsg = string.format("%s (retrying in %d sec)", mpd.last_error, retry_sec) }
173 mpd.socket:send(command)
175 local line = ""; err=0
176 while not line:match("^OK$") do
177 line, err = mpd.socket:receive("*l")
178 if not line then -- closed,timeout (mpd killed?)
180 mpd.connected = false
182 return mpd_send(mpd,action)
185 if line:match("^ACK") then
186 return { errormsg = line }
191 local pattern = string.format("(%s)", ": ")
192 local i = string.find (line, pattern, 0)
195 local key=string.sub(line,1,i-1)
196 local value=string.sub(line,i+2,-1)
197 values[string.lower(key)] = value
202 values[#values+1]=line
217 settings['host'] = x.get("mpd","server","host") or "localhost"
218 settings['port'] = x.get("mpd","server","port") or 6600
219 settings['timeout'] = x.get("mpd","server","timeout") or 1
221 volstep = x.get("mpd","control","volume_step") or 3
223 password = x.get("mpd","server","password")
227 config="/etc/mpd-lua.json"
231 file = open(config, "r")
233 content = file:read "*a"
235 settings=json.decode(content)
238 settings['host'] = settings['host'] or "localhost"
239 settings['port'] = settings["port"] or 6600
240 settings['timeout'] = settings["timeout"] or 1
242 volstep = settings["volstep"] or 3
247 settings["password"] = password
250 m = mpd_new(settings)
252 command = url_decode(os.getenv('QUERY_STRING'))
254 if not command or command=="" then
258 if command=="play" or command=="pause" or command=="stop" then
260 res=mpd_send(m,command)
262 elseif command=="previous" or command=="next" then
264 res=mpd_send(m,"play")
265 res=mpd_send(m,command)
267 elseif command=="idle" then
270 res=mpd_send(m,command)
272 elseif command=="vold" then
274 status=mpd_send(m,"status")
275 volume=tonumber(status["volume"])
276 res=mpd_send(m,"setvol "..(volume-volstep))
278 elseif command=="volu" then
280 status=mpd_send(m,"status")
281 volume=tonumber(status["volume"])
282 res=mpd_send(m,"setvol "..(volume+volstep))
284 elseif string.starts(command,"fastfwd") then
286 cmd=split(command,"|")
287 skip=tonumber(cmd[2])
292 status=mpd_send(m,"status")
293 rec_time=status["time"]
294 song=status["songid"]
299 rec_time=split(rec_time,":")
300 cur_time=tonumber(rec_time[1])
302 track_time=tonumber(rec_time[2])
304 cur_time=cur_time+skip
305 if cur_time>track_time then
309 mpd_send(m,"seekid "..song.." "..cur_time)
321 elseif string.starts(command,"rewind") then
323 cmd=split(command,"|")
324 skip=tonumber(cmd[2])
329 status=mpd_send(m,"status")
330 rec_time=status["time"]
331 song=status["songid"]
336 rec_time=split(rec_time,":")
337 cur_time=tonumber(rec_time[1])
338 cur_time=cur_time-skip
345 mpd_send(m,"seekid "..song.." "..cur_time)
350 mpd_send(m,"previous")
358 elseif command=="status" then
360 res=mpd_send(m,"status")
361 song=tonumber(res["songid"])
363 playlist=mpd_send(m,"playlistid "..song,1)
364 pl=process_playlist(playlist)
365 res['current_playing']=pl[1]['title']
367 res['current_playing']="---"
370 elseif command=="playlist" then
372 playlist=mpd_send(m,"playlistinfo",1)
373 res=process_playlist(playlist)
375 elseif command=="repeat" then
377 status=mpd_send(m,"status")
378 rep=1-status["repeat"]
379 res=mpd_send(m,"repeat "..rep)
381 elseif string.starts(command,"cpl") then
383 cmd=split(command,"|")
387 if command=="playitem" then
388 command="playid "..id
389 res=mpd_send(m,command)
392 if command=="clear" then
393 res=mpd_send(m,"clear")
396 if command=="remove" then
397 command="deleteid "..id
398 res=mpd_send(m,command)
401 if command=="moveup" then
402 playlist=mpd_send(m,"playlistinfo ",1)
403 pl=process_playlist(playlist)
404 idx=find_by_id(pl,id)
406 command="swap "..(idx-1).." "..(idx-2)
408 res=mpd_send(m,command)
411 if command=="movedown" then
412 playlist=mpd_send(m,"playlistinfo ",1)
413 pl=process_playlist(playlist)
414 idx=find_by_id(pl,id)
416 command="swap "..(idx-1).." "..(idx)
418 res=mpd_send(m,command)
421 elseif string.starts(command,"lists") then
423 cmd=split(command,"|")
426 if command=="load" then
428 lists=mpd_send(m,"listplaylists",1)
429 res=process_playlists(lists)
431 res=mpd_send(m,"load \""..cmd[3].."\"",1)
435 if command=="save" and cmd[3] then
436 res=mpd_send(m,"save \""..cmd[3].."\"",1)
439 if command=="delete" and cmd[3] then
440 res=mpd_send(m,"rm \""..cmd[3].."\"",1)
443 if command=="edit" then
445 dir=mpd_send(m,"lsinfo \""..cmd[3].."\"",1)
447 dir=mpd_send(m,"lsinfo",1)
449 res=process_directory(dir)
452 if command=="add" then
455 res=mpd_send(m,"add \""..cmd[3].."\"")
456 if (res['errormsg']) then
457 path=url_encode(cmd[3])
458 res=mpd_send(m,"add \""..path.."\"")
466 print("Content-Type: text/plain\r\n")
467 print("MPD server - unknown command "..command)
468 elseif (res['errormsg']) then
469 print("Content-Type: text/plain\r\n")
470 print("MPD server connection error: "..res['errormsg'])
472 print "Content-Type: text/plain\r\n"
473 print(json.encode(res))