MythTV  master
hulu_api.py
Go to the documentation of this file.
1 # -*- coding: UTF-8 -*-
2 
3 # ----------------------
4 # Name: hulu_api - Simple-to-use Python interface to the Hulu RSS feeds
5 # (http://www.hulu.com/)
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 Hulu Web site.
10 #
11 # License:Creative Commons GNU GPL v2
12 # (http://creativecommons.org/licenses/GPL/2.0/)
13 #-------------------------------------
14 __title__ ="hulu_api - Simple-to-use Python interface to the Hulu RSS feeds (http://www.hulu.com/)"
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 the Hulu Web site. These routines process RSS feeds
19 provided by Hulu (http://www.hulu.com/). The specific Hulu RSS feeds that are processed are controled through a user XML preference file usually found at
20 "~/.mythtv/MythNetvision/userGrabberPrefs/hulu.xml"
21 '''
22 
23 __version__="v0.1.3"
24 # 0.1.0 Initial development
25 # 0.1.1 Changed the logger to only output to stderr rather than a file
26 # 0.1.2 Removed the need for python MythTV bindings and added "%SHAREDIR%" to icon directory path
27 # 0.1.3 Dealt with a lack of description for a Hulu video
28 
29 import os, struct, sys, re, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
30 import logging
31 from socket import gethostname, gethostbyname
32 from threading import Thread
33 from copy import deepcopy
34 from operator import itemgetter, attrgetter
35 
36 from .hulu_exceptions import (HuluUrlError, HuluHttpError, HuluRssError, HuluVideoNotFound, HuluConfigFileError, HuluUrlDownloadError)
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 http://www.hulu.com/
88  This is done to support a common naming framework for all python Netvision plugins no matter their
89  site target.
90 
91  Supports search methods
92  The apikey is a not required to access http://www.hulu.com/
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 # Hulu 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 = 'Hulu_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  self.config['search_all_languages'] = search_all_languages
158 
159  self.error_messages = {'HuluUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'HuluHttpError': "! Error: An HTTP communications error with the Hulu was raised (%s)\n", 'HuluRssError': "! Error: Invalid RSS meta data\nwas received from the Hulu error (%s). Skipping item.\n", 'HuluVideoNotFound': "! Error: Video search with the Hulu did not return any results (%s)\n", 'HuluConfigFileError': "! Error: hulu_config.xml file missing\nit should be located in and named as (%s).\n", 'HuluUrlDownloadError': "! Error: Downloading a RSS feed or Web page (%s).\n", }
160 
161  # Channel details and search results
162  self.channel = {'channel_title': 'Hulu', 'channel_link': 'http://www.hulu.com/', 'channel_description': "Hulu.com is a free online video service that offers hit TV shows including Family Guy, 30 Rock, and the Daily Show with Jon Stewart, etc.", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0}
163 
164  # Season and Episode detection regex patterns
165  self.s_e_Patterns = [
166  # Title: "s18 | e87"
167  re.compile('''^.+?[Ss](?P<seasno>[0-9]+).*.+?[Ee](?P<epno>[0-9]+).*$''', re.UNICODE),
168  # Description: "season 1, episode 5"
169  re.compile('''^.+?season\\ (?P<seasno>[0-9]+).*.+?episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
170  # Thubnail: "http://media.thewb.com/thewb/images/thumbs/firefly/01/firefly_01_07.jpg"
171  re.compile('''(?P<seriesname>[^_]+)\\_(?P<seasno>[0-9]+)\\_(?P<epno>[0-9]+).*$''', re.UNICODE),
172  ]
173 
174  self.channel_icon = '%SHAREDIR%/mythnetvision/icons/hulu.png'
175 
176  self.config['image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
177  # end __init__()
178 
179 
184 
185  def getHuluConfig(self):
186  ''' Read the MNV Hulu grabber "hulu_config.xml" configuration file
187  return nothing
188  '''
189  # Read the grabber hulu_config.xml configuration file
190  url = 'file://%s/nv_python_libs/configs/XML/hulu_config.xml' % (baseProcessingDir, )
191  if not os.path.isfile(url[7:]):
192  raise HuluConfigFileError(self.error_messages['HuluConfigFileError'] % (url[7:], ))
193 
194  if self.config['debug_enabled']:
195  print(url)
196  print()
197  try:
198  self.hulu_config = etree.parse(url)
199  except Exception as e:
200  raise HuluUrlError(self.error_messages['HuluUrlError'] % (url, errormsg))
201  return
202  # end getHuluConfig()
203 
204 
206  '''Read the hulu_config.xml and user preference hulu.xml file.
207  If the hulu.xml file does not exist then copy the default.
208  return nothing
209  '''
210  # Get hulu_config.xml
211  self.getHuluConfig()
212 
213  # Check if the hulu.xml file exists
214  userPreferenceFile = self.hulu_config.find('userPreferenceFile').text
215  if userPreferenceFile[0] == '~':
216  self.hulu_config.find('userPreferenceFile').text = "%s%s" % (os.path.expanduser("~"), userPreferenceFile[1:])
217 
218  # If the user config file does not exists then copy one the default
219  if not os.path.isfile(self.hulu_config.find('userPreferenceFile').text):
220  # Make the necessary directories if they do not already exist
221  prefDir = self.hulu_config.find('userPreferenceFile').text.replace('/hulu.xml', '')
222  if not os.path.isdir(prefDir):
223  os.makedirs(prefDir)
224  defaultConfig = '%s/nv_python_libs/configs/XML/defaultUserPrefs/hulu.xml' % (baseProcessingDir, )
225  shutil.copy2(defaultConfig, self.hulu_config.find('userPreferenceFile').text)
226 
227  # Read the grabber hulu_config.xml configuration file
228  url = 'file://%s' % (self.hulu_config.find('userPreferenceFile').text, )
229  if self.config['debug_enabled']:
230  print(url)
231  print()
232  try:
233  self.userPrefs = etree.parse(url)
234  except Exception as e:
235  raise HuluUrlError(self.error_messages['HuluUrlError'] % (url, errormsg))
236  return
237  # end getUserPreferences()
238 
239  def getSeasonEpisode(self, title, desc=None, thumbnail=None):
240  ''' Check is there is any season or episode number information in an item's title
241  return array of season and/or episode numbers, Series name (only if title empty)
242  return array with None values
243  '''
244  s_e = [None, None, None]
245  if title:
246  match = self.s_e_Patterns[0].match(title)
247  if match:
248  s_e[0], s_e[1] = match.groups()
249  if not s_e[0] and desc:
250  match = self.s_e_Patterns[1].match(desc)
251  if match:
252  s_e[0], s_e[1] = match.groups()
253  if thumbnail and not title:
254  filepath, filename = os.path.split( thumbnail.replace('http:/', '') )
255  match = self.s_e_Patterns[2].match(filename)
256  if match:
257  s_e[2], s_e[0], s_e[1] = match.groups()
258  s_e[0] = '%s' % int(s_e[0])
259  s_e[1] = '%s' % int(s_e[1])
260  s_e[2] = "".join([w.capitalize() for w in re.split(re.compile(r"[\W_]*"), s_e[2].replace('_', ' ').replace('-', ' '))])
261  return s_e
262  # end getSeasonEpisode()
263 
264 
269 
270 
271  def searchTitle(self, title, pagenumber, pagelen):
272  '''Key word video search of the Hulu web site
273  return an array of matching item elements
274  return
275  '''
276  # Save the origninal URL
277  orgUrl = self.hulu_config.find('searchURLS').xpath(".//href")[0].text
278 
279  url = self.hulu_config.find('searchURLS').xpath(".//href")[0].text.replace('PAGENUM', str(pagenumber)).replace('SEARCHTERM', urllib.parse.quote_plus(title.encode("utf-8")))
280 
281  if self.config['debug_enabled']:
282  print(url)
283  print()
284 
285  self.hulu_config.find('searchURLS').xpath(".//href")[0].text = url
286 
287  # Perform a search
288  try:
289  resultTree = self.common.getUrlData(self.hulu_config.find('searchURLS'))
290  except Exception as errormsg:
291  # Restore the origninal URL
292  self.hulu_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
293  raise HuluUrlDownloadError(self.error_messages['HuluUrlDownloadError'] % (errormsg))
294 
295  # Restore the origninal URL
296  self.hulu_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
297 
298  if resultTree is None:
299  raise HuluVideoNotFound("No Hulu Video matches found for search value (%s)" % title)
300 
301  searchResults = resultTree.xpath('//result//a[@href!="#"]')
302  if not len(searchResults):
303  raise HuluVideoNotFound("No Hulu Video matches found for search value (%s)" % title)
304 
305  if self.config['debug_enabled']:
306  print("resultTree: count(%s)" % len(searchResults))
307  print()
308 
309  # Hulu search results do not have a pubDate so use the current data time
310  # e.g. "Sun, 06 Jan 2008 21:44:36 GMT"
311  pubDate = datetime.datetime.now().strftime(self.common.pubDateFormat)
312 
313  # Translate the search results into MNV RSS item format
314  titleFilter = etree.XPath(".//img")
315  thumbnailFilter = etree.XPath(".//img")
316  itemLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
317  itemThumbnail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
318  itemDict = {}
319  for result in searchResults:
320  tmpLink = result.attrib['href']
321  if not tmpLink: # Make sure that this result actually has a video
322  continue
323  huluItem = etree.XML(self.common.mnvItem)
324  # Extract and massage data
325  link = self.common.ampReplace(tmpLink)
326  tmpTitleText = titleFilter(result)[0].attrib['alt'].strip()
327  tmpList = tmpTitleText.split(':')
328  title = self.common.massageText(tmpList[0].strip())
329  if len(tmpList) > 1:
330  description = self.common.massageText(tmpList[1].strip())
331  else:
332  description = ''
333 
334  # Insert data into a new item element
335  huluItem.find('title').text = title
336  huluItem.find('author').text = 'Hulu'
337  huluItem.find('pubDate').text = pubDate
338  huluItem.find('description').text = description
339  huluItem.find('link').text = link
340  itemThumbnail(huluItem)[0].attrib['url'] = self.common.ampReplace(thumbnailFilter(result)[0].attrib['src'])
341  itemLink(huluItem)[0].attrib['url'] = link
342  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = 'us'
343  s_e = self.getSeasonEpisode(title, description, itemThumbnail(huluItem)[0].attrib['url'])
344  if s_e[0]:
345  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
346  if s_e[1]:
347  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
348  if not title and s_e[2]:
349  huluItem.find('title').text = s_e[2]
350  itemDict[link] = huluItem
351 
352  if not len(list(itemDict.keys())):
353  raise HuluVideoNotFound("No Hulu Video matches found for search value (%s)" % title)
354 
355  # Set the number of search results returned
356  self.channel['channel_numresults'] = len(itemDict)
357 
358  # Check if there are any more pages
359  lastPage = resultTree.xpath('//result//a[@alt="Go to the last page"]')
360  morePages = False
361  if len(lastPage):
362  try:
363  if pagenumber < lastPage[0].text:
364  morePages = True
365  except:
366  pass
367 
368  return [itemDict, morePages]
369  # end searchTitle()
370 
371 
372  def searchForVideos(self, title, pagenumber):
373  """Common name for a video search. Used to interface with MythTV plugin NetVision
374  """
375  # Get the user preferences
376  try:
377  self.getUserPreferences()
378  except Exception as e:
379  sys.stderr.write('%s' % e)
380  sys.exit(1)
381 
382  if self.config['debug_enabled']:
383  print("self.userPrefs:")
384  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
385  print()
386 
387 
388  # Easier for debugging
389 # print self.searchTitle(title, pagenumber, self.page_limit)
390 # print
391 # sys.exit()
392 
393  try:
394  data = self.searchTitle(title, pagenumber, self.page_limit)
395  except HuluVideoNotFound as msg:
396  sys.stderr.write("%s\n" % msg)
397  sys.exit(0)
398  except HuluUrlError as msg:
399  sys.stderr.write('%s\n' % msg)
400  sys.exit(1)
401  except HuluHttpError as msg:
402  sys.stderr.write(self.error_messages['HuluHttpError'] % msg)
403  sys.exit(1)
404  except HuluRssError as msg:
405  sys.stderr.write(self.error_messages['HuluRssError'] % msg)
406  sys.exit(1)
407  except Exception as e:
408  sys.stderr.write("! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
409  sys.exit(1)
410 
411  if self.config['debug_enabled']:
412  print("Number of items returned by the search(%s)" % len(list(data[0].keys())))
413  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
414  print()
415 
416  # Create RSS element tree
417  rssTree = etree.XML(self.common.mnvRSS+'</rss>')
418 
419  # Set the paging values
420  itemCount = len(list(data[0].keys()))
421  if data[1]:
422  self.channel['channel_returned'] = itemCount
423  self.channel['channel_startindex'] = self.page_limit*(int(pagenumber)-1)
424  self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
425  else:
426  self.channel['channel_returned'] = itemCount
427  self.channel['channel_startindex'] = itemCount+(self.page_limit*(int(pagenumber)-1))
428  self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1))
429 
430  # Add the Channel element tree
431  channelTree = self.common.mnvChannelElement(self.channel)
432  rssTree.append(channelTree)
433 
434  lastKey = None
435  for key in sorted(data[0].keys()):
436  if lastKey != key:
437  channelTree.append(data[0][key])
438  lastKey = key
439 
440  # Output the MNV search results
441  sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
442  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
443  sys.exit(0)
444  # end searchForVideos()
445 
446  def displayTreeView(self):
447  '''Gather the Hulu feeds then get a max page of videos meta data in each of them
448  Display the results and exit
449  '''
450  # Get the user preferences
451  try:
452  self.getUserPreferences()
453  except Exception as e:
454  sys.stderr.write('%s' % e)
455  sys.exit(1)
456 
457  if self.config['debug_enabled']:
458  print("self.userPrefs:")
459  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
460  print()
461 
462  # Massage channel icon
463  self.channel_icon = self.common.ampReplace(self.channel_icon)
464 
465  # Create RSS element tree
466  rssTree = etree.XML(self.common.mnvRSS+'</rss>')
467 
468  # Add the Channel element tree
469  channelTree = self.common.mnvChannelElement(self.channel)
470  rssTree.append(channelTree)
471 
472  # Process any user specified searches
473  searchResultTree = []
474  searchFilter = etree.XPath("//item")
475  userSearchStrings = 'userSearchStrings'
476  if self.userPrefs.find(userSearchStrings) is not None:
477  userSearch = self.userPrefs.find(userSearchStrings).xpath('./userSearch')
478  if len(userSearch):
479  for searchDetails in userSearch:
480  try:
481  data = self.searchTitle(searchDetails.find('searchTerm').text, 1, self.page_limit)
482  except HuluVideoNotFound as msg:
483  sys.stderr.write("%s\n" % msg)
484  continue
485  except HuluUrlError as msg:
486  sys.stderr.write('%s\n' % msg)
487  continue
488  except HuluHttpError as msg:
489  sys.stderr.write(self.error_messages['HuluHttpError'] % msg)
490  continue
491  except HuluRssError as msg:
492  sys.stderr.write(self.error_messages['HuluRssError'] % msg)
493  continue
494  except Exception as e:
495  sys.stderr.write("! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
496  continue
497  dirElement = etree.XML('<directory></directory>')
498  dirElement.attrib['name'] = self.common.massageText(searchDetails.find('dirName').text)
499  dirElement.attrib['thumbnail'] = self.channel_icon
500  lastKey = None
501  for key in sorted(data[0].keys()):
502  if lastKey != key:
503  dirElement.append(data[0][key])
504  lastKey = key
505  channelTree.append(dirElement)
506  continue
507 
508  # Create a structure of feeds that can be concurrently downloaded
509  rssData = etree.XML('<xml></xml>')
510  for feedType in ['treeviewURLS', ]:
511  if self.userPrefs.find(feedType) is None:
512  continue
513  if not len(self.userPrefs.find(feedType).xpath('./url')):
514  continue
515  for rssFeed in self.userPrefs.find(feedType).xpath('./url'):
516  urlEnabled = rssFeed.attrib.get('enabled')
517  if urlEnabled == 'false':
518  continue
519  urlName = rssFeed.attrib.get('name')
520  if urlName:
521  uniqueName = '%s;%s' % (urlName, rssFeed.text)
522  else:
523  uniqueName = 'RSS;%s' % (rssFeed.text)
524  url = etree.XML('<url></url>')
525  etree.SubElement(url, "name").text = uniqueName
526  etree.SubElement(url, "href").text = rssFeed.text
527  etree.SubElement(url, "filter").text = "//channel/title"
528  etree.SubElement(url, "filter").text = "//item"
529  etree.SubElement(url, "parserType").text = 'xml'
530  rssData.append(url)
531 
532  if self.config['debug_enabled']:
533  print("rssData:")
534  sys.stdout.write(etree.tostring(rssData, encoding='UTF-8', pretty_print=True))
535  print()
536 
537  # Get the RSS Feed data
538  if rssData.find('url') is not None:
539  try:
540  resultTree = self.common.getUrlData(rssData)
541  except Exception as errormsg:
542  raise HuluUrlDownloadError(self.error_messages['HuluUrlDownloadError'] % (errormsg))
543  if self.config['debug_enabled']:
544  print("resultTree:")
545  sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
546  print()
547 
548  # Process each directory of the user preferences that have an enabled rss feed
549  itemFilter = etree.XPath('.//item', namespaces=self.common.namespaces)
550  titleFilter = etree.XPath('.//title', namespaces=self.common.namespaces)
551  linkFilter = etree.XPath('.//link', namespaces=self.common.namespaces)
552  descriptionFilter = etree.XPath('.//description', namespaces=self.common.namespaces)
553  authorFilter = etree.XPath('.//media:credit', namespaces=self.common.namespaces)
554  pubDateFilter = etree.XPath('.//pubDate', namespaces=self.common.namespaces)
555  feedFilter = etree.XPath('//url[text()=$url]')
556  descFilter2 = etree.XPath('.//p')
557  itemThumbNail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
558  itemDwnLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
559  itemLanguage = etree.XPath('.//media:content', namespaces=self.common.namespaces)
560  itemDuration = etree.XPath('.//media:content', namespaces=self.common.namespaces)
561  rssName = etree.XPath('title', namespaces=self.common.namespaces)
562  categoryDir = None
563  categoryElement = None
564  for result in resultTree.findall('results'):
565  names = result.find('name').text.split(';')
566  names[0] = self.common.massageText(names[0])
567  if names[0] == 'RSS':
568  names[0] = self.common.massageText(rssName(result.find('result'))[0].text.replace('Hulu - ', ''))
569  count = 0
570  urlMax = None
571  url = feedFilter(self.userPrefs, url=names[1])
572  if len(url):
573  if url[0].attrib.get('max'):
574  try:
575  urlMax = int(url[0].attrib.get('max'))
576  except:
577  pass
578  elif url[0].getparent().attrib.get('globalmax'):
579  try:
580  urlMax = int(url[0].getparent().attrib.get('globalmax'))
581  except:
582  pass
583  if urlMax == 0:
584  urlMax = None
585  channelThumbnail = self.channel_icon
586  channelLanguage = 'en'
587  # Create a new directory and/or subdirectory if required
588  if names[0] != categoryDir:
589  if categoryDir is not None:
590  channelTree.append(categoryElement)
591  categoryElement = etree.XML('<directory></directory>')
592  categoryElement.attrib['name'] = names[0]
593  categoryElement.attrib['thumbnail'] = self.channel_icon
594  categoryDir = names[0]
595 
596  if self.config['debug_enabled']:
597  print("Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names))
598  print()
599 
600  # Convert each RSS item into a MNV item
601  for itemData in itemFilter(result.find('result')):
602  huluItem = etree.XML(self.common.mnvItem)
603  link = self.common.ampReplace(linkFilter(itemData)[0].text)
604  # Convert the pubDate "Sat, 01 May 2010 08:18:05 -0000" to a MNV pubdate format
605  pubdate = pubDateFilter(itemData)[0].text[:-5]+'GMT'
606 
607  # Extract and massage data also insert data into a new item element
608  huluItem.find('title').text = self.common.massageText(titleFilter(itemData)[0].text.strip())
609  if authorFilter(itemData)[0].text:
610  huluItem.find('author').text = self.common.massageText(authorFilter(itemData)[0].text.strip())
611  else:
612  huluItem.find('author').text = 'Hulu'
613  huluItem.find('pubDate').text = pubdate
614  description = etree.HTML(etree.tostring(descriptionFilter(itemData)[0], method="text", encoding=str).strip())
615  if descFilter2(description)[0].text is not None:
616  huluItem.find('description').text = self.common.massageText(descFilter2(description)[0].text.strip())
617  else:
618  huluItem.find('description').text = ''
619  for e in descFilter2(description)[1]:
620  eText = etree.tostring(e, method="text", encoding=str)
621  if not eText:
622  continue
623  if eText.startswith('Duration: '):
624  eText = eText.replace('Duration: ', '').strip()
625  videoSeconds = False
626  videoDuration = eText.split(':')
627  try:
628  if len(videoDuration) == 1:
629  videoSeconds = int(videoDuration[0])
630  elif len(videoDuration) == 2:
631  videoSeconds = int(videoDuration[0])*60+int(videoDuration[1])
632  elif len(videoDuration) == 3:
633  videoSeconds = int(videoDuration[0])*3600+int(videoDuration[1])*60+int(videoDuration[2])
634  if videoSeconds:
635  itemDwnLink(huluItem)[0].attrib['duration'] = str(videoSeconds)
636  except:
637  pass
638  elif eText.startswith('Rating: '):
639  eText = eText.replace('Rating: ', '').strip()
640  videoRating = eText.split(' ')
641  huluItem.find('rating').text = videoRating[0]
642  continue
643  huluItem.find('link').text = link
644  itemDwnLink(huluItem)[0].attrib['url'] = link
645  try:
646  itemThumbNail(huluItem)[0].attrib['url'] = self.common.ampReplace(itemThumbNail(itemData)[0].attrib['url'])
647  except IndexError:
648  pass
649  itemLanguage(huluItem)[0].attrib['lang'] = channelLanguage
650  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = 'us'
651  s_e = self.getSeasonEpisode(huluItem.find('title').text, huluItem.find('description').text, itemThumbNail(huluItem)[0].attrib['url'])
652  if s_e[0]:
653  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
654  if s_e[1]:
655  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
656  if not huluItem.find('title').text and s_e[2]:
657  huluItem.find('title').text = s_e[2]
658  categoryElement.append(huluItem)
659  if urlMax: # Check of the maximum items to processes has been met
660  count+=1
661  if count > urlMax:
662  break
663 
664  # Add the last directory processed
665  if categoryElement is not None:
666  if categoryElement.xpath('.//item') is not None:
667  channelTree.append(categoryElement)
668 
669  # Check that there was at least some items
670  if len(rssTree.xpath('//item')):
671  # Output the MNV search results
672  sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
673  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
674 
675  sys.exit(0)
676  # end displayTreeView()
677 # end Videos() class
nv_python_libs.hulu.hulu_api.Videos.getSeasonEpisode
def getSeasonEpisode(self, title, desc=None, thumbnail=None)
Definition: hulu_api.py:239
nv_python_libs.hulu.hulu_api.OutStreamEncoder.__getattr__
def __getattr__(self, attr)
Definition: hulu_api.py:54
nv_python_libs.hulu.hulu_api.OutStreamEncoder.__init__
def __init__(self, outstream, encoding=None)
Definition: hulu_api.py:41
nv_python_libs.hulu.hulu_api.Videos.userPrefs
userPrefs
Definition: hulu_api.py:233
nv_python_libs.hulu.hulu_api.Videos.channel_icon
channel_icon
Definition: hulu_api.py:165
nv_python_libs.hulu.hulu_api.Videos.searchTitle
def searchTitle(self, title, pagenumber, pagelen)
End of Utility functions.
Definition: hulu_api.py:271
nv_python_libs.hulu.hulu_api.Videos.getHuluConfig
def getHuluConfig(self)
Start - Utility functions.
Definition: hulu_api.py:185
nv_python_libs.hulu.hulu_api.OutStreamEncoder.encoding
encoding
Definition: hulu_api.py:44
nv_python_libs.hulu.hulu_api.OutStreamEncoder
Definition: hulu_api.py:39
nv_python_libs.hulu.hulu_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: hulu_api.py:94
nv_python_libs.hulu.hulu_api.OutStreamEncoder.write
def write(self, obj)
Definition: hulu_api.py:48
nv_python_libs.hulu.hulu_api.Videos.channel
channel
Definition: hulu_api.py:153
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.hulu.hulu_api.Videos.error_messages
error_messages
Definition: hulu_api.py:150
nv_python_libs.hulu.hulu_api.OutStreamEncoder.out
out
Definition: hulu_api.py:42
nv_python_libs.hulu.hulu_api.Videos.logger
logger
Definition: hulu_api.py:140
nv_python_libs.hulu.hulu_api.Videos.getUserPreferences
def getUserPreferences(self)
Definition: hulu_api.py:205
nv_python_libs.hulu.hulu_api.Videos.log_name
log_name
Definition: hulu_api.py:138
nv_python_libs.hulu.hulu_api.Videos.config
config
Definition: hulu_api.py:127
nv_python_libs.hulu.hulu_exceptions.HuluVideoNotFound
Definition: hulu_exceptions.py:37
nv_python_libs.hulu.hulu_exceptions.HuluUrlError
Definition: hulu_exceptions.py:22
nv_python_libs.hulu.hulu_api.Videos.s_e_Patterns
s_e_Patterns
Definition: hulu_api.py:156
nv_python_libs.hulu.hulu_exceptions.HuluConfigFileError
Definition: hulu_exceptions.py:42
nv_python_libs.hulu.hulu_api.Videos.displayTreeView
def displayTreeView(self)
Definition: hulu_api.py:446
nv_python_libs.hulu.hulu_api.Videos
Definition: hulu_api.py:86
nv_python_libs.hulu.hulu_api.Videos.hulu_config
hulu_config
Definition: hulu_api.py:198
nv_python_libs.hulu.hulu_api.Videos.common
common
Definition: hulu_api.py:135
nv_python_libs.hulu.hulu_api.Videos.searchForVideos
def searchForVideos(self, title, pagenumber)
Definition: hulu_api.py:372
nv_python_libs.hulu.hulu_exceptions.HuluUrlDownloadError
Definition: hulu_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