MythTV  master
mainProcess.py
Go to the documentation of this file.
1 # -*- coding: UTF-8 -*-
2 
3 # ----------------------
4 # Name: mainProcess.py
5 # Python Script
6 # Author: R.D. Vaughan
7 # Purpose:
8 # This python script is the common process for all python MythTV Netvision plugin grabber processing.
9 # It follows the MythTV standards set for Netvision grabbers.
10 # Command example:
11 # See help (-u and -h) options
12 #
13 # Design:
14 # 1) Verify the command line options (display help or version and exit)
15 # 2) Call the target site with an information request according to the selected options
16 # 3) Prepare and display XML results from the information request
17 #
18 #
19 # License:Creative Commons GNU GPL v2
20 # (http://creativecommons.org/licenses/GPL/2.0/)
21 #-------------------------------------
22 __title__ ="Netvision Common Query Processing";
23 __author__="R.D.Vaughan"
24 __version__="v0.2.6"
25 # 0.1.0 Initial development
26 # 0.1.1 Refining the code like the additional of a grabber specifing the maximum number of items to return
27 # 0.1.2 Added the Tree view option
28 # 0.1.3 Updated deocumentation
29 # 0.2.0 Public release
30 # 0.2.1 Added the ability to have a mashup name independant of the mashup title
31 # 0.2.2 Added support of the -H option
32 # 0.2.3 Added support of the XML version information
33 # 0.2.4 Added the "command" tag to the XML version information
34 # 0.2.5 Fixed a bug in the setting up of a search mashup page item maximum
35 # 0.2.6 R. Ernst: Converted to python3, added option to disable a grabber script
36 
37 import sys, os
38 from optparse import OptionParser
39 import io
40 
41 class OutStreamEncoder(object):
42  """Wraps a stream with an encoder"""
43  def __init__(self, outstream, encoding=None):
44  self.out = outstream
45  if not encoding:
46  self.encoding = sys.getfilesystemencoding()
47  else:
48  self.encoding = encoding
49 
50  def write(self, obj):
51  """Wraps the output stream, encoding Unicode strings with the specified encoding"""
52  if isinstance(obj, str):
53  obj = obj.encode(self.encoding)
54  self.out.buffer.write(obj)
55 
56  def __getattr__(self, attr):
57  """Delegate everything but write to the stream"""
58  return getattr(self.out, attr)
59 
60 if isinstance(sys.stdout, io.TextIOWrapper):
61  sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
62  sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
63 
64 
65 try:
66  from io import StringIO
67  from lxml import etree
68 except Exception as e:
69  sys.stderr.write('\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
70  sys.exit(1)
71 
72 # Check that the lxml library is current enough
73 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html)
74 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later"
75 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package
76 version = ''
77 for digit in etree.LIBXML_VERSION:
78  version+=str(digit)+'.'
79 version = version[:-1]
80 if version < '2.7.2':
81  sys.stderr.write('''
82 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
83  At least "libxml" version 2.7.2 must be installed. Your version is (%s).
84 ''' % version)
85  sys.exit(1)
86 
87 
88 class siteQueries():
89  '''Methods that quering video Web sites for metadata and outputs the results to stdout any errors are output
90  to stderr.
91  '''
92  def __init__(self,
93  apikey,
94  target,
95  mythtv = False,
96  interactive = False,
97  select_first = False,
98  debug = False,
99  custom_ui = None,
100  language = None,
101  search_all_languages = False,
102  ):
103  """apikey (str/unicode):
104  Specify the themoviedb.org API key. Applications need their own key.
105  See http://api.themoviedb.org/2.1/ to get your own API key
106 
107  mythtv (True/False):
108  When True, the movie metadata is being returned has the key and values massaged to match MythTV
109  When False, the movie metadata is being returned matches what TMDB returned
110 
111  interactive (True/False):
112  When True, uses built-in console UI is used to select the correct show.
113  When False, the first search result is used.
114 
115  select_first (True/False):
116  Automatically selects the first series search result (rather
117  than showing the user a list of more than one series).
118  Is overridden by interactive = False, or specifying a custom_ui
119 
120  debug (True/False):
121  shows verbose debugging information
122 
123  custom_ui (tvdb_ui.BaseUI subclass):
124  A callable subclass of tvdb_ui.BaseUI (overrides interactive option)
125 
126  language (2 character language abbreviation):
127  The language of the returned data. Is also the language search
128  uses. Default is "en" (English). For full list, run..
129 
130  >>> MovieDb().config['valid_languages'] #doctest: +ELLIPSIS
131  ['da', 'fi', 'nl', ...]
132 
133  search_all_languages (True/False):
134  By default, TMDB will only search in the language specified using
135  the language option. When this is True, it will search for the
136  show in any language
137 
138  """
139  self.config = {}
140 
141  self.config['apikey'] = apikey
142  self.config['target'] = target.Videos(apikey, mythtv = mythtv,
143  interactive = interactive,
144  select_first = select_first,
145  debug = debug,
146  custom_ui = custom_ui,
147  language = language,
148  search_all_languages = search_all_languages,)
149  self.searchKeys = ['title', 'releasedate', 'overview', 'url', 'thumbnail', 'video', 'runtime', 'viewcount']
150 
151  self.searchXML = {'header': """<?xml version="1.0" encoding="UTF-8"?>""", 'rss': """
152 <rss version="2.0"
153 xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
154 xmlns:content="http://purl.org/rss/1.0/modules/content/"
155 xmlns:cnettv="http://cnettv.com/mrss/"
156 xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule"
157 xmlns:media="http://search.yahoo.com/mrss/"
158 xmlns:atom="http://www.w3.org/2005/Atom"
159 xmlns:amp="http://www.adobe.com/amp/1.0"
160 xmlns:dc="http://purl.org/dc/elements/1.1/"
161 xmlns:mythtv="http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format">""", 'channel': """
162  <channel>
163  <title>%(channel_title)s</title>
164  <link>%(channel_link)s</link>
165  <description>%(channel_description)s</description>
166  <numresults>%(channel_numresults)d</numresults>
167  <returned>%(channel_returned)d</returned>
168  <startindex>%(channel_startindex)d</startindex>""", 'item': """
169  <item>
170  <title>%(item_title)s</title>
171  <author>%(item_author)s</author>
172  <pubDate>%(item_pubdate)s</pubDate>
173  <description>%(item_description)s</description>
174  <link>%(item_link)s</link>
175  <media:group>
176  <media:thumbnail url='%(item_thumbnail)s'/>
177  <media:content url='%(item_url)s' duration='%(item_duration)s' width='%(item_width)s' height='%(item_height)s' lang='%(item_lang)s'/>
178  </media:group>
179  <rating>%(item_rating)s</rating>
180  </item>""", 'end_channel': """
181  </channel>""", 'end_rss': """
182 </rss>
183 """,
184  'nextpagetoken': """
185  <nextpagetoken>%(nextpagetoken)s</nextpagetoken>""",
186  'prevpagetoken': """
187  <prevpagetoken>%(prevpagetoken)s</prevpagetoken>"""
188  }
189 
190  self.treeViewXML = {'header': """<?xml version="1.0" encoding="UTF-8"?>""", 'rss': """
191 <rss version="2.0"
192 xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
193 xmlns:content="http://purl.org/rss/1.0/modules/content/"
194 xmlns:cnettv="http://cnettv.com/mrss/"
195 xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule"
196 xmlns:media="http://search.yahoo.com/mrss/"
197 xmlns:atom="http://www.w3.org/2005/Atom"
198 xmlns:amp="http://www.adobe.com/amp/1.0"
199 xmlns:dc="http://purl.org/dc/elements/1.1/"
200 xmlns:mythtv="http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format">""", 'start_channel': """
201  <channel>
202  <title>%(channel_title)s</title>
203  <link>%(channel_link)s</link>
204  <description>%(channel_description)s</description>
205  <numresults>%(channel_numresults)d</numresults>
206  <returned>%(channel_returned)d</returned>
207  <startindex>%(channel_startindex)d</startindex>""", "start_directory": """
208  <directory name="%s" thumbnail="%s">""", 'item': """
209  <item>
210  <title>%(item_title)s</title>
211  <author>%(item_author)s</author>
212  <pubDate>%(item_pubdate)s</pubDate>
213  <description>%(item_description)s</description>
214  <link>%(item_link)s</link>
215  <media:group>
216  <media:thumbnail url='%(item_thumbnail)s'/>
217  <media:content url='%(item_url)s' duration='%(item_duration)s' width='%(item_width)s' height='%(item_height)s' lang='%(item_lang)s'/>
218  </media:group>
219  <rating>%(item_rating)s</rating>
220  </item>""", "end_directory": """
221  </directory>""", 'end_channel': """
222  </channel>""", 'end_rss': """
223 </rss>
224 """, }
225 
226 
227  # end __init__()
228 
229  def searchForVideos(self, search_text, pagenumber):
230  '''Search for vidoes that match the search text and output the results a XML to stdout
231  '''
232  self.firstVideo = True
233  self.config['target'].page_limit = self.page_limit
234  self.config['target'].grabber_title = self.grabber_title
235  self.config['target'].mashup_title = self.mashup_title
236 
237  data_sets = self.config['target'].searchForVideos(search_text, pagenumber)
238  if data_sets is None:
239  return
240  if not len(data_sets):
241  return
242 
243  for data_set in data_sets:
244  if self.firstVideo:
245  sys.stdout.write(self.searchXML['header'])
246  sys.stdout.write(self.searchXML['rss'])
247  self.firstVideo = False
248  sys.stdout.write(self.searchXML['channel'] % data_set[0])
249  if 'nextpagetoken' in data_set[0]:
250  sys.stdout.write(self.searchXML['nextpagetoken'] % data_set[0])
251  if 'prevpagetoken' in data_set[0]:
252  sys.stdout.write(self.searchXML['prevpagetoken'] % data_set[0])
253  for item in data_set[1]:
254  sys.stdout.write(self.searchXML['item'] % item)
255  sys.stdout.write(self.searchXML['end_channel'])
256  sys.stdout.write(self.searchXML['end_rss'])
257  # end searchForVideos()
258 
259  def displayTreeView(self):
260  '''Create a Tree View specific to a target site and output the results a XML to stdout. Nested
261  directories are permissable.
262  '''
263  self.firstVideo = True
264  self.config['target'].page_limit = self.page_limit
265  self.config['target'].grabber_title = self.grabber_title
266  self.config['target'].mashup_title = self.mashup_title
267 
268  data_sets = self.config['target'].displayTreeView()
269  if data_sets is None:
270  return
271  if not len(data_sets):
272  return
273 
274  for data_set in data_sets:
275  if self.firstVideo:
276  sys.stdout.write(self.treeViewXML['header'])
277  sys.stdout.write(self.treeViewXML['rss'])
278  self.firstVideo = False
279  sys.stdout.write(self.treeViewXML['start_channel'] % data_set[0])
280  for directory in data_set[1]:
281  if isinstance(directory, list):
282  if directory[0] == '':
283  sys.stdout.write(self.treeViewXML['end_directory'])
284  continue
285  sys.stdout.write(self.treeViewXML['start_directory'] % (directory[0], directory[1]))
286  continue
287  sys.stdout.write(self.treeViewXML['item'] % directory)
288  sys.stdout.write(self.treeViewXML['end_channel'])
289  sys.stdout.write(self.treeViewXML['end_rss'])
290  # end displayTreeView()
291 
292  def displayHTML(self, videocode):
293  '''Request a custom Web page from the grabber and display on stdout
294  '''
295  self.firstVideo = True
296  self.config['target'].page_limit = self.page_limit
297  self.config['target'].grabber_title = self.grabber_title
298  self.config['target'].mashup_title = self.mashup_title
299  self.config['target'].HTMLvideocode = videocode
300 
301  sys.stdout.write(self.config['target'].displayCustomHTML())
302  return
303  # end displayHTML()
304 
305 # end Class siteQueries()
306 
308  '''Common processing for all Netvision python grabbers.
309  '''
310  def __init__(self, target, apikey, ):
311  '''
312  '''
313  self.target = target
314  self.apikey = apikey
315  # end __init__()
316 
317 
318  def main(self):
319  """Gets video details a search term
320  """
321  parser = OptionParser()
322 
323  parser.add_option( "-d", "--debug", action="store_true", default=False, dest="debug",
324  help="Show debugging info (URLs, raw XML ... etc, info varies per grabber)")
325  parser.add_option( "-u", "--usage", action="store_true", default=False, dest="usage",
326  help="Display examples for executing the script")
327  parser.add_option( "-v", "--version", action="store_true", default=False, dest="version",
328  help="Display grabber name and supported options")
329  parser.add_option( "-l", "--language", metavar="LANGUAGE", default='', dest="language",
330  help="Select data that matches the specified language fall back to English if nothing found (e.g. 'es' EspaƱol, 'de' Deutsch ... etc).\nNot all sites or grabbers support this option.")
331  parser.add_option( "-p", "--pagenumber", metavar="PAGE NUMBER", default=1, dest="pagenumber",
332  help="Display specific page of the search results. Default is page 1.\nPage number is ignored with the Tree View option (-T).")
333  functionality = ''
334  if self.grabberInfo['search']:
335  parser.add_option( "-S", "--search", action="store_true", default=False, dest="search",
336  help="Search for videos")
337  functionality+='S'
338 
339  if self.grabberInfo['tree']:
340  parser.add_option( "-T", "--treeview", action="store_true", default=False, dest="treeview",
341  help="Display a Tree View of a sites videos")
342  functionality+='T'
343 
344  if self.grabberInfo['html']:
345  parser.add_option( "-H", "--customhtml", action="store_true", default=False,
346  dest="customhtml", help="Return a custom HTML Web page")
347  functionality+='H'
348 
349  parser.usage="./%%prog -hduvl%s [parameters] <search text>\nVersion: %s Author: %s\n\nFor details on the MythTV Netvision plugin see the wiki page at:\nhttp://www.mythtv.org/wiki/MythNetvision" % (functionality, self.grabberInfo['version'], self.grabberInfo['author'])
350 
351  opts, args = parser.parse_args()
352 
353  # Make alls command line arguments unicode utf8
354  for index in range(len(args)):
355  args[index] = str(args[index])
356 
357  if opts.debug:
358  sys.stdout.write("\nopts: %s\n" % opts)
359  sys.stdout.write("\nargs: %s\n\n" % args)
360 
361  # Process version command line requests
362  if opts.version:
363  # grabber scripts must be explicitly enabled
364  if self.grabberInfo.get('enabled', False):
365  version = etree.XML('<grabber></grabber>')
366  else:
367  version = etree.XML('<disabledgrabber></disabledgrabber>')
368  etree.SubElement(version, "name").text = self.grabberInfo['title']
369  etree.SubElement(version, "author").text = self.grabberInfo['author']
370  etree.SubElement(version, "thumbnail").text = self.grabberInfo['thumbnail']
371  etree.SubElement(version, "command").text = self.grabberInfo['command']
372  for t in self.grabberInfo['type']:
373  etree.SubElement(version, "type").text = t
374  etree.SubElement(version, "description").text = self.grabberInfo['desc']
375  etree.SubElement(version, "version").text = self.grabberInfo['version']
376  if self.grabberInfo['search']:
377  etree.SubElement(version, "search").text = 'true'
378  if self.grabberInfo['tree']:
379  etree.SubElement(version, "tree").text = 'true'
380  sys.stdout.write(etree.tostring(version, encoding='UTF-8', pretty_print=True))
381  sys.exit(0)
382 
383  # Process usage command line requests
384  if opts.usage:
385  sys.stdout.write(self.grabberInfo['usage'])
386  sys.exit(0)
387 
388  if self.grabberInfo['search']:
389  if opts.search and not len(args) == 1:
390  sys.stderr.write("! Error: There must be one value for the search option. Your options are (%s)\n" % (args))
391  sys.exit(1)
392  if opts.search and args[0] == '':
393  sys.stderr.write("! Error: There must be a non-empty search argument, yours is empty.\n")
394  sys.exit(1)
395 
396  if self.grabberInfo['html']:
397  if opts.customhtml and not len(args) == 1:
398  sys.stderr.write("! Error: There must be one value for the search option. Your options are (%s)\n" % (args))
399  sys.exit(1)
400  if opts.customhtml and args[0] == '':
401  sys.stderr.write("! Error: There must be a non-empty Videocode argument, yours is empty.\n")
402  sys.exit(1)
403 
404  if not self.grabberInfo['search'] and not self.grabberInfo['tree'] and not self.grabberInfo['html']:
405  sys.stderr.write("! Error: You have not selected a valid option.\n")
406  sys.exit(1)
407 
408  Queries = siteQueries(self.apikey, self.target,
409  mythtv = True,
410  interactive = False,
411  select_first = False,
412  debug = opts.debug,
413  custom_ui = None,
414  language = opts.language,
415  search_all_languages = False,)
416 
417  # Set the maximum number of items to display per Mythtvnetvision search page
418  if self.grabberInfo['search']:
419  if opts.search:
420  if not 'SmaxPage' in list(self.grabberInfo.keys()):
421  Queries.page_limit = 20 # Default items per page
422  else:
423  Queries.page_limit = self.grabberInfo['SmaxPage']
424 
425  # Set the maximum number of items to display per Mythtvnetvision tree view page
426  if self.grabberInfo['tree']:
427  if opts.treeview:
428  if not 'TmaxPage' in list(self.grabberInfo.keys()):
429  Queries.page_limit = 20 # Default items per page
430  else:
431  Queries.page_limit = self.grabberInfo['TmaxPage']
432 
433  # Set the grabber title
434  Queries.grabber_title = self.grabberInfo['title']
435 
436  # Set the mashup title
437  if not 'mashup_title' in list(self.grabberInfo.keys()):
438  Queries.mashup_title = Queries.grabber_title
439  else:
440  if self.grabberInfo['search']:
441  if opts.search:
442  Queries.mashup_title = self.grabberInfo['mashup_title'] + "search"
443  if self.grabberInfo['tree']:
444  if opts.treeview:
445  Queries.mashup_title = self.grabberInfo['mashup_title'] + "treeview"
446  if self.grabberInfo['html']:
447  if opts.customhtml:
448  Queries.mashup_title = self.grabberInfo['mashup_title'] + "customhtml"
449 
450  # Process requested option
451  if self.grabberInfo['search']:
452  if opts.search: # Video search -S
453  self.page_limit = Queries.page_limit
454  Queries.searchForVideos(args[0], opts.pagenumber)
455 
456  if self.grabberInfo['tree']:
457  if opts.treeview: # Video treeview -T
458  self.page_limit = Queries.page_limit
459  Queries.displayTreeView()
460 
461  if self.grabberInfo['html']:
462  if opts.customhtml: # Video treeview -H
463  Queries.displayHTML(args[0])
464 
465  sys.exit(0)
466  # end main()
nv_python_libs.mainProcess.OutStreamEncoder.__getattr__
def __getattr__(self, attr)
Definition: mainProcess.py:56
nv_python_libs.mainProcess.siteQueries.treeViewXML
treeViewXML
Definition: mainProcess.py:190
nv_python_libs.mainProcess.siteQueries.__init__
def __init__(self, apikey, target, mythtv=False, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
Definition: mainProcess.py:92
nv_python_libs.mainProcess.mainProcess
Definition: mainProcess.py:307
nv_python_libs.mainProcess.siteQueries.displayHTML
def displayHTML(self, videocode)
Definition: mainProcess.py:292
nv_python_libs.mainProcess.mainProcess.__init__
def __init__(self, target, apikey)
Definition: mainProcess.py:310
nv_python_libs.mainProcess.OutStreamEncoder.write
def write(self, obj)
Definition: mainProcess.py:50
nv_python_libs.mainProcess.siteQueries
Definition: mainProcess.py:88
nv_python_libs.mainProcess.siteQueries.firstVideo
firstVideo
Definition: mainProcess.py:232
nv_python_libs.mainProcess.siteQueries.config
config
Definition: mainProcess.py:129
nv_python_libs.mainProcess.mainProcess.target
target
Definition: mainProcess.py:313
nv_python_libs.mainProcess.OutStreamEncoder
Definition: mainProcess.py:41
nv_python_libs.mainProcess.OutStreamEncoder.encoding
encoding
Definition: mainProcess.py:46
nv_python_libs.mainProcess.OutStreamEncoder.__init__
def __init__(self, outstream, encoding=None)
Definition: mainProcess.py:43
nv_python_libs.mainProcess.mainProcess.main
def main(self)
Definition: mainProcess.py:318
nv_python_libs.mainProcess.siteQueries.searchKeys
searchKeys
Definition: mainProcess.py:139
nv_python_libs.mainProcess.mainProcess.page_limit
page_limit
Definition: mainProcess.py:453
nv_python_libs.mainProcess.siteQueries.displayTreeView
def displayTreeView(self)
Definition: mainProcess.py:259
hardwareprofile.distros.all.get
def get()
Definition: all.py:22
nv_python_libs.mainProcess.OutStreamEncoder.out
out
Definition: mainProcess.py:44
nv_python_libs.mainProcess.mainProcess.apikey
apikey
Definition: mainProcess.py:314
nv_python_libs.mainProcess.siteQueries.searchXML
searchXML
Definition: mainProcess.py:141
nv_python_libs.mainProcess.siteQueries.searchForVideos
def searchForVideos(self, search_text, pagenumber)
Definition: mainProcess.py:229