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
215 settings['host'] = x.get("mpd","server","host") or "localhost"
216 settings['port'] = x.get("mpd","server","port") or 6600
217 settings['timeout'] = x.get("mpd","server","timeout") or 1
219 volstep = x.get("mpd","control","volume_step") or 3
221 password = x.get("mpd","server","password")
225 config="/etc/mpd-lua.json"
229 file = open(config, "r")
231 content = file:read "*a"
233 settings=json.decode(content)
236 settings['host'] = settings['host'] or "localhost"
237 settings['port'] = settings["port"] or 6600
238 settings['timeout'] = settings["timeout"] or 1
240 volstep = settings["volstep"] or 3
245 settings["password"] = password
248 m = mpd_new(settings)
250 command = url_decode(os.getenv('QUERY_STRING'))
252 if not command or command=="" then
256 if command=="play" or command=="pause" or command=="stop" then
258 res=mpd_send(m,command)
260 elseif command=="previous" or command=="next" then
262 res=mpd_send(m,"play")
263 res=mpd_send(m,command)
265 elseif command=="idle" then
268 res=mpd_send(m,command)
270 elseif command=="vold" then
272 status=mpd_send(m,"status")
273 volume=tonumber(status["volume"])
274 res=mpd_send(m,"setvol "..(volume-volstep))
276 elseif command=="volu" then
278 status=mpd_send(m,"status")
279 volume=tonumber(status["volume"])
280 res=mpd_send(m,"setvol "..(volume+volstep))
282 elseif string.starts(command,"fastfwd") then
284 cmd=split(command,"|")
285 skip=tonumber(cmd[2])
290 status=mpd_send(m,"status")
291 rec_time=status["time"]
292 song=status["songid"]
297 rec_time=split(rec_time,":")
298 cur_time=tonumber(rec_time[1])
300 track_time=tonumber(rec_time[2])
302 cur_time=cur_time+skip
303 if cur_time>track_time then
307 mpd_send(m,"seekid "..song.." "..cur_time)
319 elseif string.starts(command,"rewind") then
321 cmd=split(command,"|")
322 skip=tonumber(cmd[2])
327 status=mpd_send(m,"status")
328 rec_time=status["time"]
329 song=status["songid"]
334 rec_time=split(rec_time,":")
335 cur_time=tonumber(rec_time[1])
336 cur_time=cur_time-skip
343 mpd_send(m,"seekid "..song.." "..cur_time)
348 mpd_send(m,"previous")
356 elseif command=="status" then
358 res=mpd_send(m,"status")
359 song=tonumber(res["songid"])
361 playlist=mpd_send(m,"playlistid "..song,1)
362 pl=process_playlist(playlist)
363 res['current_playing']=pl[1]['title']
365 res['current_playing']="---"
368 elseif command=="playlist" then
370 playlist=mpd_send(m,"playlistinfo",1)
371 res=process_playlist(playlist)
373 elseif command=="repeat" then
375 status=mpd_send(m,"status")
376 rep=1-status["repeat"]
377 res=mpd_send(m,"repeat "..rep)
379 elseif string.starts(command,"cpl") then
381 cmd=split(command,"|")
385 if command=="playitem" then
386 command="playid "..id
387 res=mpd_send(m,command)
390 if command=="clear" then
391 res=mpd_send(m,"clear")
394 if command=="remove" then
395 command="deleteid "..id
396 res=mpd_send(m,command)
399 if command=="moveup" then
400 playlist=mpd_send(m,"playlistinfo ",1)
401 pl=process_playlist(playlist)
402 idx=find_by_id(pl,id)
404 command="swap "..(idx-1).." "..(idx-2)
406 res=mpd_send(m,command)
409 if command=="movedown" then
410 playlist=mpd_send(m,"playlistinfo ",1)
411 pl=process_playlist(playlist)
412 idx=find_by_id(pl,id)
414 command="swap "..(idx-1).." "..(idx)
416 res=mpd_send(m,command)
419 elseif string.starts(command,"lists") then
421 cmd=split(command,"|")
424 if command=="load" then
426 lists=mpd_send(m,"listplaylists",1)
427 res=process_playlists(lists)
429 res=mpd_send(m,"load \""..cmd[3].."\"",1)
433 if command=="save" and cmd[3] then
434 res=mpd_send(m,"save \""..cmd[3].."\"",1)
437 if command=="delete" and cmd[3] then
438 res=mpd_send(m,"rm \""..cmd[3].."\"",1)
441 if command=="edit" then
443 dir=mpd_send(m,"lsinfo \""..cmd[3].."\"",1)
445 dir=mpd_send(m,"lsinfo",1)
447 res=process_directory(dir)
450 if command=="add" then
453 res=mpd_send(m,"add \""..cmd[3].."\"")
454 if (res['errormsg']) then
455 path=url_encode(cmd[3])
456 res=mpd_send(m,"add \""..path.."\"")
464 print("Content-Type: text/plain\r\n")
465 print("MPD server - unknown command "..command)
466 elseif (res['errormsg']) then
467 print("Content-Type: text/plain\r\n")
468 print("MPD server connection error: "..res['errormsg'])
470 print "Content-Type: text/plain\r\n"
471 print(json.encode(res))