Basic video plugin help
#1
I'm writing my first plugin and I have a few questions. I'm writing it to connect to my SageTV server and list and play recorded shows. I have the basics working just fine so far, but the reason I want a plugin instead of just sharing the directory is to get more readable file names and include show descriptions. The data source is an xml file generated by SageTV.

How do I add the show description to the plugin screen? I've done some searching and I think I'll need to use the Skin.SetString function along with ListItem.Plot, but it wasn't clear to me how or where I call that.

Also, I'm having a heck of a time parsing the xml to get the data. It appears that xpath is not available, and minidom was giving me fits, any tips on an easier way to extract the info I need from an xml file?

Here's the code I have so far, it works great but is basically doing the exact same thing as sharing the folder with SMB.

Code:
import urllib,urllib2,re,xbmcplugin,xbmcgui

#TV DASH - by You 2008.

def CATEGORIES():
        addDir('SageTV','http://www.aaronblackshear.com/recordings1.xml',2,'')
                      
def INDEX(url):
        req = urllib2.Request(url)
        req.add_header('User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3')
        response = urllib2.urlopen(req)
        link=response.read()
        response.close()
        match=re.compile('filePath="(.+?)"').findall(link)
        for name in match:
                addDir(name.replace('D:\\SageTV\\',''),name.replace('D:\\SageTV\\','smb://sagetv/SageRecordings/'),2,'')

def VIDEOLINKS(url,name):
        req = urllib2.Request(url)
        req.add_header('User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3')
        response = urllib2.urlopen(req)
        link=response.read()
        response.close()
        match=re.compile('filePath="(.+?)"').findall(link)
        for name in match:
          displayname = name.replace('D:\\SageTV\\','')
          displayname = displayname.replace('.ts','')
          addLink(displayname,name.replace('D:\\SageTV\\','smb://sagetv/SageRecordings/'),'')
        

                
def get_params():
        param=[]
        paramstring=sys.argv[2]
        if len(paramstring)>=2:
                params=sys.argv[2]
                cleanedparams=params.replace('?','')
                if (params[len(params)-1]=='/'):
                        params=params[0:len(params)-2]
                pairsofparams=cleanedparams.split('&')
                param={}
                for i in range(len(pairsofparams)):
                        splitparams={}
                        splitparams=pairsofparams[i].split('=')
                        if (len(splitparams))==2:
                                param[splitparams[0]]=splitparams[1]
                                
        return param




def addLink(name,url,iconimage):
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultVideo.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz)
        return ok


def addDir(name,url,mode,iconimage):
        u=sys.argv[0]+"?url="+urllib.quote_plus(url)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name)
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
        return ok
        
              
params=get_params()
url=None
name=None
mode=None

try:
        url=urllib.unquote_plus(params["url"])
except:
        pass
try:
        name=urllib.unquote_plus(params["name"])
except:
        pass
try:
        mode=int(params["mode"])
except:
        pass

print "Mode: "+str(mode)
print "URL: "+str(url)
print "Name: "+str(name)

if mode==None or url==None or len(url)<1:
        print ""
        CATEGORIES()
      
elif mode==1:
        print ""+url
        INDEX(url)
        
elif mode==2:
        print ""+url
        VIDEOLINKS(url,name)



xbmcplugin.endOfDirectory(int(sys.argv[1]))
Reply
#2
I used minidom for my tvnz plugin - didn't have much trouble with it, but I wasn't hardly doing anything complicated.

You could use elementtree as an alternative to parsing the XML.

As for the plot, you want to investigate the setInfo function on the listitem.

Grab the tvnz ondemand plugin (search the forum) as I use that to set the tag line.

Cheers,
Jonathan
Always read the XBMC online-manual, FAQ and search the forum before posting.
Do not e-mail XBMC-Team members directly asking for support. Read/follow the forum rules.
For troubleshooting and bug reporting please make sure you read this first.


Image
Reply
#3
Thanks jmarshall, "plugin x uses that function" was exactly what I was looking for, I'm happy to dig through the code and try to figure it out
Reply
#4
Arron,

I sent you a PM as well.

I've been adapting the JHH code to do this very thing. I've got the entire thing working as a plugin, and I've even got a logo, and I've got settings working properly.

I'm trying to build out additional functionality as to allow for delete, setting of various sage flags, etc.

My main default.py is below.


Code:
icetre@XBMC-FamilyRoom:~/.xbmc/addons/plugin.video.sagetv$ cat default.py
import urllib,urllib2,string,re,sys,socket,os
import xbmc,xbmcplugin,xbmcgui
import xml.dom.minidom

# PLEASE EDIT settings.xml TO CHANGE ANY OTHER SETTINGS #


def INDEX():
        res=[]
        #WebSock = urllib.urlopen(webaddress + '/sage/Recordings') # Opens a 'Socket' to URL
        WebSock = urllib.urlopen(webaddress + '/sage/Search?SearchString=&searchType=TVFiles&search_fields=title&filename=&TimeRange=0&Categories=**Any**&Channels=**Any**&watched=any&dontlike=any&favorite=any&firstruns=any&hdtv=any&archived=any&manrec=any&autodelete=any&partials=none&sort1=airdate_asc&sort2=none&grouping=None&pagelen=inf')
        link = WebSock.read()                                     # Reads Contents of URL and saves to Variable
        WebSock.close()                                           # Closes connection to url

        match = re.compile('epgcell.*?MediaFileId=(\d+)">', re.DOTALL).findall(link)

        for url in match:
                url_txt= url
                addDir(url_txt)


def get_params():
        param=[]
        paramstring=sys.argv[2]
        if len(paramstring)>=2:
                params=sys.argv[2]
                cleanedparams=params.replace('?','')
                if (params[len(params)-1]=='/'):
                        params=params[0:len(params)-2]
                pairsofparams=cleanedparams.split('&')
                param={}
                for i in range(len(pairsofparams)):
                        splitparams={}
                        splitparams=pairsofparams[i].split('=')
                        if (len(splitparams))==2:
                                param[splitparams[0]]=splitparams[1]
                                
        return param

      
def addLink(name,url,iconimage):
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultVideo.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz)
        return ok

def addDir(url):
        u = webaddress + '/sage/DetailedInfo?MediaFileId=' + url
        mode = 2
        WebSock   = urllib.urlopen(u)            # Opens a 'Socket' to URL
        link      = WebSock.read()               # Reads Contents of URL and saves to Variable
        WebSock.close()                          # Closes connection to url
        filematch = re.compile('Files:.*?Segment=0">(.*?)</a>', re.DOTALL).findall(link)[0]
        duration  = re.compile('Duration: (.*?)</p>', re.DOTALL).findall(link)[0]
        channel   = re.compile('Channel:.*?-(.*?)</p>', re.DOTALL).findall(link)[0]
        name      = re.compile('Detailed Information for (.*?)</title>', re.DOTALL).findall(link)[0]
        try:
                namesub = ' - ' + re.compile('<p>Episode: (.*?)</p>', re.DOTALL).findall(link)[0]
        except:
                namesub = ' '
        name = name + namesub
        try:
                description = re.compile('Description:(.*?)</p>', re.DOTALL).findall(link)[0]
        except:
                description = ''
        try:
                icon = 'http://'+ipaddress+':'+port+'/thumbs/'+re.compile('FavoriteId=(\d+)', re.DOTALL).findall(link)[0]+'.jpg'
        except:
                icon = webaddress + '/sage/sagelogo.gif'
        iconimage = icon
        if RecordingPath == [u'']:
                filematch = 'http://'+ipaddress+':'+port+'/sagepublic/MediaFile?MediaFileId='+url+'&Segment=0'
        else:
                ct = 0
                for i in RecordingPath:
                        filematch = filematch.replace(i,XBMCPath[ct])
                        ct = ct + 1
        u = webaddress + '/sage/EditShowInfo?MediaFileId=' + url
        mode = 2
        WebSock   = urllib.urlopen(u)            # Opens a 'Socket' to URL
        link      = WebSock.read()               # Reads Contents of URL and saves to Variable
        WebSock.close()                          # Closes connection to url      
        dates = re.compile('air_start_yr" value="(\d+).*?start_mth.*?"(\d+)" selected.*?air_start_dd" value="(\d+).*?air_start_hh" value="(\d+).*?air_start_mm" value="(\d+)', re.DOTALL).findall(link)[0]
        year = (int(dates[0]) - 2000) * 372 * 24 + int(dates[1]) * 31 * 24 + int(dates[2]) * 24 + int(dates[3])
        date = dates[0] +'-'+ dates[1] +'-'+ dates[2]
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="video", infoLabels={ "Title": name, "Plot": description, "Rating": 4.5, "Size": year, "Genre": 'genre', "Duration": duration, "Date": date, "Writer": 'writer', "Tagline": 'tagline', "Plotoutline": description, "Studio": channel, "Year": int(dates[0])})
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=filematch,listitem=liz,isFolder=False)
        return ok

url=None
name=None

dirHome   = os.getcwd()
dirHome   = dirHome.replace(";","")
dirHome   = dirHome +'\\'

ipaddress = xbmcplugin.getSetting( int( sys.argv[ 1 ] ), "Ipaddress" )
port = xbmcplugin.getSetting( int( sys.argv[ 1 ] ), 'Port' )
userid = xbmcplugin.getSetting( int( sys.argv[ 1 ] ), 'UserId' )
password = xbmcplugin.getSetting( int( sys.argv[ 1 ] ), 'Password' )

s = xbmcplugin.getSetting( int( sys.argv[ 1 ] ), 'RecordingPath' )
RecordingPath = s.split(',')
s = xbmcplugin.getSetting( int( sys.argv[ 1 ] ), 'XBMCPath' )
XBMCPath = s.split(',')

webaddress = "http://" + userid + ":" + password + "@" + ipaddress + ":" + port

INDEX()
xbmcplugin.endOfDirectory(int(sys.argv[1]))


As for resources/settings.xml
Code:
<settings>
   <setting id="update" type="bool" label="30020" default="true"/>
   <setting type="sep"/>
   <setting id="Ipaddress" type="text" label="IP Address" default="0.0.0.0"/>
   <setting id="Port" type="text" label="Port" default="8080"/>
   <setting type="sep"/>
   <setting id="UserId" type="text" label="User ID" default="sage"/>
   <setting id="Password" type="text" label="Password" default="frey"/>
   <setting type="sep"/>
   <setting id="RecodringPath" type="text" label="Recording Path" default=""/>
   <setting id="XBMCPath" type="text" label="XBMC Path" default=""/>
</settings>


I'd love to talk with you more about perhaps combining efforts, or our goals in this script.

Adam
Reply
#5
Ok, I've got all the basic functionality working. The plugin reads the xml generated by SageTV, lists the files and plays them. But the display is just a file list, it doesn't show episode info, etc. I can get the episode info from the xml and store it, so it comes up if I hit info on a show, but I'd like to get the display to use the view in the TV Show library.

I've been searching all night and it seems I need to use setContent to tell XBMC and the skin how to display the content, but I'm not sure where I should call that. I've been experimenting with putting it in the addDir and addLink functions, but to no avail.

Here's my code: (messy and probably crappy, with tons of comments since I was learning python on the fly)


Code:
import urllib,urllib2,re
import xbmc,xbmcplugin,xbmcgui
from xml.dom.minidom import parse

#TV DASH - by You 2008.

def CATEGORIES():
        addDir('SageTV','http://www.aaronblackshear.com/recordings.xml',2,'')

def VIDEOLINKS(url,name):
        #Videolinks gets called immediately after adddir, so the timeline is categories, adddir, and then videolinks
        #Videolinks then calls addlink in a loop
        # To do in this function: set it up to use the xml from Sage instead of the html, then i can parse the xml to get showname, plot, path ,etc much quicker
        print 'this is when videolinks gets called'
        print url
        print name
        #This code parses the xml link
        req = urllib.urlopen(url)
        content = parse(req)    
        # Writing some code here to test xml parsing
        for showlist in content.getElementsByTagName('show'):
          strTitle = ''
          strEpisode = ''
          strDescription = ''
          #print 'showlist: '+showlist[0]
          for shownode in showlist.childNodes:
            #print 'shownode: '+shownode.nodeName
            #print shownode[0]
            if shownode.nodeName == 'title':
              strTitle = shownode.toxml()
              strTitle = strTitle.replace('<title>','')
              strTitle = strTitle.replace('</title>','')
              strTitle = strTitle.replace('&amp;','&')
              print 'showtitle: '+strTitle
            if shownode.nodeName == 'episode':
              strEpisode = shownode.toxml()
              strEpisode = strEpisode.replace('<episode>','')
              strEpisode = strEpisode.replace('</episode>','')
              strEpisode = strEpisode.replace('&amp;','&')
              print 'episode: '+strEpisode
              #print len(strEpisode)
            if shownode.nodeName == 'description':
              strDescription = shownode.toxml()
              strDescription = strDescription.replace('<description>','')
              strDescription = strDescription.replace('</description>','')
              strDescription = strDescription.replace('&amp;','&')
              print 'description: '+strDescription
              # now that we have the title, episode and description, create a showname string depending on which ones you have
              # if there is no episode name use the description in the title
            if len(strEpisode) == 0:
              strShowname = strTitle+' - '+strDescription
              strPlot = strDescription
              # else if there is an episode use that
            elif len(strEpisode) > 0:
              strShowname = strTitle+' - '+strEpisode
              strPlot = strDescription
              print strShowname
            if shownode.nodeName == 'airing':
              for shownode1 in shownode.childNodes:
                if shownode1.nodeName == 'mediafile':
                  for shownode2 in shownode1.childNodes:
                    if shownode2.nodeName == 'segmentList':
                      shownode3 =  shownode2.childNodes[1]
                      print shownode3.getAttribute('filePath')
                      strFilepath = shownode3.getAttribute('filePath')
                      addLink(strShowname,strFilepath.replace('D:\\SageTV\\','smb://sagetv/SageRecordings/'),strPlot,'')
                
def get_params():
        param=[]
        paramstring=sys.argv[2]
        if len(paramstring)>=2:
                params=sys.argv[2]
                cleanedparams=params.replace('?','')
                if (params[len(params)-1]=='/'):
                        params=params[0:len(params)-2]
                pairsofparams=cleanedparams.split('&')
                param={}
                for i in range(len(pairsofparams)):
                        splitparams={}
                        splitparams=pairsofparams[i].split('=')
                        if (len(splitparams))==2:
                                param[splitparams[0]]=splitparams[1]
                                
        return param

def addLink(name,url,plot,iconimage):
        print 'This is when addlink gets called'
        print name
        print url
        print plot
        print iconimage
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultVideo.png", thumbnailImage=iconimage)
        #liz.setContent(int(sys.argv[1]),'tvshows')
        liz.setInfo( type="Video", infoLabels={ "Title": name, "Plot": plot} )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=False)
        return ok


def addDir(name,url,mode,iconimage):
        print sys.argv[0]
        # This line makes the plugin call the main default.py with three arguments, the url, the mode, and the name
        # Since it calls it with arguments, when the get_params function runs first it creates an array with the arguments in it, so that url, name, and mode get assigned in the try section
        # Then since those are assigned and now mode = 2 it calls videolinks with url and name as arguments, which then lists all the files
        u=sys.argv[0]+"?url="+urllib.quote_plus(url)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name)
        print "u: "+u
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
        return ok
        
              
params=get_params()
url=None
name=None
mode=None

try:
        url=urllib.unquote_plus(params["url"])
except:
        pass
try:
        name=urllib.unquote_plus(params["name"])
except:
        pass
try:
        mode=int(params["mode"])
except:
        pass

print "Mode: "+str(mode)
print "URL: "+str(url)
print "Name: "+str(name)

if mode==None or url==None or len(url)<1:
        print ""
        CATEGORIES()
      
elif mode==1:
        print ""+url
        INDEX(url)
        
elif mode==2:
        print ""+url
        VIDEOLINKS(url,name)


xbmcplugin.endOfDirectory(int(sys.argv[1]))
Reply
#6
Basically you just need to call setContent before you call endDirectory in the appropriate listing.
Always read the XBMC online-manual, FAQ and search the forum before posting.
Do not e-mail XBMC-Team members directly asking for support. Read/follow the forum rules.
For troubleshooting and bug reporting please make sure you read this first.


Image
Reply
#7
jmarshall Wrote:Basically you just need to call setContent before you call endDirectory in the appropriate listing.

I tried it in a bunch of different places and it still wasn't working, but I figured out the problem. I am coding/testing this with a fresh Dharma install on my main windows machine, and I did not have a library since I hadn't added any sources. I added a folder with a TV show in it, scanned, and now it shows me the episode view for the add-on. So apparently the library view types are not available to an add-on unless you have a library setup already.

This is a gotcha that might trip people up in the future, hopefully they can find this post if they're searching.
Reply
#8
Hi

Did either of you get any further with this? I am looking at trying to do the exact same thing. I would love to start where you left off.

Thanks
Greg
Reply
#9
Given the recent death of SageTV I am sure there are a lot of people that would be interested in this.
Reply
#10
gveres Wrote:Hi

Did either of you get any further with this? I am looking at trying to do the exact same thing. I would love to start where you left off.

Thanks
Greg

BradVido88 wrote an awesome program for connecting SageTV and XBMC, check the Supplemental Tools forum. My add-on above works just fine too, but it does not integrate SageTV recordings into the XBMC library. For me that was desired. It also requires somewhat manual installation, anyone with the skills to do so is welcome to make it into a full-blown add-on in the repository.
Reply

Logout Mark Read Team Forum Stats Members Help
Basic video plugin help0