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.request, urllib.parse, urllib.error
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, )
41 """Wraps a stream with an encoder"""
50 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
51 if isinstance(obj, str):
53 self.
out.buffer.write(obj)
56 """Delegate everything but write to the stream"""
57 return getattr(self.
out, attr)
59 if isinstance(sys.stdout, io.TextIOWrapper):
66 '''If the MythTV python interface is found, required to access Netvision icon directory settings
68 from MythTV
import MythDB, MythLog
70 '''Create an instance of each: MythDB
72 MythLog._setlevel(
'none')
74 except MythError
as e:
75 sys.stderr.write(
'\n! Error - %s\n' % e.args[0])
76 filename = os.path.expanduser(
"~")+
'/.mythtv/config.xml'
77 if not os.path.isfile(filename):
78 sys.stderr.write(
'\n! Error - A correctly configured (%s) file must exist\n' % filename)
80 sys.stderr.write(
'\n! Error - Check that (%s) is correctly configured\n' % filename)
82 except Exception
as e:
83 sys.stderr.write(
"\n! Error - Creating an instance caused an error for one of: MythDB. error(%s)\n" % e)
85 except Exception
as e:
86 sys.stderr.write(
"\n! Error - MythTV python bindings could not be imported. error(%s)\n" % e)
91 from io
import StringIO
92 from lxml
import etree
93 except Exception
as e:
94 sys.stderr.write(
'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
102 for digit
in etree.LIBXML_VERSION:
103 version+=str(digit)+
'.'
104 version = version[:-1]
105 if version <
'2.7.2':
107 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
108 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
114 """Main interface to the MNV treeview table search
115 This is done to support a common naming framework for all python Netvision plugins no matter their site
118 Supports search methods
119 The apikey is a not required for this grabber
125 select_first = False,
129 search_all_languages = False,
131 """apikey (str/unicode):
132 Specify the target site API key. Applications need their own key in some cases
135 When True, the returned meta data is being returned has the key and values massaged to match MythTV
136 When False, the returned meta data is being returned matches what target site returned
138 interactive (True/False): (This option is not supported by all target site apis)
139 When True, uses built-in console UI is used to select the correct show.
140 When False, the first search result is used.
142 select_first (True/False): (This option is not supported currently implemented in any grabbers)
143 Automatically selects the first series search result (rather
144 than showing the user a list of more than one series).
145 Is overridden by interactive = False, or specifying a custom_ui
148 shows verbose debugging information
150 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
151 A callable subclass of interactive class (overrides interactive option)
153 language (2 character language abbreviation): (This option is not supported by all target site apis)
154 The language of the returned data. Is also the language search
155 uses. Default is "en" (English). For full list, run..
157 search_all_languages (True/False): (This option is not supported by all target site apis)
158 By default, a Netvision grabber will only search in the language specified using
159 the language option. When this is True, it will search for the
165 if apikey
is not None:
166 self.
config[
'apikey'] = apikey
170 self.
config[
'debug_enabled'] = debug
178 self.
config[
'custom_ui'] = custom_ui
180 self.
config[
'interactive'] = interactive
182 self.
config[
'select_first'] = select_first
184 self.
config[
'search_all_languages'] = search_all_languages
186 self.
error_messages = {
'MNVSQLError':
"! Error: A SQL call cause the exception error (%s)\n",
'MNVVideoNotFound':
"! Error: Video search did not return any results (%s)\n", }
188 self.
channel = {
'channel_title':
'Search all tree views',
'channel_link':
'http://www.mythtv.org/wiki/MythNetvision',
'channel_description':
"MythNetvision treeview data base search",
'channel_numresults': 0,
'channel_returned': 1,
'channel_startindex': 0}
190 self.
channel_icon =
'%SHAREDIR%/mythnetvision/icons/mnvsearch.png'
194 def searchTitle(self, title, pagenumber, pagelen, feedtitle=False):
195 '''Key word video search of the MNV treeview tables
196 return an array of matching item elements
207 resultList = self.
getTreeviewData(title, pagenumber, pagelen, feedtitle=feedtitle)
208 except Exception
as errormsg:
211 if self.
config[
'debug_enabled']:
212 print(
"resultList: count(%s)" % len(resultList))
216 if not len(resultList):
217 raise MNVVideoNotFound(
"No treeview Video matches found for search value (%s)" % title)
221 if len(resultList) > pagelen:
227 itemThumbnail = etree.XPath(
'.//media:thumbnail', namespaces=self.
common.namespaces)
228 itemContent = etree.XPath(
'.//media:content', namespaces=self.
common.namespaces)
229 for result
in resultList:
230 if not result[
'url']:
232 mnvsearchItem = etree.XML(self.
common.mnvItem)
234 mnvsearchItem.find(
'link').text = result[
'url']
236 mnvsearchItem.find(
'title').text = result[
'title']
237 if result[
'subtitle']:
238 etree.SubElement(mnvsearchItem,
"subtitle").text = result[
'subtitle']
239 if result[
'description']:
240 mnvsearchItem.find(
'description').text = result[
'description']
242 mnvsearchItem.find(
'author').text = result[
'author']
244 mnvsearchItem.find(
'pubDate').text = result[
'date'].strftime(self.
common.pubDateFormat)
245 if result[
'rating'] !=
'32576' and result[
'rating']:
246 mnvsearchItem.find(
'rating').text = result[
'rating']
247 if result[
'thumbnail']:
248 itemThumbnail(mnvsearchItem)[0].attrib[
'url'] = result[
'thumbnail']
249 if result[
'mediaURL']:
250 itemContent(mnvsearchItem)[0].attrib[
'url'] = result[
'mediaURL']
251 if result[
'filesize']:
252 itemContent(mnvsearchItem)[0].attrib[
'length'] = str(result[
'filesize'])
254 itemContent(mnvsearchItem)[0].attrib[
'duration'] = str(result[
'time'])
256 itemContent(mnvsearchItem)[0].attrib[
'width'] = str(result[
'width'])
258 itemContent(mnvsearchItem)[0].attrib[
'height'] = str(result[
'height'])
259 if result[
'language']:
260 itemContent(mnvsearchItem)[0].attrib[
'lang'] = result[
'language']
261 if not result[
'season'] == 0
and not result[
'episode'] == 0:
263 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = str(result[
'season'])
264 if result[
'episode']:
265 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = str(result[
'episode'])
266 if result[
'customhtml'] == 1:
267 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text =
'true'
268 if result[
'countries']:
269 countries = result[
'countries'].split(
' ')
270 for country
in countries:
271 etree.SubElement(mnvsearchItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = country
272 itemDict[result[
'title'].lower()] = mnvsearchItem
274 if not len(list(itemDict.keys())):
275 raise MNVVideoNotFound(
"No MNV Video matches found for search value (%s)" % title)
279 self.
channel[
'channel_numresults'] = pagelen
281 self.
channel[
'channel_numresults'] = len(itemDict)
283 return [itemDict, morePages]
288 """Common name for a video search. Used to interface with MythTV plugin NetVision
296 data = self.
searchTitle(title, pagenumber, self.page_limit, feedtitle=feedtitle)
297 except MNVVideoNotFound
as msg:
299 return [{},
'0',
'0',
'0']
300 sys.stderr.write(
"%s\n" % msg)
302 except MNVSQLError
as msg:
303 sys.stderr.write(
'%s\n' % msg)
305 except Exception
as e:
306 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
309 if self.
config[
'debug_enabled']:
310 print(
"data: count(%s)" % len(data[0]))
315 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
318 itemCount = len(list(data[0].keys()))
320 self.
channel[
'channel_returned'] = itemCount
321 self.
channel[
'channel_startindex'] = itemCount+(self.page_limit*(int(pagenumber)-1))
322 self.
channel[
'channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
324 self.
channel[
'channel_returned'] = itemCount+(self.page_limit*(int(pagenumber)-1))
325 self.
channel[
'channel_startindex'] = self.
channel[
'channel_returned']
326 self.
channel[
'channel_numresults'] = self.
channel[
'channel_returned']
330 return [data[0], self.
channel[
'channel_returned'], self.
channel[
'channel_startindex'], self.
channel[
'channel_numresults']]
334 rssTree.append(channelTree)
337 for key
in sorted(data[0].keys()):
339 channelTree.append(data[0][key])
343 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
344 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
349 ''' Use a SQL call to get any matching data base entries from the "netvisiontreeitems" and
350 "netvisionrssitems" tables. The search term can contain multiple search words separated
352 return a list of items found in the search or an empty dictionary if none were found
355 sqlStatement =
"(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"
357 sqlStatement =
'(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'
358 searchTerm =
"`title` LIKE '%%SEARCHTERM%%' OR `description` LIKE '%%SEARCHTERM%%'"
361 searchList = searchTerms.split(
';')
362 if not len(searchList):
365 dbSearchStatements =
''
366 for aSearch
in searchList:
367 tmpTerms = searchTerm.replace(
'SEARCHTERM', aSearch)
368 if not len(dbSearchStatements):
369 dbSearchStatements+=tmpTerms
371 dbSearchStatements+=
' OR ' + tmpTerms
375 pageLimit = pagelen+1
377 fromResults = (int(pagenumber)-1)*int(pagelen)
378 pageLimit = pagelen+1
381 sqlStatement = sqlStatement.replace(
'FEEDTITLE', feedtitle)
383 query = sqlStatement % (dbSearchStatements, fromResults, pageLimit,)
384 if self.
config[
'debug_enabled']:
385 print(
"FromRow(%s) pageLimit(%s)" % (fromResults, pageLimit))
387 sys.stdout.write(query)
395 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():
396 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})