mirror of
				https://github.com/DigitalDevices/octonet.git
				synced 2023-10-10 11:36:52 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			422 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			422 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
 | 
						|
 | 
						|
local ContentDirectory = {}
 | 
						|
 | 
						|
local db = require("DataBase")
 | 
						|
 | 
						|
-- local dlnaprofile = 'DLNA.ORG_PN=MPEG_TS;DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0D100000000000000000000000000000'
 | 
						|
local dlnaprofile = 'DLNA.ORG_PN=MPEG_TS;DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=8D100000000000000000000000000000'
 | 
						|
local dlnaschema = ' xmlns:dlna="urn:schemas-dlna-org:metadata-1-0"'
 | 
						|
 | 
						|
if DisableDLNA then
 | 
						|
  dlnaprofile = '*'
 | 
						|
  dlnaschema = ''
 | 
						|
end
 | 
						|
 | 
						|
local Schema = 'xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1"'
 | 
						|
 | 
						|
local DIDLStart = '' -- '<?xml version="1.0" encoding="utf-8"?>'
 | 
						|
                ..'<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/"'
 | 
						|
                ..' xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"'
 | 
						|
                ..' xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"'
 | 
						|
                -- .. dlnaschema
 | 
						|
                ..'>'
 | 
						|
local DIDLEnd = '</DIDL-Lite>'
 | 
						|
 | 
						|
-- State variables
 | 
						|
local SystemUpdateID = 36
 | 
						|
-- ---------------
 | 
						|
----------------------------------------------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
local AllFolders = {}
 | 
						|
local RootFolders = {}
 | 
						|
local Folders = {}
 | 
						|
local AllItems = {}
 | 
						|
 | 
						|
local i,f,vi,ai
 | 
						|
 | 
						|
----- Hierarchical Folders
 | 
						|
 | 
						|
RootFolders[#RootFolders+1] = { title = "All", id = "64" }
 | 
						|
RootFolders[#RootFolders+1] = { title = "Audio", id = "1" }
 | 
						|
RootFolders[#RootFolders+1] = { title = "Images", id = "3" }
 | 
						|
RootFolders[#RootFolders+1] = { title = "Video", id = "2" }
 | 
						|
for _,f in ipairs(RootFolders) do
 | 
						|
  f.VideoItems = {}
 | 
						|
  f.AudioItems = {}
 | 
						|
  f.ChildFolders = {}
 | 
						|
  AllFolders[f.id] = f
 | 
						|
end
 | 
						|
AllFolders["2"].ChildFolders = Folders
 | 
						|
AllFolders["64"].ChildFolders = Folders
 | 
						|
 | 
						|
----
 | 
						|
 | 
						|
for _,f in ipairs(db.SourceList) do
 | 
						|
  f.id = f.refid
 | 
						|
  f.VideoItems = {}
 | 
						|
  f.AudioItems = {}
 | 
						|
  f.ChildFolders = {}
 | 
						|
  table.insert(Folders,f)
 | 
						|
--  table.insert(RootFolders,f)
 | 
						|
  AllFolders[f.id] = f
 | 
						|
end
 | 
						|
 | 
						|
for _,vi in ipairs(db.ChannelList) do
 | 
						|
  f = AllFolders[vi.refid]
 | 
						|
  vi.id = f.id.."$"..tostring(#f.VideoItems)
 | 
						|
  vi.src = f.src
 | 
						|
  vi.parentID = f.id
 | 
						|
  vi.request = string.gsub(vi.request,'&','&amp;')
 | 
						|
  vi.title = string.gsub(vi.title,'&','&amp;')
 | 
						|
  vi.title = string.gsub(vi.title,'<','&lt;')
 | 
						|
  vi.title = string.gsub(vi.title,'>','&gt;')
 | 
						|
  table.insert(f.VideoItems,vi)
 | 
						|
  AllItems[vi.id] = vi
 | 
						|
end
 | 
						|
 | 
						|
----- Add Stream Folder
 | 
						|
 | 
						|
f = {}
 | 
						|
f.id = "STRM"
 | 
						|
f.title = "Current Streams"
 | 
						|
f.VideoItems = {}
 | 
						|
f.AudioItems = {}
 | 
						|
f.ChildFolders = {}
 | 
						|
table.insert(Folders,f)
 | 
						|
AllFolders[f.id] = f
 | 
						|
for i = 1,4,1 do
 | 
						|
  vi = { id = f.id.."$"..tostring(i-1), parentID = f.id, title = "Stream "..tostring(i), stream = tostring(i) }
 | 
						|
  table.insert(f.VideoItems,vi)
 | 
						|
  AllItems[vi.id] = vi  
 | 
						|
end
 | 
						|
 | 
						|
----------------------------------------------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
local function Folder(title,id,parentid,childCount)
 | 
						|
  local F = '<container id="'..id..'" parentID="'..parentid..'"'
 | 
						|
        -- ..' childCount="'..childCount..'"'
 | 
						|
        ..' restricted="1"'
 | 
						|
        -- ..' searchable="1"'
 | 
						|
        ..'>'
 | 
						|
        ..'<dc:title>'..title..'</dc:title>'
 | 
						|
        ..'<upnp:class>object.container.storageFolder</upnp:class>'
 | 
						|
        ..'</container>'
 | 
						|
  return F
 | 
						|
end
 | 
						|
 | 
						|
local function VideoItem(Host,Item,nCompat)
 | 
						|
  local rtspreq = ''
 | 
						|
  if Item.stream then
 | 
						|
    rtspreq = 'stream='..Item.stream
 | 
						|
  else
 | 
						|
    -- Some clients don't like a long request url, or an url with '&' in it
 | 
						|
    -- Fail them for now
 | 
						|
    -- if nCompat then 
 | 
						|
      -- rtspreq = "stream_99"
 | 
						|
    -- elseif Item.src then 
 | 
						|
    if Item.src then 
 | 
						|
      rtspreq = '?src='..Item.src..'&amp;'..Item.request
 | 
						|
    else
 | 
						|
      rtspreq = '?'..Item.request
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  local didl = '<item id="'..Item.id..'" parentID="'..Item.parentID
 | 
						|
  didl = didl .. '" restricted="1">'
 | 
						|
              ..'<dc:title>'..Item.title..'</dc:title>'
 | 
						|
              ..'<upnp:class>object.item.videoItem.videoBroadcast</upnp:class>'    
 | 
						|
  if Item.channelNr then
 | 
						|
    didl = didl ..'<upnp:channelNr>'..Item.channelNr..'</upnp:channelNr>'
 | 
						|
  end
 | 
						|
  didl = didl ..'<res' 
 | 
						|
              ..' protocolInfo="rtsp-rtp-udp:*:video/mpeg:'..dlnaprofile..'">'
 | 
						|
              ..'rtsp://'..Host..':554/'..rtspreq
 | 
						|
              ..'</res>'
 | 
						|
  didl = didl ..'</item>'
 | 
						|
  return didl
 | 
						|
  
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
local function BrowseChildren(client,Host,Request,nCompat)
 | 
						|
  local ObjectID = UPnP:GetRequestParam(Request,"ObjectID")
 | 
						|
  local BrowseFlag = UPnP:GetRequestParam(Request,"BrowseFlag")
 | 
						|
  local Filter = UPnP:GetRequestParam(Request,"Filter")
 | 
						|
  local StartingIndex = tonumber(UPnP:GetRequestParam(Request,"StartingIndex"))
 | 
						|
  local RequestedCount = tonumber(UPnP:GetRequestParam(Request,"RequestedCount"))
 | 
						|
  print("BrowseChildren",ObjectID,Filter,StartingIndex,RequestedCount)
 | 
						|
 | 
						|
  local didl = DIDLStart;
 | 
						|
  local Error = 0
 | 
						|
  local NumberReturned = 0
 | 
						|
  local TotalMatches = 0
 | 
						|
  local UpdateID = SystemUpdateID
 | 
						|
  local f,vi,ai
 | 
						|
  
 | 
						|
  if ObjectID == "0" then
 | 
						|
    if nCompat then       
 | 
						|
      for _,f in ipairs(RootFolders) do
 | 
						|
        didl = didl..Folder(f.title,f.id,ObjectID,tostring(#f.VideoItems + #f.AudioItems + #f.ChildFolders))
 | 
						|
        NumberReturned = NumberReturned + 1
 | 
						|
        TotalMatches = TotalMatches +1
 | 
						|
      end
 | 
						|
    else
 | 
						|
      for _,f in ipairs(Folders) do
 | 
						|
        didl = didl..Folder(f.title,f.id,ObjectID,tostring(#f.VideoItems + #f.AudioItems + #f.ChildFolders))
 | 
						|
        NumberReturned = NumberReturned + 1
 | 
						|
        TotalMatches = TotalMatches +1
 | 
						|
      end
 | 
						|
    end
 | 
						|
    
 | 
						|
  else
 | 
						|
    local f = AllFolders[ObjectID]
 | 
						|
    if f then
 | 
						|
        
 | 
						|
      local Index = 0
 | 
						|
      for i,cf in ipairs(f.ChildFolders) do
 | 
						|
        if Index >= StartingIndex and (RequestedCount == 0 or NumberReturned < RequestedCount) then
 | 
						|
          didl = didl..Folder(cf.title,cf.id,ObjectID,tostring(#cf.VideoItems + #cf.AudioItems + #cf.ChildFolders))
 | 
						|
          NumberReturned = NumberReturned + 1
 | 
						|
        end
 | 
						|
        Index = Index + 1
 | 
						|
        TotalMatches = TotalMatches +1
 | 
						|
      end
 | 
						|
              
 | 
						|
      for i,vi in ipairs(f.VideoItems) do
 | 
						|
        if Index >= StartingIndex and (RequestedCount == 0 or NumberReturned < RequestedCount) then
 | 
						|
          didl = didl..VideoItem(Host,vi,nCompat)
 | 
						|
          NumberReturned = NumberReturned + 1
 | 
						|
        end
 | 
						|
        Index = Index + 1
 | 
						|
        TotalMatches = TotalMatches +1
 | 
						|
      end
 | 
						|
    
 | 
						|
    else
 | 
						|
      Error = 710
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  didl = didl..DIDLEnd
 | 
						|
  print("Returned",StartingIndex,NumberReturned,TotalMatches,Error)
 | 
						|
  
 | 
						|
  if Error == 0 then 
 | 
						|
    local Args = { { n = "Result", v = didl }, { n = "NumberReturned", v = tostring(NumberReturned)},
 | 
						|
                   { n = "TotalMatches", v = tostring(TotalMatches)}, { n = "UpdateID", v = tostring(UpdateID) } }
 | 
						|
    UPnP:SendResponse(client,UPnP:CreateResponse(Schema,"Browse",Args))
 | 
						|
  else
 | 
						|
    UPnP:SendSoapError(client,Error)
 | 
						|
  end
 | 
						|
  
 | 
						|
  return
 | 
						|
end
 | 
						|
 | 
						|
local function BrowseMetaData(client,Host,Request,nCompat)
 | 
						|
  local ObjectID = UPnP:GetRequestParam(Request,"ObjectID")
 | 
						|
  local didl = DIDLStart;
 | 
						|
  local UpdateID = SystemUpdateID
 | 
						|
  local Error = 0
 | 
						|
 | 
						|
  if ObjectID == "0" then
 | 
						|
    local ChildCount = #Folders
 | 
						|
    if nCompat then ChildCount = #RootFolders end
 | 
						|
    didl = didl .. '<container id="1" parentID = "-1" childCount="'..tostring(ChildCount)..'" restricted="true">>'
 | 
						|
                .. '<:dc:title>OctopusNet</dc:title>'
 | 
						|
                ..'<upnp:class>object.container.storageFolder</upnp:class>'
 | 
						|
                ..'</container>'
 | 
						|
  else
 | 
						|
    local f = AllFolders[ObjectID]
 | 
						|
    if f then    
 | 
						|
      didl = didl..Folder(f.title,f.id,ObjectID,tostring(#f.VideoItems + #f.AudioItems + #f.ChildFolders))
 | 
						|
    else
 | 
						|
      local item = AllItems[ObjectID]
 | 
						|
      if item then
 | 
						|
        didl = didl..VideoItem(Host,item,nCompat)      
 | 
						|
      else
 | 
						|
        Error = 710
 | 
						|
      end 
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  didl = didl..DIDLEnd
 | 
						|
  
 | 
						|
  if Error == 0 then 
 | 
						|
    local Args = { { n = "Result", v = didl }, { n = "NumberReturned", v = "1"},
 | 
						|
                   { n = "TotalMatches", v = "1"}, { n = "UpdateID", v = tostring(UpdateID) } }
 | 
						|
    UPnP:SendResponse(client,UPnP:CreateResponse(Schema,"Browse",Args))
 | 
						|
  else
 | 
						|
    UPnP:SendSoapError(client,Error)
 | 
						|
  end
 | 
						|
 | 
						|
  didl = DIDLEnd;  
 | 
						|
end
 | 
						|
 | 
						|
local function Search(client,Host,Request,nCompat)
 | 
						|
  local ContainerID = UPnP:GetRequestParam(Request,"ContainerID")
 | 
						|
  local SearchCriteria = UPnP:GetRequestParam(Request,"SearchCriteria")
 | 
						|
  local Filter = UPnP:GetRequestParam(Request,"Filter")
 | 
						|
  local StartingIndex = tonumber(UPnP:GetRequestParam(Request,"StartingIndex"))
 | 
						|
  local RequestedCount = tonumber(UPnP:GetRequestParam(Request,"RequestedCount"))
 | 
						|
  local SortCriteria = UPnP:GetRequestParam(Request,"SortCriteria")
 | 
						|
  print(SearchCriteria,ContainerID,Filter,StartingIndex,RequestedCount,SortCriteria)
 | 
						|
 | 
						|
  local didl = DIDLStart;
 | 
						|
  local Error = 0
 | 
						|
  local NumberReturned = 0
 | 
						|
  local TotalMatches = 0
 | 
						|
  local UpdateID = SystemUpdateID
 | 
						|
  
 | 
						|
  if string.match(SearchCriteria,"videoItem") then
 | 
						|
  
 | 
						|
    if ContainerID == "0" then
 | 
						|
      if not nCompat or nCompat ~= "WMP" then
 | 
						|
        local Index = 0
 | 
						|
        for _,vi in pairs(AllItems) do
 | 
						|
          if Index >= StartingIndex and (RequestedCount == 0 or NumberReturned < RequestedCount) then
 | 
						|
            didl = didl..VideoItem(Host,vi,nCompat)
 | 
						|
            NumberReturned = NumberReturned + 1
 | 
						|
          end
 | 
						|
          Index = Index + 1
 | 
						|
          TotalMatches = TotalMatches + 1
 | 
						|
          -- if nCompat and TotalMatches > 19 then break end
 | 
						|
        end
 | 
						|
      end
 | 
						|
      
 | 
						|
    else
 | 
						|
      local f = AllFolders[ContainerID]
 | 
						|
      if f then
 | 
						|
        local src = f.src
 | 
						|
        if src then src = "src="..src.."&"  else src = "" end
 | 
						|
        
 | 
						|
        local Index = 0
 | 
						|
        for _,vi in ipairs(f.VideoItems) do
 | 
						|
          if Index >= StartingIndex and (RequestedCount == 0 or NumberReturned < RequestedCount) then
 | 
						|
            didl = didl..VideoItem(Host,vi,nCompat)
 | 
						|
            NumberReturned = NumberReturned + 1
 | 
						|
          end
 | 
						|
          Index = Index + 1
 | 
						|
          TotalMatches = TotalMatches +1
 | 
						|
        end
 | 
						|
      else
 | 
						|
        Error = 710
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  didl = didl..DIDLEnd
 | 
						|
  -- didl = TestDidl  
 | 
						|
    -- NumberReturned = 1
 | 
						|
    -- TotalMatches = 1
 | 
						|
  print("Returned",StartingIndex,NumberReturned,TotalMatches,Error)
 | 
						|
 | 
						|
    
 | 
						|
  if Error == 0 then 
 | 
						|
    local Args = { { n = "Result", v = didl }, { n = "NumberReturned", v = tostring(NumberReturned)},
 | 
						|
                   { n = "TotalMatches", v = tostring(TotalMatches)}, { n = "UpdateID", v = tostring(UpdateID) } }
 | 
						|
    UPnP:SendResponse(client,UPnP:CreateResponse(Schema,"Search",Args))
 | 
						|
  else
 | 
						|
    UPnP:SendSoapError(client,Error)
 | 
						|
  end
 | 
						|
  
 | 
						|
  return
 | 
						|
end
 | 
						|
 | 
						|
local function SendResult(client,Action,VarName,Result)
 | 
						|
  local Args = { { n = VarName, v = Result } }
 | 
						|
  UPnP:SendResponse(client,UPnP:CreateResponse(Schema,Action,Args))
 | 
						|
end
 | 
						|
 | 
						|
function ContentDirectory:Invoke(client,Attributes,Request)
 | 
						|
  local Action = string.match(Attributes["SOAPACTION"],".+%#([%a%d%-_]+)")
 | 
						|
  local Host = tostring(Attributes["host"])
 | 
						|
  
 | 
						|
  local Compability = nil
 | 
						|
  if Attributes["USER-AGENT"] then
 | 
						|
    if string.match(Attributes["USER-AGENT"],"Windows%-Media%-Player") then Compability = "WMP"
 | 
						|
    elseif string.match(Attributes["USER-AGENT"],"IPI%/1%.0") then Compability = "IPI" 
 | 
						|
    end
 | 
						|
  end
 | 
						|
  print("Compability=",Compability)
 | 
						|
  
 | 
						|
  print(Host,"ContentDirectory",Action)
 | 
						|
  if Action == "Browse" then 
 | 
						|
    local BrowseFlag = tostring(UPnP:GetRequestParam(Request,"BrowseFlag"))
 | 
						|
    if BrowseFlag == "BrowseDirectChildren" then
 | 
						|
      BrowseChildren(client,Host,Request,Compability) 
 | 
						|
    elseif BrowseFlag == "BrowseMetadata" then
 | 
						|
      BrowseMetaData(client,Host,Request,Compability) 
 | 
						|
    else
 | 
						|
      UPnP:SendSoapError(client,710)
 | 
						|
    end
 | 
						|
  elseif Action == "Search" then 
 | 
						|
    Search(client,Host,Request,Compability) 
 | 
						|
  -- elseif Action == "X_GetRemoteSharingStatus" then
 | 
						|
    -- SendResult(client,Action,"0")    
 | 
						|
  elseif Action == "GetSortCapabilities" then
 | 
						|
    SendResult(client,Action,"SortCaps","dc:title,upnp:class,upnp:originalTrackNumber")    
 | 
						|
  elseif Action == "GetSearchCapabilities" then
 | 
						|
    SendResult(client,Action,"SearchCaps","dc:title")    
 | 
						|
  elseif Action == "GetSystemUpdateID" then
 | 
						|
    SendResult(client,Action,"Id","1")    
 | 
						|
  else
 | 
						|
    UPnP:SendSoapError(client,401)
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
function ContentDirectory:Subscribe(client,callback,timeout)
 | 
						|
  local r = "HTTP/1.1 200 OK\r\n"
 | 
						|
          .. 'Content-Type: text/xml; charset="utf-8"\r\n'
 | 
						|
          .. "Server: "..UPnP.Server.."\r\n"
 | 
						|
          .. "SID: uuid:50c95800-e839-4b96-b7ae-779d989e1399\r\n"
 | 
						|
          .. "Timeout: Second-1800\r\n"
 | 
						|
          .. "Content-Length: 0\r\n"
 | 
						|
          .. "Connection: close\r\n"
 | 
						|
          .. "EXT:\r\n"
 | 
						|
          .. "\r\n"
 | 
						|
  client:send(r)  
 | 
						|
  
 | 
						|
  local ipaddr,port = client:getpeername()  
 | 
						|
  local Args = { { n = "TransferIDs", v = "" }, { n = "SystemUpdateID", v = tostring(SystemUpdateID) } }
 | 
						|
  UPnP:SendEvent(callback,"50c95800-e839-4b96-b7ae-779d989e1399",0,Args)
 | 
						|
  
 | 
						|
end
 | 
						|
 | 
						|
function ContentDirectory:Renew(client,sid,timeout)
 | 
						|
  local r = "HTTP/1.1 200 OK\r\n"
 | 
						|
          .. 'Content-Type: text/xml; charset="utf-8"\r\n'
 | 
						|
          .. "Server: "..UPnP.Server.."\r\n"
 | 
						|
          .. "SID: uuid:50c95800-e839-4b96-b7ae-779d989e1399\r\n"
 | 
						|
          .. "Timeout: Second-1800\r\n"
 | 
						|
          .. "Content-Length: 0\r\n"
 | 
						|
          .. "Connection: close\r\n"
 | 
						|
          .. "EXT:\r\n"
 | 
						|
          .. "\r\n"
 | 
						|
  client:send(r)  
 | 
						|
end
 | 
						|
 | 
						|
function ContentDirectory:Unsubscribe(client,sid)
 | 
						|
  local r = "HTTP/1.1 200 OK\r\n"
 | 
						|
          .. 'Content-Type: text/xml; charset="utf-8"\r\n'
 | 
						|
          .. "Server: "..UPnP.Server.."\r\n"
 | 
						|
          .. "Content-Length: 0\r\n"
 | 
						|
          .. "Connection: close\r\n"
 | 
						|
          .. "EXT:\r\n"
 | 
						|
          .. "\r\n"
 | 
						|
  client:send(r)  
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
function ContentDirectory:Description()
 | 
						|
  t = ""
 | 
						|
  local f = io.open("ContentDirectory.xml","r")
 | 
						|
  if not f then os.exit() end
 | 
						|
  while true do
 | 
						|
    local line = f:read()
 | 
						|
    if not line then break end
 | 
						|
    t = t .. line .. "\r\n"  
 | 
						|
  end
 | 
						|
  f:close()
 | 
						|
  return t
 | 
						|
end
 | 
						|
 | 
						|
return ContentDirectory |