MythTV  master
mashups_api.py
Go to the documentation of this file.
1 # -*- coding: UTF-8 -*-
2 
3 # ----------------------
4 # Name: mashups_api - Simple-to-use Python interface to Mashups of RSS feeds and HTML video data
5 #
6 # Python Script
7 # Author: R.D. Vaughan
8 # Purpose: This python script is intended to perform a variety of utility functions to
9 # search and access text metadata, video and image URLs from various Internet sources.
10 #
11 # License:Creative Commons GNU GPL v2
12 # (http://creativecommons.org/licenses/GPL/2.0/)
13 #-------------------------------------
14 __title__ ="mashups_api - Simple-to-use Python interface to Mashups of RSS feeds and HTML video data"
15 __author__="R.D. Vaughan"
16 __purpose__='''
17 This python script is intended to perform a variety of utility functions to search and access text
18 meta data, video and image URLs from various Internet sources. These routines process the RSS feeds and information into MNV standard channel, directory and item RSS XML files. The specific Mashups are specified through a user XML preference file usually found at
19 "~/.mythtv/MythNetvision/userGrabberPrefs/xxxxMashup.xml" where "xxxx" is the specific mashup name matching the associated grabber name that calls these functions.
20 '''
21 
22 __version__="v0.1.6"
23 # 0.1.0 Initial development
24 # 0.1.1 Added Search Mashup capabilities
25 # 0.1.2 Fixed a couple of error messages with improper variable names
26 # 0.1.3 Add the ability for a Mashup to search the "internetcontentarticles" table
27 # 0.1.4 Add the ability for a Mashup to pass variables to a XSLT style sheet
28 # 0.1.5 Removed a redundant build of the common XSLT function dictionary
29 # 0.1.6 Corrected a bug were a users custom setting were not being updated properly
30 
31 import os, struct, sys, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
32 from socket import gethostname, gethostbyname
33 from copy import deepcopy
34 import logging
35 
36 from .mashups_exceptions import (MashupsUrlError, MashupsHttpError, MashupsRssError, MashupsVideoNotFound, MashupsConfigFileError, MashupsUrlDownloadError)
37 import io
38 
39 class OutStreamEncoder(object):
40  """Wraps a stream with an encoder"""
41  def __init__(self, outstream, encoding=None):
42  self.out = outstream
43  if not encoding:
44  self.encoding = sys.getfilesystemencoding()
45  else:
46  self.encoding = encoding
47 
48  def write(self, obj):
49  """Wraps the output stream, encoding Unicode strings with the specified encoding"""
50  if isinstance(obj, str):
51  obj = obj.encode(self.encoding)
52  self.out.buffer.write(obj)
53 
54  def __getattr__(self, attr):
55  """Delegate everything but write to the stream"""
56  return getattr(self.out, attr)
57 
58 if isinstance(sys.stdout, io.TextIOWrapper):
59  sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
60  sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
61 
62 
63 try:
64  from io import StringIO
65  from lxml import etree
66 except Exception as e:
67  sys.stderr.write('\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
68  sys.exit(1)
69 
70 # Check that the lxml library is current enough
71 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html)
72 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later"
73 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package
74 version = ''
75 for digit in etree.LIBXML_VERSION:
76  version+=str(digit)+'.'
77 version = version[:-1]
78 if version < '2.7.2':
79  sys.stderr.write('''
80 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
81  At least "libxml" version 2.7.2 must be installed. Your version is (%s).
82 ''' % version)
83  sys.exit(1)
84 
85 
86 class Videos(object):
87  """Main interface to any Mashup
88  This is done to support a common naming framework for all python Netvision plugins
89  no matter their site target.
90 
91  Supports MNV Mashup Search and Treeview methods
92  The apikey is a not required for Mashups
93  """
94  def __init__(self,
95  apikey,
96  mythtv = True,
97  interactive = False,
98  select_first = False,
99  debug = False,
100  custom_ui = None,
101  language = None,
102  search_all_languages = False,
103  ):
104  """apikey (str/unicode):
105  Specify the target site API key. Applications need their own key in some cases
106 
107  mythtv (True/False):
108  When True, the returned meta data is being returned has the key and values massaged to match MythTV
109  When False, the returned meta data is being returned matches what target site returned
110 
111  interactive (True/False): (This option is not supported by all target site apis)
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): (This option is not supported currently implemented in any grabbers)
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 (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
124  A callable subclass of interactive class (overrides interactive option)
125 
126  language (2 character language abbreviation): (This option is not supported by all target site apis)
127  The language of the returned data. Is also the language search
128  uses. Default is "en" (English). For full list, run..
129 
130  search_all_languages (True/False): (This option is not supported by all target site apis)
131  By default, a Netvision grabber will only search in the language specified using
132  the language option. When this is True, it will search for the
133  show in any language
134 
135  """
136  self.config = {}
137 
138  if apikey is not None:
139  self.config['apikey'] = apikey
140  else:
141  pass # Mashups does not require an apikey
142 
143  self.config['debug_enabled'] = debug # show debugging messages
144  self.common = common
145  self.common.debug = debug # Set the common function debug level
146 
147  self.log_name = 'Mashups_Grabber'
148  self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
149  self.logger = self.common.logger # Setups the logger (self.log.debug() etc)
150 
151  self.config['custom_ui'] = custom_ui
152 
153  self.config['interactive'] = interactive
154 
155  self.config['select_first'] = select_first
156 
157  if language:
158  self.config['language'] = language
159  else:
160  self.config['language'] = 'en'
161 
162  self.config['search_all_languages'] = search_all_languages
163 
164  self.error_messages = {'MashupsUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'MashupsHttpError': "! Error: An HTTP communications error with the Mashups was raised (%s)\n", 'MashupsRssError': "! Error: Invalid RSS meta data\nwas received from the Mashups error (%s). Skipping item.\n", 'MashupsVideoNotFound': "! Error: Video search with the Mashups did not return any results (%s)\n", 'MashupsConfigFileError': "! Error: mashups_config.xml file missing\nit should be located in and named as (%s).\n", 'MashupsUrlDownloadError': "! Error: Downloading a RSS feed or Web page (%s).\n", }
165 
166  # Channel details and search results
167  self.channel = {'channel_title': '', 'channel_link': 'http://www.mythtv.org/wiki/MythNetvision', 'channel_description': "Mashups combines media from multiple sources to create a new work", 'channel_numresults': 0, 'channel_returned': 0, 'channel_startindex': 0}
168 
169  self.channel_icon = '%SHAREDIR%/mythnetvision/icons/mashups.png'
170 
171  self.config['image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
172  # end __init__()
173 
174 
179 
180  def setTreeViewIcon(self, dir_icon=None):
181  '''Check if there is a specific generic tree view icon. If not default to the channel icon.
182  return self.tree_dir_icon
183  '''
185  if not dir_icon:
186  if self.tree_key not in self.feed_icons:
187  return self.tree_dir_icon
188  if self.feed not in self.feed_icons[self.tree_key]:
189  return self.tree_dir_icon
190  if not dir_icon:
191  return self.tree_dir_icon
192  dir_icon = self.feed_icons[self.tree_key][self.feed]
193  if not dir_icon:
194  return self.tree_dir_icon
195  self.tree_dir_icon = '%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, )
196  return self.tree_dir_icon
197  # end setTreeViewIcon()
198 
199 
200  def getMashupsConfig(self):
201  ''' Read the MNV Mashups grabber "mashups_config.xml" configuration file
202  return nothing
203  '''
204  # Read the grabber mashups_config.xml configuration file
205  url = '%s/nv_python_libs/configs/XML/mashups_config.xml' % (baseProcessingDir, )
206  if not os.path.isfile(url):
207  raise MashupsConfigFileError(self.error_messages['MashupsConfigFileError'] % (url, ))
208 
209  if self.config['debug_enabled']:
210  print(url)
211  print()
212  try:
213  self.mashups_config = etree.parse(url)
214  except Exception as errormsg:
215  raise MashupsUrlError(self.error_messages['MashupsUrlError'] % (url, errormsg))
216  return
217  # end getMashupsConfig()
218 
219 
221  '''Read the mashups_config.xml and user preference xxxxxMashup.xml file.
222  If the xxxxxMashup.xml file does not exist then copy the default.
223  return nothing
224  '''
225  # Get mashups_config.xml
226  self.getMashupsConfig()
227 
228  # Check if the mashups.xml file exists
229  fileName = '%s.xml' % self.mashup_title.replace('treeview', '').replace('search', '')
230  userPreferenceFile = '%s/%s' % (self.mashups_config.find('userPreferenceFile').text, fileName)
231  if userPreferenceFile[0] == '~':
232  self.mashups_config.find('userPreferenceFile').text = "%s%s" % (os.path.expanduser("~"), userPreferenceFile[1:])
233 
234  # If the user config file does not exists then copy one from the default
235  create = False
236  defaultConfig = '%s/nv_python_libs/configs/XML/defaultUserPrefs/%s' % (baseProcessingDir, fileName, )
237  prefDir = self.mashups_config.find('userPreferenceFile').text.replace('/'+fileName, '')
238  if not os.path.isfile(self.mashups_config.find('userPreferenceFile').text):
239  # Make the necessary directories if they do not already exist
240  if not os.path.isdir(prefDir):
241  os.makedirs(prefDir)
242  shutil.copy2(defaultConfig, self.mashups_config.find('userPreferenceFile').text)
243  create = True
244 
245  # Read the grabber mashups_config.xml configuration file
246  if self.config['debug_enabled']:
247  print(self.mashups_config.find('userPreferenceFile').text)
248  print()
249  try:
250  self.userPrefs = etree.parse(self.mashups_config.find('userPreferenceFile').text)
251  except Exception as errormsg:
252  raise MashupsUrlError(self.error_messages['MashupsUrlError'] % (self.mashups_config.find('userPreferenceFile').text, errormsg))
253 
254  if create:
255  return
256 
257  # Merge the existing entries with the user's current settings to get any distributed
258  # additions or changes
259  try:
260  defaultPrefs = etree.parse(defaultConfig)
261  except Exception as errormsg:
262  raise MashupsUrlError(self.error_messages['MashupsUrlError'] % (defaultConfig, errormsg))
263  urlFilter = etree.XPath('//sourceURL[@url=$url and @name=$name]', namespaces=self.common.namespaces)
264  globalmaxFilter = etree.XPath('./../..', namespaces=self.common.namespaces)
265  for sourceURL in self.userPrefs.xpath('//sourceURL'):
266  url = sourceURL.attrib['url']
267  name = sourceURL.attrib['name']
268  defaultSourceURL = urlFilter(defaultPrefs, url=url, name=name)
269  if len(defaultSourceURL):
270  defaultSourceURL[0].attrib['enabled'] = sourceURL.attrib['enabled']
271  if sourceURL.attrib.get('max'):
272  defaultSourceURL[0].attrib['max'] = sourceURL.attrib['max']
273  directory = globalmaxFilter(sourceURL)[0]
274  if directory.attrib.get('globalmax'):
275  defaultDir = directory.attrib.get('globalmax')
276  globalmaxFilter(defaultSourceURL[0])[0].attrib['globalmax'] = directory.attrib['globalmax']
277 
278  # Save the updated xxxxMashup.xml file
279  tagName = defaultPrefs.getroot().tag
280 
281  # Get the internal documentaion
282  docComment = ''
283  for element in defaultPrefs.iter(tag=etree.Comment):
284  docComment+=etree.tostring(element, encoding='UTF-8', pretty_print=True)[:-1]
285 
286  fd = open(self.mashups_config.find('userPreferenceFile').text, 'w')
287  fd.write(('<%s>\n' % tagName)+docComment)
288  fd.write(''.join(etree.tostring(element, encoding='UTF-8', pretty_print=True) for element in defaultPrefs.xpath('/%s/*' % tagName))+('</%s>\n'% tagName))
289  fd.close()
290 
291  # Make sure that the user preferences are using the latest version
292  self.userPrefs = defaultPrefs
293 
294  return
295  # end getUserPreferences()
296 
297 
302 
303  def searchForVideos(self, title, pagenumber):
304  """Common name for a video search. Used to interface with MythTV plugin NetVision
305  Display the results and exit
306  """
307  # Get the user preferences
308  try:
309  self.getUserPreferences()
310  except Exception as e:
311  sys.stderr.write('%s' % e)
312  sys.exit(1)
313 
314  if self.config['debug_enabled']:
315  print("self.userPrefs:")
316  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
317  print()
318 
319  # Import the mnvsearch_api.py functions
320  fullPath = '%s/nv_python_libs/%s' % (self.common.baseProcessingDir, 'mnvsearch')
321  sys.path.append(fullPath)
322  try:
323  exec('''import mnvsearch_api''')
324  except Exception as errmsg:
325  sys.stderr.write('! Error: Dynamic import of mnvsearch_api functions\nmessage(%s)\n' % (errmsg))
326  sys.exit(1)
327  mnvsearch_api.common = self.common
328  mnvsearch = mnvsearch_api.Videos(None, debug=self.config['debug_enabled'], language=self.config['language'])
329  mnvsearch.page_limit = self.page_limit
330 
331  # Get the dictionary of mashups functions pointers
332  self.common.buildFunctionDict()
333 
334  # Massage channel icon
335  self.channel_icon = self.common.ampReplace(self.channel_icon)
336 
337  # Create RSS element tree
338  rssTree = etree.XML(self.common.mnvRSS+'</rss>')
339 
340  # Add the Channel element tree
341  self.channel['channel_title'] = self.grabber_title
342  self.channel_icon = self.setTreeViewIcon(dir_icon=self.mashup_title.replace('Mashuptreeview', ''))
343  channelTree = self.common.mnvChannelElement(self.channel)
344  rssTree.append(channelTree)
345 
346  # Globally add all the xpath extentions to the "mythtv" namespace allowing access within the
347  # XSLT stylesheets
348  self.common.buildFunctionDict()
349  mnvXpath = etree.FunctionNamespace('http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format')
350  mnvXpath.prefix = 'mnvXpath'
351  for key in list(self.common.functionDict.keys()):
352  mnvXpath[key] = self.common.functionDict[key]
353 
354  # Build Search parameter dictionary
355  self.common.pagenumber = pagenumber
356  self.common.page_limit = self.page_limit
357  self.common.language = self.config['language']
358  self.common.searchterm = title.encode("utf-8")
359  searchParms = {
360  'searchterm': urllib.parse.quote_plus(title.encode("utf-8")),
361  'pagemax': self.page_limit,
362  'language': self.config['language'],
363  }
364  # Create a structure of feeds that can be concurrently downloaded
365  xsltFilename = etree.XPath('./@xsltFile', namespaces=self.common.namespaces)
366  sourceData = etree.XML('<xml></xml>')
367  for source in self.userPrefs.xpath('//search//sourceURL[@enabled="true"]'):
368  if source.attrib.get('mnvsearch'):
369  continue
370  urlName = source.attrib.get('name')
371  if urlName:
372  uniqueName = '%s;%s' % (urlName, source.attrib.get('url'))
373  else:
374  uniqueName = 'RSS;%s' % (source.attrib.get('url'))
375  url = etree.XML('<url></url>')
376  etree.SubElement(url, "name").text = uniqueName
377  if source.attrib.get('pageFunction'):
378  searchParms['pagenum'] = self.common.functionDict[source.attrib['pageFunction']]('dummy', 'dummy')
379  else:
380  searchParms['pagenum'] = pagenumber
381  etree.SubElement(url, "href").text = source.attrib.get('url') % searchParms
382  if len(xsltFilename(source)):
383  for xsltName in xsltFilename(source):
384  etree.SubElement(url, "xslt").text = xsltName.strip()
385  etree.SubElement(url, "parserType").text = source.attrib.get('type')
386  sourceData.append(url)
387 
388  if self.config['debug_enabled']:
389  print("rssData:")
390  sys.stdout.write(etree.tostring(sourceData, encoding='UTF-8', pretty_print=True))
391  print()
392 
393  # Get the source data
394  if sourceData.find('url') is not None:
395  # Process each directory of the user preferences that have an enabled rss feed
396  try:
397  resultTree = self.common.getUrlData(sourceData)
398  except Exception as errormsg:
399  raise MashupsUrlDownloadError(self.error_messages['MashupsUrlDownloadError'] % (errormsg))
400  if self.config['debug_enabled']:
401  print("resultTree:")
402  sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
403  print()
404 
405  # Process the results
406  itemFilter = etree.XPath('.//item', namespaces=self.common.namespaces)
407  # Create special directory elements
408  for result in resultTree.findall('results'):
409  channelTree.xpath('numresults')[0].text = self.common.numresults
410  channelTree.xpath('returned')[0].text = self.common.returned
411  channelTree.xpath('startindex')[0].text = self.common.startindex
412  for item in itemFilter(result):
413  channelTree.append(item)
414 
415  # Process any mnvsearches
416  for source in self.userPrefs.xpath('//search//sourceURL[@enabled="true" and @mnvsearch]'):
417  results = mnvsearch.searchForVideos(title, pagenumber, feedtitle=source.xpath('./@mnvsearch')[0])
418  if len(list(results[0].keys())):
419  channelTree.xpath('returned')[0].text = '%s' % (int(channelTree.xpath('returned')[0].text)+results[1])
420  channelTree.xpath('startindex')[0].text = '%s' % (int(channelTree.xpath('startindex')[0].text)+results[2])
421  channelTree.xpath('numresults')[0].text = '%s' % (int(channelTree.xpath('numresults')[0].text)+results[3])
422  lastKey = None
423  for key in sorted(results[0].keys()):
424  if lastKey != key:
425  channelTree.append(results[0][key])
426  lastKey = key
427 
428  # Check that there was at least some items
429  if len(rssTree.xpath('//item')):
430  # Output the MNV Mashup results
431  sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
432  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
433 
434  sys.exit(0)
435  # end searchForVideos()
436 
437 
438  def displayTreeView(self):
439  '''Gather the Mashups Internet sources then get the videos meta data in each of them
440  Display the results and exit
441  '''
442  # Get the user preferences
443  try:
444  self.getUserPreferences()
445  except Exception as e:
446  sys.stderr.write('%s' % e)
447  sys.exit(1)
448 
449  if self.config['debug_enabled']:
450  print("self.userPrefs:")
451  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
452  print()
453 
454  # Massage channel icon
455  self.channel_icon = self.common.ampReplace(self.channel_icon)
456 
457  # Create RSS element tree
458  rssTree = etree.XML(self.common.mnvRSS+'</rss>')
459 
460  # Add the Channel element tree
461  self.channel['channel_title'] = self.grabber_title
462  self.channel_icon = self.setTreeViewIcon(dir_icon=self.mashup_title.replace('Mashuptreeview', ''))
463  channelTree = self.common.mnvChannelElement(self.channel)
464  rssTree.append(channelTree)
465  self.common.language = self.config['language']
466 
467  # Globally add all the xpath extentions to the "mythtv" namespace allowing access within the
468  # XSLT stylesheets
469  self.common.buildFunctionDict()
470  mnvXpath = etree.FunctionNamespace('http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format')
471  mnvXpath.prefix = 'mnvXpath'
472  for key in list(self.common.functionDict.keys()):
473  mnvXpath[key] = common.functionDict[key]
474 
475  # Create a structure of feeds that can be concurrently downloaded
476  xsltFilename = etree.XPath('./@xsltFile', namespaces=self.common.namespaces)
477  sourceData = etree.XML('<xml></xml>')
478  for source in self.userPrefs.xpath('//directory//sourceURL[@enabled="true"]'):
479  urlName = source.attrib.get('name')
480  if urlName:
481  uniqueName = '%s;%s' % (urlName, source.attrib.get('url'))
482  else:
483  uniqueName = 'RSS;%s' % (source.attrib.get('url'))
484  url = etree.XML('<url></url>')
485  etree.SubElement(url, "name").text = uniqueName
486  etree.SubElement(url, "href").text = source.attrib.get('url')
487  if source.attrib.get('parameter') is not None:
488  etree.SubElement(url, "parameter").text = source.attrib.get('parameter')
489  if len(xsltFilename(source)):
490  for xsltName in xsltFilename(source):
491  etree.SubElement(url, "xslt").text = xsltName.strip()
492  etree.SubElement(url, "parserType").text = source.attrib.get('type')
493  sourceData.append(url)
494 
495  if self.config['debug_enabled']:
496  print("rssData:")
497  sys.stdout.write(etree.tostring(sourceData, encoding='UTF-8', pretty_print=True))
498  print()
499 
500  # Get the source data
501  if sourceData.find('url') is not None:
502  # Process each directory of the user preferences that have an enabled rss feed
503  try:
504  resultTree = self.common.getUrlData(sourceData)
505  except Exception as errormsg:
506  raise MashupsUrlDownloadError(self.error_messages['MashupsUrlDownloadError'] % (errormsg))
507  if self.config['debug_enabled']:
508  print("resultTree:")
509  sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
510  print()
511  # Process the results
512  categoryDir = None
513  categoryElement = None
514  xsltShowName = etree.XPath('//directory//sourceURL[@url=$url]/../@name', namespaces=self.common.namespaces)
515  channelThumbnail = etree.XPath('.//directoryThumbnail', namespaces=self.common.namespaces)
516  directoryFilter = etree.XPath('.//directory', namespaces=self.common.namespaces)
517  itemFilter = etree.XPath('.//item', namespaces=self.common.namespaces)
518  feedFilter = etree.XPath('//directory//sourceURL[@url=$url]')
519  channelThumbnail = etree.XPath('.//directoryThumbnail', namespaces=self.common.namespaces)
520  specialDirectoriesFilter = etree.XPath('.//specialDirectories')
521  specialDirectoriesKeyFilter = etree.XPath('.//specialDirectories/*[name()=$name]/@key')
522  specialDirectoriesDict = {}
523  # Create special directory elements
524  for result in resultTree.findall('results'):
525  if len(specialDirectoriesFilter(result)):
526  for element in specialDirectoriesFilter(result)[0]:
527  if not element.tag in list(specialDirectoriesDict.keys()):
528  specialDirectoriesElement = etree.XML('<directory></directory>')
529  specialDirectoriesElement.attrib['name'] = element.attrib['dirname']
530  if element.attrib.get('count'):
531  count = int(element.attrib['count'])
532  else:
533  count = 1
534  if element.attrib.get('thumbnail'):
535  specialDirectoriesElement.attrib['thumbnail'] = element.attrib['thumbnail']
536  else:
537  specialDirectoriesElement.attrib['thumbnail'] = self.channel_icon
538  specialDirectoriesDict[element.tag] = [specialDirectoriesElement, element.attrib['reverse'], count]
539  for result in resultTree.findall('results'):
540  names = result.find('name').text.split(';')
541  names[0] = self.common.massageText(names[0])
542  if len(xsltShowName(self.userPrefs, url=names[1])):
543  names[0] = self.common.massageText(xsltShowName(self.userPrefs, url=names[1])[0].strip())
544  if names[0] == 'RSS':
545  names[0] = self.common.massageText(rssName(result.find('result'))[0].text)
546  count = 0
547  urlMax = None
548  url = feedFilter(self.userPrefs, url=names[1])
549  if len(url):
550  if url[0].attrib.get('max'):
551  try:
552  urlMax = int(url[0].attrib.get('max'))
553  except:
554  pass
555  elif url[0].getparent().getparent().attrib.get('globalmax'):
556  try:
557  urlMax = int(url[0].getparent().getparent().attrib.get('globalmax'))
558  except:
559  pass
560  if urlMax == 0:
561  urlMax = None
562 
563  # Create a new directory and/or subdirectory if required
564  if names[0] != categoryDir:
565  if categoryDir is not None:
566  channelTree.append(categoryElement)
567  categoryElement = etree.XML('<directory></directory>')
568  categoryElement.attrib['name'] = names[0]
569  if len(channelThumbnail(result)):
570  categoryElement.attrib['thumbnail'] = channelThumbnail(result)[0].text
571  else:
572  categoryElement.attrib['thumbnail'] = self.channel_icon
573  categoryDir = names[0]
574 
575  # Add the special directories videos
576  for key in list(specialDirectoriesDict.keys()):
577  sortDict = {}
578  count = 0
579  for sortData in specialDirectoriesKeyFilter(result, name=key):
580  sortDict[sortData] = count
581  count+=1
582  if len(sortDict):
583  if specialDirectoriesDict[key][1] == 'true':
584  sortedKeys = sorted(list(sortDict.keys()), reverse=True)
585  else:
586  sortedKeys = sorted(list(sortDict.keys()), reverse=False)
587  if specialDirectoriesDict[key][2] == 0:
588  number = len(sortDict)
589  else:
590  number = specialDirectoriesDict[key][2]
591  for count in range(number):
592  if count == len(sortDict):
593  break
594  specialDirectoriesDict[key][0].append(deepcopy(itemFilter(result)[sortDict[sortedKeys[count]]]))
595 
596  if len(directoryFilter(result)):
597  for directory in directoryFilter(result):
598  if not len(itemFilter(directory)):
599  continue
600  tmpDirElement = etree.XML('<directory></directory>')
601  tmpDirElement.attrib['name'] = directory.attrib['name']
602  if directory.attrib.get('thumbnail'):
603  tmpDirElement.attrib['thumbnail'] = directory.attrib['thumbnail']
604  else:
605  tmpDirElement.attrib['thumbnail'] = self.channel_icon
606  count = 0
607  for item in itemFilter(directory):
608  tmpDirElement.append(item)
609  if urlMax:
610  count+=1
611  if count == urlMax:
612  break
613  categoryElement.append(tmpDirElement)
614  else:
615  # Process the results through its XSLT stylesheet and append the results to the
616  # directory date and time order
617  count = 0
618  for item in itemFilter(result):
619  categoryElement.append(item)
620  if urlMax:
621  count+=1
622  if count == urlMax:
623  break
624 
625  # Add the last directory processed and the "Special" directories
626  if categoryElement is not None:
627  if len(itemFilter(categoryElement)):
628  channelTree.append(categoryElement)
629  # Add the special directories videos
630  for key in list(specialDirectoriesDict.keys()):
631  if len(itemFilter(specialDirectoriesDict[key][0])):
632  channelTree.append(specialDirectoriesDict[key][0])
633 
634  # Check that there was at least some items
635  if len(rssTree.xpath('//item')):
636  # Output the MNV Mashup results
637  sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
638  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
639 
640  sys.exit(0)
641  # end displayTreeView()
642 # end Videos() class
nv_python_libs.mashups.mashups_api.OutStreamEncoder.encoding
encoding
Definition: mashups_api.py:44
nv_python_libs.mashups.mashups_api.Videos.displayTreeView
def displayTreeView(self)
Definition: mashups_api.py:438
nv_python_libs.mashups.mashups_exceptions.MashupsConfigFileError
Definition: mashups_exceptions.py:42
nv_python_libs.mashups.mashups_api.Videos.getMashupsConfig
def getMashupsConfig(self)
Definition: mashups_api.py:200
nv_python_libs.mashups.mashups_api.Videos.log_name
log_name
Definition: mashups_api.py:138
nv_python_libs.mashups.mashups_api.OutStreamEncoder
Definition: mashups_api.py:39
nv_python_libs.mashups.mashups_exceptions.MashupsUrlError
Definition: mashups_exceptions.py:22
nv_python_libs.mashups.mashups_api.Videos.__init__
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
Definition: mashups_api.py:94
nv_python_libs.mashups.mashups_api.Videos.mashups_config
mashups_config
Definition: mashups_api.py:213
nv_python_libs.mashups.mashups_api.OutStreamEncoder.write
def write(self, obj)
Definition: mashups_api.py:48
nv_python_libs.mashups.mashups_api.Videos.error_messages
error_messages
Definition: mashups_api.py:155
nv_python_libs.mashups.mashups_api.Videos.getUserPreferences
def getUserPreferences(self)
Definition: mashups_api.py:220
nv_python_libs.mashups.mashups_api.OutStreamEncoder.__getattr__
def __getattr__(self, attr)
Definition: mashups_api.py:54
nv_python_libs.mashups.mashups_api.Videos
Definition: mashups_api.py:86
nv_python_libs.mashups.mashups_api.Videos.logger
logger
Definition: mashups_api.py:140
nv_python_libs.mashups.mashups_api.Videos.tree_dir_icon
tree_dir_icon
Definition: mashups_api.py:184
nv_python_libs.mashups.mashups_api.Videos.channel_icon
channel_icon
Definition: mashups_api.py:160
print
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)
Definition: vbi608extractor.cpp:29
nv_python_libs.mashups.mashups_api.Videos.searchForVideos
def searchForVideos(self, title, pagenumber)
End of Utility functions.
Definition: mashups_api.py:303
nv_python_libs.mashups.mashups_api.Videos.setTreeViewIcon
def setTreeViewIcon(self, dir_icon=None)
Start - Utility functions.
Definition: mashups_api.py:180
nv_python_libs.mashups.mashups_api.Videos.userPrefs
userPrefs
Definition: mashups_api.py:250
nv_python_libs.mashups.mashups_api.Videos.config
config
Definition: mashups_api.py:127
nv_python_libs.mashups.mashups_api.Videos.common
common
Definition: mashups_api.py:135
nv_python_libs.mashups.mashups_api.Videos.channel
channel
Definition: mashups_api.py:158
nv_python_libs.mashups.mashups_api.OutStreamEncoder.__init__
def __init__(self, outstream, encoding=None)
Definition: mashups_api.py:41
nv_python_libs.mashups.mashups_api.OutStreamEncoder.out
out
Definition: mashups_api.py:42
nv_python_libs.mashups.mashups_exceptions.MashupsUrlDownloadError
Definition: mashups_exceptions.py:47
find
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
Definition: dvbstreamhandler.cpp:363