Windows MediaBrowser metadata importer for MythTV

From MythTV Official Wiki
Jump to: navigation, search

List-add.png Todo: Script needs to be updated to use Python bindings for database credentials.

I have never much been a fan of MythTV's metadata structure which by default stores all the metadata in a separate location to the media itself - in my mind media and its metadata ought to live together, especially when as in my case I keep a lot of my media on external HDs which are usually powered down unless I need them - which has obvious problems for MythTV by default because most of the media collection will appear and disappear over time.

While there are home brewed solutions to this for MythTV, I have to admit that I prefer how Windows Media Center + the MediaBrowser plugin for it handle media in general. MythTV has the superior TV and DVR solution over Win7MCE no doubt, but MythTV simply isn't very good with large semi-online media collections (though MythVideo is MUCH better than MythMusic). MythTV has a particularly poor solution to automatically fetching metadata when tools such as Media Center Master provide a lovely GUI tool which does almost everything automatically and only requires human intervention when it can't figure out a title heuristically. Generally speaking you manually run MCM once a week or so and it'll fill in all the gaps automatically even for brand new shows - a great tool. And when it comes to you filing away the online HD media onto the offline HD system, you simply move stuff over and all its metadata goes with it.

For a while now I have had a Win7MCE machine and a totally separate MythTV machine at opposite ends of the house. However, being lazy I have found myself increasingly watching media on the MythTV machine via a Samba share over a 802.11n wireless connection and what annoys me there is that MythTV won't recognise the MediaBrowser metadata already in the media collection. Last night my temper snapped, so I spent 2am-7am writing the metadata importer below. I hope that you find it useful!

The Importer Script

The following script is a bit of Python cobbled together quickly with the intention of being able to drop it into /etc/cron.daily/ and leave it be. It obviously only knows about new media when you manually scan for it in MythVideo (BTW this sucks - why can't MythVideo automatically discover new media on its own instead of having to have the user manually instruct it to do so?), so after you add a new item you'll need to scan for it and then run this script or wait for the cron job to run it for you.

The script removes the metadata XML and hidden directories from the database once processed. This has the advantage of considerably tidying up the MythVideo interface by removing lots of useless entries. Unfortunately when you next rescan all these entries will reappear until the next time the script is run :(

Note that MythTV v0.22 or later is required. It doesn't support the new metadata of v0.23 but could be very easily extended to do so (it DOES work as-is on v0.23 though!).

Updated on 11th April 2010 to correctly set fanart. Looks gorgeous with 0.23 Arclight theme!

#!/usr/bin/python
import MySQLdb, sys, os, re
from xml.etree.ElementTree import ElementTree

# read DB connection parameters
fp = open("/etc/mythtv/mysql.txt")
DB = {}
for line in fp:
  if "=" in line:
    k,v = line.strip().split("=",1)
    DB[k] = v

required_keys = ["DBHostName", "DBUserName", "DBName", "DBPassword"]
for k in required_keys:
  if k not in DB:
    raise Exception("Missing MySQL connection detail: %s" % k)

# connect to database
con = MySQLdb.connect(DB["DBHostName"], DB["DBUserName"], DB["DBPassword"], DB["DBName"], use_unicode=True, charset="utf8")
crs = con.cursor()

# get all files without covers
sql = "SELECT intid, filename FROM videometadata WHERE LCASE(filename) LIKE '%.xml';"
crs.execute(sql)
videos = {}
while True:
  data = crs.fetchone()
  if not data: break
  intid, filename = data
  path, leaf = os.path.split(filename)
  xmlfile = os.path.join(path, "mymovies.xml")
  isSeries = False
  if not os.path.exists(xmlfile):
    xmlfile = os.path.join(path, leaf)
    isSeries = True
  if os.path.exists(xmlfile):
    if not xmlfile in videos:
      print "Found",xmlfile
      metadata = ElementTree()
      metadata.parse(xmlfile)
      #metadata.write(sys.stdout)
      root = metadata.getroot()
      if path.endswith("/Episodes") or path.endswith("/Movies"):
        videos[xmlfile] = (intid, isSeries, None, None)
        continue

      if isSeries:
        if leaf=="series.xml":
          videos[xmlfile] = (intid, isSeries, None, None)
          continue        
        # For Episodes
        #title = root.findtext("SeriesName")
        #subtitle = root.findtext("EpisodeName")
        director = ''
        try: director = root.findtext("Director")
        except: pass
        plot = root.findtext("Overview")
        rating = 0
        try: rating = root.findtext("Rating")
        except: pass
        inetref = root.findtext("EpisodeID")
        year = ''
        try: year = root.findtext("FirstAired")
        except: pass
        #userrating = ''
        #length = 0
        #season = int(root.findtext("SeasonNumber"))
        #episode = int(root.findtext("EpisodeNumber"))
        coverfile = ''
        try:
          if os.path.exists(os.path.dirname(xmlfile)+root.findtext("filename")):
            coverfile = os.path.dirname(xmlfile)+root.findtext("filename")
        except: pass
        #category = 0
        #trailer = ''
        #screenshot = ''
        #banner = ''
        fanart = os.path.join(os.path.dirname(os.path.dirname(path)), "backdrop.jpg")
        if not os.path.exists(fanart):
            fanart = os.path.join(os.path.dirname(os.path.dirname(path)), "backdrop1.jpg")
            if not os.path.exists(fanart): fanart = ''
        videofile = os.path.join(os.path.dirname(path), leaf[:leaf.rfind('.')+1]+'%')
        sql = "UPDATE videometadata SET director=%s, plot=%s, rating=%s, inetref=%s, year=%s, coverfile=%s, fanart=%s WHERE filename LIKE %s;"
        params = (director, plot, rating, inetref, year, coverfile, fanart, videofile)
        #print      sql % params
        videos[xmlfile] = (intid, isSeries, sql, params)
      else:
        if "[exception:" in root.findtext("Description"):
          videos[xmlfile] = (intid, isSeries, None, None)
          continue
        # For movies
        #title = root.findtext("LocalTitle")
        #subtitle = ''
        director = ''
        for person in root.find("Persons").findall("Person"):
          if person.findtext("Type") == "Director":
            director = person.findtext("Name")
            break
        plot = root.findtext("Description")
        rating = float(root.findtext("IMDBrating"))
        inetref = root.findtext("TMDbId")
        year = 0
        try: year = int(root.findtext("ProductionYear"))
        except: pass
        #userrating = ''
        length = 0
        try: length = int(root.findtext("RunningTime"))
        except: pass
        #season = ''
        #episode = ''
        coverfile = os.path.join(path, "folder.jpg")
        if os.path.exists(coverfile): coverfile = ''
        #category = 0
        #trailer = ''
        #screenshot = ''
        #banner = ''
        fanart = os.path.join(path, "backdrop.jpg")
        if not os.path.exists(fanart):
            fanart = os.path.join(path, "backdrop1.jpg")
            if not os.path.exists(fanart): fanart = ''
        sql = "UPDATE videometadata SET director=%s, plot=%s, rating=%s, inetref=%s, year=%s, length=%s, coverfile=%s, fanart=%s WHERE MID(filename, 1, %s)=%s;"
        params = (director, plot, "%.1f" % rating, inetref, "%d" % year, "%d" % length, coverfile, fanart, str(len(path)+1), path+os.sep)
        #print      sql % params
        videos[xmlfile] = (intid, isSeries, sql, params)


print "\nDeleting xml files ..."
for (intid, isSeries, sql, params) in videos.values():
  if sql is not None:
    crs.execute(sql, params)
  sql = "DELETE FROM videometadata WHERE intid=%s;"
  #crs.execute(sql, intid)

crs.execute("DELETE FROM videometadata WHERE filename LIKE '%/metadata/%';");

I hope that you find this script useful.

--Ned14 18:55, 7 March 2010 (UTC)