Багфиксы. Обработка особенностей поведения mopidy и mpd. Обработка пустых и малознача...
[mpd-lua.git] / mpd.lua
diff --git a/mpd.lua b/mpd.lua
index 0200a8a37cfa92c3e6ccb314a7410048734d77fe..f421b755ba9d0aa817d2dc5ce4f4e721d0f71bbc 100755 (executable)
--- a/mpd.lua
+++ b/mpd.lua
@@ -1,5 +1,10 @@
 #!/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, "+", " ")
@@ -9,6 +14,23 @@ function url_decode(str)
   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
@@ -21,25 +43,53 @@ 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 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
+    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
@@ -65,11 +115,9 @@ function process_directory(directory)
   return res
 end
 
-require "uci"
-require("socket")
-
 function mpd_new(settings)
     local client = {}
+
     if settings == nil then settings = {} end
 
     client.hostname = settings.hostname or "localhost"
@@ -82,7 +130,6 @@ function mpd_new(settings)
     return client
 end
 
-
 function mpd_send(mpd,action,raw)
 
     local command = string.format("%s\n", action)
@@ -160,14 +207,42 @@ function mpd_send(mpd,action,raw)
     return values
 end
 
+hasuci = false
 
-json=require("json")
+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
 
-x = uci.cursor()
-settings = {}
-settings['host'] = x.get("mpd","server","host") or "localhost"
-settings['port'] = x.get("mpd","server","port") or 6600
-password = x.get("mpd","server","password")
+end
+  
 if password then
   settings["password"] = password
 end
@@ -177,76 +252,194 @@ m = mpd_new(settings)
 command = url_decode(os.getenv('QUERY_STRING'))
 
 if not command or command=="" then
-  command="status"
+  command="idle"
 end
 
-if command=="play" or command=="pause" or command=="stop" or command=="previous" or command=="next" then
+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-3))
+  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+3))
+  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=res["song"]
-  playlist=mpd_send(m,"playlist",1)
-  pl=process_playlist(playlist)
+  song=tonumber(res["songid"])
   if song then 
-    res['current_playing']=pl[song]['name']
-  else
-    res['current_playing']="No songs selected"
+    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,"playlist",1)
+
+  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="play "..id
+    command="playid "..id
     res=mpd_send(m,command)
   end
+
   if command=="clear" then
     res=mpd_send(m,"clear")
   end
+
   if command=="remove" then
-    command="delete "..id
+    command="deleteid "..id
     res=mpd_send(m,command)
   end
+
   if command=="moveup" then
-    command="swap "..id.." "..(id-1)
+    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
-    command="swap "..id.." "..(id+1)
+    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)
+      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)
+    res=mpd_send(m,"save \""..cmd[3].."\"",1)
   end
+
   if command=="delete" and cmd[3] then
-    res=mpd_send(m,"rm "..cmd[3],1)
+    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)
@@ -255,20 +448,26 @@ elseif string.starts(command,"lists") then
     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['error_msg']) then
+elseif (res['errormsg']) then
   print("Content-Type: text/plain\r\n")
-  print("MPD server connection error: "..res['error_msg'])
+  print("MPD server connection error: "..res['errormsg'])
 else
   print "Content-Type: text/plain\r\n"
   print(json.encode(res))