14 __title__ =
"mnvsearch_api - Simple-to-use Python interface to search the MythNetvision data base tables" 15 __author__=
"R.D. Vaughan" 17 This python script is intended to perform a data base search of MythNetvision data base tables for 18 videos based on a command line search term. 30 import os, struct, sys, re, time, datetime, shutil, urllib
32 from socket
import gethostname, gethostbyname
33 from threading
import Thread
34 from copy
import deepcopy
35 from operator
import itemgetter, attrgetter
37 from mnvsearch_exceptions
import (MNVSQLError, MNVVideoNotFound, )
40 """Wraps a stream with an encoder""" 49 """Wraps the output stream, encoding Unicode strings with the specified encoding""" 50 if isinstance(obj, unicode):
62 """Delegate everything but write to the stream""" 63 return getattr(self.
out, attr)
70 '''If the MythTV python interface is found, required to access Netvision icon directory settings 72 from MythTV
import MythDB, MythLog
74 '''Create an instance of each: MythDB 76 MythLog._setlevel(
'none')
79 sys.stderr.write(
u'\n! Error - %s\n' % e.args[0])
80 filename = os.path.expanduser(
"~")+
'/.mythtv/config.xml' 81 if not os.path.isfile(filename):
82 sys.stderr.write(
u'\n! Error - A correctly configured (%s) file must exist\n' % filename)
84 sys.stderr.write(
u'\n! Error - Check that (%s) is correctly configured\n' % filename)
87 sys.stderr.write(
u"\n! Error - Creating an instance caused an error for one of: MythDB. error(%s)\n" % e)
90 sys.stderr.write(
u"\n! Error - MythTV python bindings could not be imported. error(%s)\n" % e)
95 from StringIO
import StringIO
96 from lxml
import etree
98 sys.stderr.write(
u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
106 for digit
in etree.LIBXML_VERSION:
107 version+=str(digit)+
'.' 108 version = version[:-1]
109 if version <
'2.7.2':
110 sys.stderr.write(
u''' 111 ! Error - The installed version of the "lxml" python library "libxml" version is too old. 112 At least "libxml" version 2.7.2 must be installed. Your version is (%s). 118 """Main interface to the MNV treeview table search 119 This is done to support a common naming framework for all python Netvision plugins no matter their site 122 Supports search methods 123 The apikey is a not required for this grabber 129 select_first = False,
133 search_all_languages = False,
135 """apikey (str/unicode): 136 Specify the target site API key. Applications need their own key in some cases 139 When True, the returned meta data is being returned has the key and values massaged to match MythTV 140 When False, the returned meta data is being returned matches what target site returned 142 interactive (True/False): (This option is not supported by all target site apis) 143 When True, uses built-in console UI is used to select the correct show. 144 When False, the first search result is used. 146 select_first (True/False): (This option is not supported currently implemented in any grabbers) 147 Automatically selects the first series search result (rather 148 than showing the user a list of more than one series). 149 Is overridden by interactive = False, or specifying a custom_ui 152 shows verbose debugging information 154 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) 155 A callable subclass of interactive class (overrides interactive option) 157 language (2 character language abbreviation): (This option is not supported by all target site apis) 158 The language of the returned data. Is also the language search 159 uses. Default is "en" (English). For full list, run.. 161 search_all_languages (True/False): (This option is not supported by all target site apis) 162 By default, a Netvision grabber will only search in the language specified using 163 the language option. When this is True, it will search for the 169 if apikey
is not None:
170 self.
config[
'apikey'] = apikey
174 self.
config[
'debug_enabled'] = debug
178 self.
log_name =
u'MNVsearch_Grabber' 182 self.
config[
'custom_ui'] = custom_ui
184 self.
config[
'interactive'] = interactive
186 self.
config[
'select_first'] = select_first
188 self.
config[
'search_all_languages'] = search_all_languages
190 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", }
192 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}
194 self.
channel_icon =
u'%SHAREDIR%/mythnetvision/icons/mnvsearch.png' 198 def searchTitle(self, title, pagenumber, pagelen, feedtitle=False):
199 '''Key word video search of the MNV treeview tables 200 return an array of matching item elements 211 resultList = self.
getTreeviewData(title, pagenumber, pagelen, feedtitle=feedtitle)
212 except Exception, errormsg:
213 raise MNVSQLError(self.
error_messages[
'MNVSQLError'] % (errormsg))
215 if self.
config[
'debug_enabled']:
216 print "resultList: count(%s)" % len(resultList)
220 if not len(resultList):
221 raise MNVVideoNotFound(
u"No treeview Video matches found for search value (%s)" % title)
225 if len(resultList) > pagelen:
231 itemThumbnail = etree.XPath(
'.//media:thumbnail', namespaces=self.
common.namespaces)
232 itemContent = etree.XPath(
'.//media:content', namespaces=self.
common.namespaces)
233 for result
in resultList:
234 if not result[
'url']:
236 mnvsearchItem = etree.XML(self.
common.mnvItem)
238 mnvsearchItem.find(
'link').text = result[
'url']
240 mnvsearchItem.find(
'title').text = result[
'title']
241 if result[
'subtitle']:
242 etree.SubElement(mnvsearchItem,
"subtitle").text = result[
'subtitle']
243 if result[
'description']:
244 mnvsearchItem.find(
'description').text = result[
'description']
246 mnvsearchItem.find(
'author').text = result[
'author']
248 mnvsearchItem.find(
'pubDate').text = result[
'date'].strftime(self.
common.pubDateFormat)
249 if result[
'rating'] !=
'32576' and result[
'rating']:
250 mnvsearchItem.find(
'rating').text = result[
'rating']
251 if result[
'thumbnail']:
252 itemThumbnail(mnvsearchItem)[0].attrib[
'url'] = result[
'thumbnail']
253 if result[
'mediaURL']:
254 itemContent(mnvsearchItem)[0].attrib[
'url'] = result[
'mediaURL']
255 if result[
'filesize']:
256 itemContent(mnvsearchItem)[0].attrib[
'length'] =
unicode(result[
'filesize'])
258 itemContent(mnvsearchItem)[0].attrib[
'duration'] =
unicode(result[
'time'])
260 itemContent(mnvsearchItem)[0].attrib[
'width'] =
unicode(result[
'width'])
262 itemContent(mnvsearchItem)[0].attrib[
'height'] =
unicode(result[
'height'])
263 if result[
'language']:
264 itemContent(mnvsearchItem)[0].attrib[
'lang'] = result[
'language']
265 if not result[
'season'] == 0
and not result[
'episode'] == 0:
267 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text =
unicode(result[
'season'])
268 if result[
'episode']:
269 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text =
unicode(result[
'episode'])
270 if result[
'customhtml'] == 1:
271 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text =
'true' 272 if result[
'countries']:
273 countries = result[
'countries'].split(
u' ')
274 for country
in countries:
275 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = country
276 itemDict[result[
'title'].lower()] = mnvsearchItem
278 if not len(itemDict.keys()):
279 raise MNVVideoNotFound(
u"No MNV Video matches found for search value (%s)" % title)
283 self.
channel[
'channel_numresults'] = pagelen
285 self.
channel[
'channel_numresults'] = len(itemDict)
287 return [itemDict, morePages]
292 """Common name for a video search. Used to interface with MythTV plugin NetVision 300 data = self.
searchTitle(title, pagenumber, self.page_limit, feedtitle=feedtitle)
301 except MNVVideoNotFound, msg:
303 return [{},
'0',
'0',
'0']
304 sys.stderr.write(
u"%s\n" % msg)
306 except MNVSQLError, msg:
307 sys.stderr.write(
u'%s\n' % msg)
310 sys.stderr.write(
u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
313 if self.
config[
'debug_enabled']:
314 print "data: count(%s)" % len(data[0])
319 rssTree = etree.XML(self.
common.mnvRSS+
u'</rss>')
322 itemCount = len(data[0].keys())
324 self.
channel[
'channel_returned'] = itemCount
325 self.
channel[
'channel_startindex'] = itemCount+(self.page_limit*(int(pagenumber)-1))
326 self.
channel[
'channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
328 self.
channel[
'channel_returned'] = itemCount+(self.page_limit*(int(pagenumber)-1))
329 self.
channel[
'channel_startindex'] = self.
channel[
'channel_returned']
330 self.
channel[
'channel_numresults'] = self.
channel[
'channel_returned']
334 return [data[0], self.
channel[
'channel_returned'], self.
channel[
'channel_startindex'], self.
channel[
'channel_numresults']]
338 rssTree.append(channelTree)
341 for key
in sorted(data[0].keys()):
343 channelTree.append(data[0][key])
347 sys.stdout.write(
u'<?xml version="1.0" encoding="UTF-8"?>\n')
348 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
353 ''' Use a SQL call to get any matching data base entries from the "netvisiontreeitems" and 354 "netvisionrssitems" tables. The search term can contain multiple search words separated 356 return a list of items found in the search or an empty dictionary if none were found 359 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" 361 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' 362 searchTerm =
u"`title` LIKE '%%SEARCHTERM%%' OR `description` LIKE '%%SEARCHTERM%%'" 365 searchList = searchTerms.split(
u';')
366 if not len(searchList):
369 dbSearchStatements =
u'' 370 for aSearch
in searchList:
371 tmpTerms = searchTerm.replace(
u'SEARCHTERM', aSearch)
372 if not len(dbSearchStatements):
373 dbSearchStatements+=tmpTerms
375 dbSearchStatements+=
u' OR ' + tmpTerms
379 pageLimit = pagelen+1
381 fromResults = (int(pagenumber)-1)*int(pagelen)
382 pageLimit = pagelen+1
385 sqlStatement = sqlStatement.replace(
u'FEEDTITLE', feedtitle)
387 query = sqlStatement % (dbSearchStatements, fromResults, pageLimit,)
388 if self.
config[
'debug_enabled']:
389 print "FromRow(%s) pageLimit(%s)" % (fromResults, pageLimit)
391 sys.stdout.write(query)
399 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():
400 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})
def __init__(self, outstream, encoding=None)
def searchTitle(self, title, pagenumber, pagelen, feedtitle=False)
def getTreeviewData(self, searchTerms, pagenumber, pagelen, feedtitle=False)
def __getattr__(self, attr)
def searchForVideos(self, title, pagenumber, feedtitle=False)
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)