00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014 __title__ ="mnvsearch_api - Simple-to-use Python interface to search the MythNetvision data base tables"
00015 __author__="R.D. Vaughan"
00016 __purpose__='''
00017 This python script is intended to perform a data base search of MythNetvision data base tables for
00018 videos based on a command line search term.
00019 '''
00020
00021 __version__="0.1.4"
00022
00023
00024
00025
00026
00027
00028
00029
00030 import os, struct, sys, re, time, datetime, shutil, urllib
00031 import logging
00032 from socket import gethostname, gethostbyname
00033 from threading import Thread
00034 from copy import deepcopy
00035 from operator import itemgetter, attrgetter
00036
00037 from mnvsearch_exceptions import (MNVSQLError, MNVVideoNotFound, )
00038
00039 class OutStreamEncoder(object):
00040 """Wraps a stream with an encoder"""
00041 def __init__(self, outstream, encoding=None):
00042 self.out = outstream
00043 if not encoding:
00044 self.encoding = sys.getfilesystemencoding()
00045 else:
00046 self.encoding = encoding
00047
00048 def write(self, obj):
00049 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
00050 if isinstance(obj, unicode):
00051 try:
00052 self.out.write(obj.encode(self.encoding))
00053 except IOError:
00054 pass
00055 else:
00056 try:
00057 self.out.write(obj)
00058 except IOError:
00059 pass
00060
00061 def __getattr__(self, attr):
00062 """Delegate everything but write to the stream"""
00063 return getattr(self.out, attr)
00064 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00065 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00066
00067
00068
00069 try:
00070 '''If the MythTV python interface is found, required to access Netvision icon directory settings
00071 '''
00072 from MythTV import MythDB, MythLog
00073 try:
00074 '''Create an instance of each: MythDB
00075 '''
00076 MythLog._setlevel('none')
00077 mythdb = MythDB()
00078 except MythError, e:
00079 sys.stderr.write(u'\n! Error - %s\n' % e.args[0])
00080 filename = os.path.expanduser("~")+'/.mythtv/config.xml'
00081 if not os.path.isfile(filename):
00082 sys.stderr.write(u'\n! Error - A correctly configured (%s) file must exist\n' % filename)
00083 else:
00084 sys.stderr.write(u'\n! Error - Check that (%s) is correctly configured\n' % filename)
00085 sys.exit(1)
00086 except Exception, e:
00087 sys.stderr.write(u"\n! Error - Creating an instance caused an error for one of: MythDB. error(%s)\n" % e)
00088 sys.exit(1)
00089 except Exception, e:
00090 sys.stderr.write(u"\n! Error - MythTV python bindings could not be imported. error(%s)\n" % e)
00091 sys.exit(1)
00092
00093
00094 try:
00095 from StringIO import StringIO
00096 from lxml import etree
00097 except Exception, e:
00098 sys.stderr.write(u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
00099 sys.exit(1)
00100
00101
00102
00103
00104
00105 version = ''
00106 for digit in etree.LIBXML_VERSION:
00107 version+=str(digit)+'.'
00108 version = version[:-1]
00109 if version < '2.7.2':
00110 sys.stderr.write(u'''
00111 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
00112 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
00113 ''' % version)
00114 sys.exit(1)
00115
00116
00117 class Videos(object):
00118 """Main interface to the MNV treeview table search
00119 This is done to support a common naming framework for all python Netvision plugins no matter their site
00120 target.
00121
00122 Supports search methods
00123 The apikey is a not required for this grabber
00124 """
00125 def __init__(self,
00126 apikey,
00127 mythtv = True,
00128 interactive = False,
00129 select_first = False,
00130 debug = False,
00131 custom_ui = None,
00132 language = None,
00133 search_all_languages = False,
00134 ):
00135 """apikey (str/unicode):
00136 Specify the target site API key. Applications need their own key in some cases
00137
00138 mythtv (True/False):
00139 When True, the returned meta data is being returned has the key and values massaged to match MythTV
00140 When False, the returned meta data is being returned matches what target site returned
00141
00142 interactive (True/False): (This option is not supported by all target site apis)
00143 When True, uses built-in console UI is used to select the correct show.
00144 When False, the first search result is used.
00145
00146 select_first (True/False): (This option is not supported currently implemented in any grabbers)
00147 Automatically selects the first series search result (rather
00148 than showing the user a list of more than one series).
00149 Is overridden by interactive = False, or specifying a custom_ui
00150
00151 debug (True/False):
00152 shows verbose debugging information
00153
00154 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
00155 A callable subclass of interactive class (overrides interactive option)
00156
00157 language (2 character language abbreviation): (This option is not supported by all target site apis)
00158 The language of the returned data. Is also the language search
00159 uses. Default is "en" (English). For full list, run..
00160
00161 search_all_languages (True/False): (This option is not supported by all target site apis)
00162 By default, a Netvision grabber will only search in the language specified using
00163 the language option. When this is True, it will search for the
00164 show in any language
00165
00166 """
00167 self.config = {}
00168
00169 if apikey is not None:
00170 self.config['apikey'] = apikey
00171 else:
00172 pass
00173
00174 self.config['debug_enabled'] = debug
00175 self.common = common
00176 self.common.debug = debug
00177
00178 self.log_name = u'MNVsearch_Grabber'
00179 self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
00180 self.logger = self.common.logger
00181
00182 self.config['custom_ui'] = custom_ui
00183
00184 self.config['interactive'] = interactive
00185
00186 self.config['select_first'] = select_first
00187
00188 self.config['search_all_languages'] = search_all_languages
00189
00190 self.error_messages = {'MNVSQLError': u"! Error: A SQL call cause the exception error (%s)\n", 'MNVVideoNotFound': u"! Error: Video search did not return any results (%s)\n", }
00191
00192 self.channel = {'channel_title': u'Search all tree views', 'channel_link': u'http://www.mythtv.org/wiki/MythNetvision', 'channel_description': u"MythNetvision treeview data base search", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0}
00193
00194 self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/mnvsearch.png'
00195
00196
00197
00198 def searchTitle(self, title, pagenumber, pagelen, feedtitle=False):
00199 '''Key word video search of the MNV treeview tables
00200 return an array of matching item elements
00201 return
00202 '''
00203
00204
00205
00206
00207
00208
00209
00210 try:
00211 resultList = self.getTreeviewData(title, pagenumber, pagelen, feedtitle=feedtitle)
00212 except Exception, errormsg:
00213 raise MNVSQLError(self.error_messages['MNVSQLError'] % (errormsg))
00214
00215 if self.config['debug_enabled']:
00216 print "resultList: count(%s)" % len(resultList)
00217 print resultList
00218 print
00219
00220 if not len(resultList):
00221 raise MNVVideoNotFound(u"No treeview Video matches found for search value (%s)" % title)
00222
00223
00224 morePages = False
00225 if len(resultList) > pagelen:
00226 morePages = True
00227 resultList.pop()
00228
00229
00230 itemDict = {}
00231 itemThumbnail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
00232 itemContent = etree.XPath('.//media:content', namespaces=self.common.namespaces)
00233 for result in resultList:
00234 if not result['url']:
00235 continue
00236 mnvsearchItem = etree.XML(self.common.mnvItem)
00237
00238 mnvsearchItem.find('link').text = result['url']
00239 if result['title']:
00240 mnvsearchItem.find('title').text = result['title']
00241 if result['subtitle']:
00242 etree.SubElement(mnvsearchItem, "subtitle").text = result['subtitle']
00243 if result['description']:
00244 mnvsearchItem.find('description').text = result['description']
00245 if result['author']:
00246 mnvsearchItem.find('author').text = result['author']
00247 if result['date']:
00248 mnvsearchItem.find('pubDate').text = result['date'].strftime(self.common.pubDateFormat)
00249 if result['rating'] != '32576' and result['rating']:
00250 mnvsearchItem.find('rating').text = result['rating']
00251 if result['thumbnail']:
00252 itemThumbnail(mnvsearchItem)[0].attrib['url'] = result['thumbnail']
00253 if result['mediaURL']:
00254 itemContent(mnvsearchItem)[0].attrib['url'] = result['mediaURL']
00255 if result['filesize']:
00256 itemContent(mnvsearchItem)[0].attrib['length'] = unicode(result['filesize'])
00257 if result['time']:
00258 itemContent(mnvsearchItem)[0].attrib['duration'] = unicode(result['time'])
00259 if result['width']:
00260 itemContent(mnvsearchItem)[0].attrib['width'] = unicode(result['width'])
00261 if result['height']:
00262 itemContent(mnvsearchItem)[0].attrib['height'] = unicode(result['height'])
00263 if result['language']:
00264 itemContent(mnvsearchItem)[0].attrib['lang'] = result['language']
00265 if not result['season'] == 0 and not result['episode'] == 0:
00266 if result['season']:
00267 etree.SubElement(mnvsearchItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = unicode(result['season'])
00268 if result['episode']:
00269 etree.SubElement(mnvsearchItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = unicode(result['episode'])
00270 if result['customhtml'] == 1:
00271 etree.SubElement(mnvsearchItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text = 'true'
00272 if result['countries']:
00273 countries = result['countries'].split(u' ')
00274 for country in countries:
00275 etree.SubElement(mnvsearchItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = country
00276 itemDict[result['title'].lower()] = mnvsearchItem
00277
00278 if not len(itemDict.keys()):
00279 raise MNVVideoNotFound(u"No MNV Video matches found for search value (%s)" % title)
00280
00281
00282 if morePages:
00283 self.channel['channel_numresults'] = pagelen
00284 else:
00285 self.channel['channel_numresults'] = len(itemDict)
00286
00287 return [itemDict, morePages]
00288
00289
00290
00291 def searchForVideos(self, title, pagenumber, feedtitle=False):
00292 """Common name for a video search. Used to interface with MythTV plugin NetVision
00293 """
00294
00295
00296
00297
00298
00299 try:
00300 data = self.searchTitle(title, pagenumber, self.page_limit, feedtitle=feedtitle)
00301 except MNVVideoNotFound, msg:
00302 if feedtitle:
00303 return [{}, '0', '0', '0']
00304 sys.stderr.write(u"%s\n" % msg)
00305 sys.exit(0)
00306 except MNVSQLError, msg:
00307 sys.stderr.write(u'%s\n' % msg)
00308 sys.exit(1)
00309 except Exception, e:
00310 sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
00311 sys.exit(1)
00312
00313 if self.config['debug_enabled']:
00314 print "data: count(%s)" % len(data[0])
00315 print data
00316 print
00317
00318
00319 rssTree = etree.XML(self.common.mnvRSS+u'</rss>')
00320
00321
00322 itemCount = len(data[0].keys())
00323 if data[1] == True:
00324 self.channel['channel_returned'] = itemCount
00325 self.channel['channel_startindex'] = itemCount+(self.page_limit*(int(pagenumber)-1))
00326 self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
00327 else:
00328 self.channel['channel_returned'] = itemCount+(self.page_limit*(int(pagenumber)-1))
00329 self.channel['channel_startindex'] = self.channel['channel_returned']
00330 self.channel['channel_numresults'] = self.channel['channel_returned']
00331
00332
00333 if feedtitle:
00334 return [data[0], self.channel['channel_returned'], self.channel['channel_startindex'], self.channel['channel_numresults']]
00335
00336
00337 channelTree = self.common.mnvChannelElement(self.channel)
00338 rssTree.append(channelTree)
00339
00340 lastKey = None
00341 for key in sorted(data[0].keys()):
00342 if lastKey != key:
00343 channelTree.append(data[0][key])
00344 lastKey = key
00345
00346
00347 sys.stdout.write(u'<?xml version="1.0" encoding="UTF-8"?>\n')
00348 sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
00349 sys.exit(0)
00350
00351
00352 def getTreeviewData(self, searchTerms, pagenumber, pagelen, feedtitle=False):
00353 ''' Use a SQL call to get any matching data base entries from the "netvisiontreeitems" and
00354 "netvisionrssitems" tables. The search term can contain multiple search words separated
00355 by a ";" character.
00356 return a list of items found in the search or an empty dictionary if none were found
00357 '''
00358 if feedtitle:
00359 sqlStatement = u"(SELECT title, description, subtitle, season, episode, url, type, thumbnail, mediaURL, author, date, rating, filesize, player, playerargs, download, downloadargs, time, width, height, language, customhtml, countries FROM `internetcontentarticles` WHERE `feedtitle` LIKE '%%%%FEEDTITLE%%%%' AND (%s)) ORDER BY title ASC LIMIT %s , %s"
00360 else:
00361 sqlStatement = u'(SELECT title, description, subtitle, season, episode, url, type, thumbnail, mediaURL, author, date, rating, filesize, player, playerargs, download, downloadargs, time, width, height, language, customhtml, countries FROM `internetcontentarticles` WHERE %s) ORDER BY title ASC LIMIT %s , %s'
00362 searchTerm = u"`title` LIKE '%%SEARCHTERM%%' OR `description` LIKE '%%SEARCHTERM%%'"
00363
00364
00365 searchList = searchTerms.split(u';')
00366 if not len(searchList):
00367 return {}
00368
00369 dbSearchStatements = u''
00370 for aSearch in searchList:
00371 tmpTerms = searchTerm.replace(u'SEARCHTERM', aSearch)
00372 if not len(dbSearchStatements):
00373 dbSearchStatements+=tmpTerms
00374 else:
00375 dbSearchStatements+=u' OR ' + tmpTerms
00376
00377 if pagenumber == 1:
00378 fromResults = 0
00379 pageLimit = pagelen+1
00380 else:
00381 fromResults = (int(pagenumber)-1)*int(pagelen)
00382 pageLimit = pagelen+1
00383
00384 if feedtitle:
00385 sqlStatement = sqlStatement.replace(u'FEEDTITLE', feedtitle)
00386
00387 query = sqlStatement % (dbSearchStatements, fromResults, pageLimit,)
00388 if self.config['debug_enabled']:
00389 print "FromRow(%s) pageLimit(%s)" % (fromResults, pageLimit)
00390 print "query:"
00391 sys.stdout.write(query)
00392 print
00393
00394
00395 items = []
00396 c = mythdb.cursor()
00397 host = gethostname()
00398 c.execute(query)
00399 for title, description, subtitle, season, episode, url, media_type, thumbnail, mediaURL, author, date, rating, filesize, player, playerargs, download, downloadargs, time, width, height, language, customhtml, countries in c.fetchall():
00400 items.append({'title': title, 'description': description, 'subtitle': subtitle, 'season': season, 'episode': episode, 'url': url, 'media_type': media_type, 'thumbnail': thumbnail, 'mediaURL': mediaURL, 'author': author, 'date': date, 'rating': rating, 'filesize': filesize, 'player': player, 'playerargs': playerargs, 'download': download, 'downloadargs': downloadargs, 'time': time, 'width': width, 'height': height, 'language': language, 'customhtml': customhtml, 'countries': countries})
00401 c.close()
00402
00403 return items
00404
00405