# 0.1.0 Initial development
# 0.2.0 Inital beta release
# 0.3.0 Add mythvideo metadata updating including movie graphics through
# 0.1.0 Initial development
# 0.2.0 Inital beta release
# 0.3.0 Add mythvideo metadata updating including movie graphics through
# 0.7.2 Fixed a bug where an inetref field was not properly initialized and caused an abort. Ticket #8243
# 0.7.3 Fixed a bug where a user selected TMDB# was not being used.
# Minor change to fuzzy matching of a file named parsed title with those from TMDB and TVDB.
# 0.7.2 Fixed a bug where an inetref field was not properly initialized and caused an abort. Ticket #8243
# 0.7.3 Fixed a bug where a user selected TMDB# was not being used.
# Minor change to fuzzy matching of a file named parsed title with those from TMDB and TVDB.
+ # 0.7.4 Update for changes in Python bindings
+ # 0.7.5 Added the TMDB MovieRating as videometadata table "rating" field
+ # 0.7.6 Modifications to support MythTV python bindings changes
+ # 0.7.7 Pull hostname from python bindings instead of socket libraries
+ # Added support of unicode characters within a jamu.conf file
+ # Replace 'xml' module version check with generic Python version, to correct failure in Python 2.7
+ # 0.7.8 Fixed a bug which caused jamu to crash due to an extra unicode conversion introduced in 0.7.7.
+ # See also #9637.
import sys, os, re, locale, subprocess, locale, ConfigParser, urllib, codecs, shutil, datetime, fnmatch, string
from datetime import date
from optparse import OptionParser
import sys, os, re, locale, subprocess, locale, ConfigParser, urllib, codecs, shutil, datetime, fnmatch, string
from datetime import date
from optparse import OptionParser
sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
'''If the MythTV python interface is found, we can insert data directly to MythDB or
get the directories to store poster, fanart, banner and episode graphics.
'''
'''If the MythTV python interface is found, we can insert data directly to MythDB or
get the directories to store poster, fanart, banner and episode graphics.
'''
- from MythTV import MythDB, DBData, Video, MythVideo, MythBE, FileOps, MythError, MythLog
+ from MythTV import MythDB, Video, MythVideo, MythBE, MythError, MythLog, RecordedProgram
+ from MythTV.database import DBData
- print u"\n! Warning - Creating an instance caused an error for one of: MythDBConn or MythVideo, error(%s)\n" % e
+ print u"\n! Warning - Creating an instance caused an error for one of: MythDB or MythVideo, error(%s)\n" % e
+ localhostname = mythdb.gethostname()
try:
MythLog._setlevel('none') # Some non option -M cannot have any logging on stdout
mythbeconn = MythBE(backend=localhostname, db=mythdb)
try:
MythLog._setlevel('none') # Some non option -M cannot have any logging on stdout
mythbeconn = MythBE(backend=localhostname, db=mythdb)
- table = 'videotypes'
- where = 'intid=%s'
- setwheredat = 'self.intid,'
- logmodule = 'Python VideoType'
- @staticmethod
- def getAll(db=None):
- db = MythDB(db)
- c = db.cursor()
- c.execute("""SELECT * FROM videotypes""")
- types = []
- for row in c.fetchall():
- types.append(VideoTypes(db=db, raw=row))
- c.close()
- return types
+ _table = 'videotypes'
+ _where = 'intid=%s'
+ _setwheredat = 'self.intid,'
+ _logmodule = 'Python VideoType'
- def __init__(self, id=None, ext=None, db=None, raw=None):
- if raw is not None:
- DBData.__init__(self, db=db, raw=raw)
- elif id is not None:
+ def __init__(self, id=None, ext=None, db=None):
+ if id is not None:
DBData.__init__(self, data=(id,), db=db)
elif ext is not None:
DBData.__init__(self, data=(id,), db=db)
elif ext is not None:
- self.__dict__['where'] = 'extension=%s'
- self.__dict__['wheredat'] = 'self.extension,'
+ self.__dict__['_where'] = 'extension=%s'
+ self.__dict__['_wheredat'] = 'self.extension,'
DBData.__init__(self, data=(ext,), db=db)
DBData.__init__(self, data=(ext,), db=db)
- if records:
- for record in records:
- # Only include Video, coverfile, banner, fanart, screenshot and trailers storage groups
- if record.groupname in storagegroupnames.keys():
- dirname = record.dirname
- try:
- dirname = unicode(record.dirname, 'utf8')
- except (UnicodeDecodeError):
- sys.stderr.write(u"\n! Error: The local Storage group (%s) directory contained\ncharacters that caused a UnicodeDecodeError. This storage group has been rejected.'\n" % (record['groupname']))
- continue # Skip any line that has non-utf8 characters in it
- except (UnicodeEncodeError, TypeError):
- pass
- # Strip the trailing slash so it is consistent with all other directory paths in Jamu
- if dirname[-1:] == u'/':
- storagegroups[storagegroupnames[record.groupname]].append(dirname[:-1])
- else:
- storagegroups[storagegroupnames[record.groupname]].append(dirname)
- continue
+ for record in records:
+ # Only include Video, coverfile, banner, fanart, screenshot and trailers storage groups
+ if record.groupname in storagegroupnames.keys():
+ dirname = record.dirname
+ try:
+ dirname = unicode(record.dirname, 'utf8')
+ except (UnicodeDecodeError):
+ sys.stderr.write(u"\n! Error: The local Storage group (%s) directory contained\ncharacters that caused a UnicodeDecodeError. This storage group has been rejected.'\n" % (record['groupname']))
+ continue # Skip any line that has non-utf8 characters in it
+ except (UnicodeEncodeError, TypeError):
+ pass
+ # Strip the trailing slash so it is consistent with all other directory paths in Jamu
+ if dirname[-1:] == u'/':
+ storagegroups[storagegroupnames[record.groupname]].append(dirname[:-1])
+ else:
+ storagegroups[storagegroupnames[record.groupname]].append(dirname)
+ continue
# regex strings to parse folder names for TV series title, season and episode numbers
self.config['fullname_parse_season_episode_translation'] = {u'slash': u'\\', u'season': u'Season', u'episode': u'Episode'}
self.config['fullname_parse_regex'] = [
# regex strings to parse folder names for TV series title, season and episode numbers
self.config['fullname_parse_season_episode_translation'] = {u'slash': u'\\', u'season': u'Season', u'episode': u'Episode'}
self.config['fullname_parse_regex'] = [
# Title/Season 1/s01e01 Subtitle
u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
u'''(?P<seasno>[0-9]+)/[Ss][0-9]+[Ee](?P<epno>[0-9]+).+$''',
# Title/Season 1/s01e01 Subtitle
u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
u'''(?P<seasno>[0-9]+)/[Ss][0-9]+[Ee](?P<epno>[0-9]+).+$''',
-# u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
-# u'''(?P<seasno>[0-9]+)/(?:(?P=seasno))[Xx](?P<epno>[0-9]+).+$''',
- # Title [xx]/Season 1/1x01 Subtitle
- u'''^.+?/(?P<seriesname>[^/]+?)(?:\[.*\])*/%(season)s%(slash)s '''+
- u'''(?P<seasno>[0-9]+)/(?:(?P=seasno))[Xx](?P<epno>[0-9]+).+$''',
+ u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
+ u'''[0-9]+/(?P<seasno>[0-9]+)[Xx](?P<epno>[0-9]+).+$''',
+ # Title/Season 1/01 Subtitle
+ u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
+ u'''(?P<seasno>[0-9]+)/(?P<epno>[0-9]+).+$''',
# Title/Season 1/Title s01e01 Subtitle
u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
u'''(?P<seasno>[0-9]+)/(?:(?P=seriesname))%(slash)s [Ss][0-9]+'''+
# Title/Season 1/Title s01e01 Subtitle
u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
u'''(?P<seasno>[0-9]+)/(?:(?P=seriesname))%(slash)s [Ss][0-9]+'''+
for section in cfg.sections():
if section[:5] == 'File ':
self.config['config_file'] = section[5:]
for section in cfg.sections():
if section[:5] == 'File ':
self.config['config_file'] = section[5:]
- self.config['name_parse'].append(re.compile(unicode(cfg.get(section, option), 'utf8'), re.UNICODE))
+ self.config['name_parse'].append(re.compile(cfg.get(section, option), re.UNICODE))
continue
if section == 'ignore-directory':
# Video directories to be excluded from Jamu processing
for option in cfg.options(section):
continue
if section == 'ignore-directory':
# Video directories to be excluded from Jamu processing
for option in cfg.options(section):
- if records:
- for record in records:
- # Remove any extentions that are in Jamu's list but the user wants ignore
- if record.f_ignore:
- if record.extension in self.config['video_file_exts']:
- self.config['video_file_exts'].remove(record.extension)
- if record.extension.lower() in self.config['video_file_exts']:
- self.config['video_file_exts'].remove(record.extension.lower())
- else: # Add extentions that are not in the Jamu list
- if not record.extension in self.config['video_file_exts']:
- self.config['video_file_exts'].append(record.extension)
+ for record in records:
+ # Remove any extentions that are in Jamu's list but the user wants ignore
+ if record.f_ignore:
+ if record.extension in self.config['video_file_exts']:
+ self.config['video_file_exts'].remove(record.extension)
+ if record.extension.lower() in self.config['video_file_exts']:
+ self.config['video_file_exts'].remove(record.extension.lower())
+ else: # Add extentions that are not in the Jamu list
+ if not record.extension in self.config['video_file_exts']:
+ self.config['video_file_exts'].append(record.extension)
# Make sure that all video file extensions are lower case
for index in range(len(self.config['video_file_exts'])):
self.config['video_file_exts'][index] = self.config['video_file_exts'][index].lower()
# Make sure that all video file extensions are lower case
for index in range(len(self.config['video_file_exts'])):
self.config['video_file_exts'][index] = self.config['video_file_exts'][index].lower()
#remove ._- characters from name (- removed only if next to end of line)
seriesname = re.sub("[\._]|\-(?=$)", " ", seriesname).strip()
#remove ._- characters from name (- removed only if next to end of line)
seriesname = re.sub("[\._]|\-(?=$)", " ", seriesname).strip()
-
- # RAMSI
- seriesname = re.sub("(?:\[.*\])+$", " ", seriesname).strip()
-
+ # ramsi remove [en] tags
+ seriesname = re.sub("(?:\[.*\])+", " ", seriesname).strip()
+
seasno, epno = int(seasno), int(epno)
if self.config['series_name_override']:
seasno, epno = int(seasno), int(epno)
if self.config['series_name_override']:
movie = movie.replace(self.config['dvd'], '')
categories+=u', DVD'
movie = re.sub("[\._]|\-(?=$)", " ", movie).strip()
movie = movie.replace(self.config['dvd'], '')
categories+=u', DVD'
movie = re.sub("[\._]|\-(?=$)", " ", movie).strip()
movie = movie.replace(self.config['dvd'], '')
categories+=u', DVD'
movie = re.sub("[\._]|\-(?=$)", " ", movie).strip()
movie = movie.replace(self.config['dvd'], '')
categories+=u', DVD'
movie = re.sub("[\._]|\-(?=$)", " ", movie).strip()
filename = self.rtnRelativePath(name, u'mythvideo')
# Use the MythVideo hashing protocol when the video is in a storage groups
if filename[0] != u'/':
filename = self.rtnRelativePath(name, u'mythvideo')
# Use the MythVideo hashing protocol when the video is in a storage groups
if filename[0] != u'/':
if meta_dict.has_key('rating'):
if meta_dict['rating'] == '':
meta_dict['rating'] = 'Unknown'
if meta_dict.has_key('rating'):
if meta_dict['rating'] == '':
meta_dict['rating'] = 'Unknown'
sys.stdout.write(u"Simulation Mythdb update for old file:\n(%s) new:\n(%s)\n" % (video_file, tmp_filename))
else:
self._displayMessage(u"Mythdb update for old file:\n(%s) new:\n(%s)\n" % (video_file, tmp_filename))
sys.stdout.write(u"Simulation Mythdb update for old file:\n(%s) new:\n(%s)\n" % (video_file, tmp_filename))
else:
self._displayMessage(u"Mythdb update for old file:\n(%s) new:\n(%s)\n" % (video_file, tmp_filename))
if self.config['simulation']:
sys.stdout.write(u"Simulation Mythdb update for renamed file(%s)\n" % (tmp_filename))
else:
self._displayMessage(u"Mythdb update for renamed file(%s)\n" % (tmp_filename))
if self.config['simulation']:
sys.stdout.write(u"Simulation Mythdb update for renamed file(%s)\n" % (tmp_filename))
else:
self._displayMessage(u"Mythdb update for renamed file(%s)\n" % (tmp_filename))
else:
if self.config['simulation']:
sys.stdout.write(u"Simulation Mythdb add for renamed file(%s)\n" % (tmp_filename))
else:
if self.config['simulation']:
sys.stdout.write(u"Simulation Mythdb add for renamed file(%s)\n" % (tmp_filename))
if self.config['video_dir']:
if not mythvideo.getVideo(exactfile=meta_dict[u'filename'], host=meta_dict[u'host']):
missing_list.append(cfile)
if self.config['video_dir']:
if not mythvideo.getVideo(exactfile=meta_dict[u'filename'], host=meta_dict[u'host']):
missing_list.append(cfile)
if vidrec[u'host'] != u'' and vidrec[u'host'] != None:
if vidrec[u'host'].lower() != localhostname.lower():
continue
if vidrec[u'host'] != u'' and vidrec[u'host'] != None:
if vidrec[u'host'].lower() != localhostname.lower():
continue
# end updateMiroVideo()
def _getScheduledRecordedProgramList(self):
'''Find all Scheduled and Recorded programs
return array of found programs, if none then empty array is returned
'''
# end updateMiroVideo()
def _getScheduledRecordedProgramList(self):
'''Find all Scheduled and Recorded programs
return array of found programs, if none then empty array is returned
'''
- for recordedProgram in recordedlist:
- try:
- recordedRecord = recordedProgram.getRecorded()
- except MythError, e:
- sys.stderr.write(u"\n! Error: Getting recorded table record: %s\n" % e.args[0])
- return programs
+ for recordedRecord in recordedlist:
- if not recordedDetails.subtitle:
- recordedprogram[recordedDetails.title]= u'%d' % recordedDetails.airdate
+ if not recordedDetails['subtitle']:
+ recordedprogram[recordedDetails['title']]= u'%d' % recordedDetails['airdate']
sys.stdout.write(u"\n\nEntry exists in MythDB but category is 0 and year is 1895 (default values).\nUpdating (%s).\n" % cfile['filename'])
filename = self.rtnRelativePath(videopath, u'mythvideo')
if filename[0] == u'/':
sys.stdout.write(u"\n\nEntry exists in MythDB but category is 0 and year is 1895 (default values).\nUpdating (%s).\n" % cfile['filename'])
filename = self.rtnRelativePath(videopath, u'mythvideo')
if filename[0] == u'/':
num_mythdb_updates+=1
videos_updated_metadata.append(cfile['filename'])
self._displayMessage(u"\nReference number (%s) added for (%s) \n" % (inetref, cfile['filename']))
num_mythdb_updates+=1
videos_updated_metadata.append(cfile['filename'])
self._displayMessage(u"\nReference number (%s) added for (%s) \n" % (inetref, cfile['filename']))
# Only update the reference number and title
if self.config['mythtv_ref_num'] or inetref == '99999999':
if inetref == u'99999999':
# Only update the reference number and title
if self.config['mythtv_ref_num'] or inetref == '99999999':
if inetref == u'99999999':
num_mythdb_updates+=1
videos_updated_metadata.append(cfile['filename'])
self._displayMessage(u"\nReference number (%s) added for (%s) \n" % (inetref, cfile['filename']))
num_mythdb_updates+=1
videos_updated_metadata.append(cfile['filename'])
self._displayMessage(u"\nReference number (%s) added for (%s) \n" % (inetref, cfile['filename']))
# Clean up a few fields before updating Mythdb
if available_metadata['showlevel'] == 0: # Allows mythvideo to display this video
available_metadata['showlevel'] = 1
# Clean up a few fields before updating Mythdb
if available_metadata['showlevel'] == 0: # Allows mythvideo to display this video
available_metadata['showlevel'] = 1
num_mythdb_updates+=1
videos_updated_metadata.append(cfile['filename'])
for key in ['genres', 'cast', 'countries']:
if key == 'genres' and len(cfile['categories']):
genres_cast[key]+=cfile['categories']
if genres_cast.has_key(key):
num_mythdb_updates+=1
videos_updated_metadata.append(cfile['filename'])
for key in ['genres', 'cast', 'countries']:
if key == 'genres' and len(cfile['categories']):
genres_cast[key]+=cfile['categories']
if genres_cast.has_key(key):
- self._addCastGenreCountry( genres_cast[key], Video(id=intid, db=mythvideo), key)
+ self._addCastGenreCountry( genres_cast[key], Video(intid, db=mythvideo), key)