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 
71 class Videos(object):
72  """Main interface to http://www.hulu.com/
73  This is done to support a common naming framework for all python Netvision plugins no matter their
74  site target.
75 
76  Supports search methods
77  The apikey is a not required to access http://www.hulu.com/
78  """
79  def __init__(self,
80  apikey,
81  mythtv = True,
82  interactive = False,
83  select_first = False,
84  debug = False,
85  custom_ui = None,
86  language = None,
87  search_all_languages = False,
88  ):
89  """apikey (str/unicode):
90  Specify the target site API key. Applications need their own key in some cases
91 
92  mythtv (True/False):
93  When True, the returned meta data is being returned has the key and values massaged to match MythTV
94  When False, the returned meta data is being returned matches what target site returned
95 
96  interactive (True/False): (This option is not supported by all target site apis)
97  When True, uses built-in console UI is used to select the correct show.
98  When False, the first search result is used.
99 
100  select_first (True/False): (This option is not supported currently implemented in any grabbers)
101  Automatically selects the first series search result (rather
102  than showing the user a list of more than one series).
103  Is overridden by interactive = False, or specifying a custom_ui
104 
105  debug (True/False):
106  shows verbose debugging information
107 
108  custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
109  A callable subclass of interactive class (overrides interactive option)
110 
111  language (2 character language abbreviation): (This option is not supported by all target site apis)
112  The language of the returned data. Is also the language search
113  uses. Default is "en" (English). For full list, run..
114 
115  search_all_languages (True/False): (This option is not supported by all target site apis)
116  By default, a Netvision grabber will only search in the language specified using
117  the language option. When this is True, it will search for the
118  show in any language
119 
120  """
121  self.config = {}
122 
123  if apikey is not None:
124  self.config['apikey'] = apikey
125  else:
126  pass # Hulu does not require an apikey
127 
128  self.config['debug_enabled'] = debug # show debugging messages
129  self.common = common
130  self.common.debug = debug # Set the common function debug level
131 
132  self.log_name = 'Hulu_Grabber'
133  self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
134  self.logger = self.common.logger # Setups the logger (self.log.debug() etc)
135 
136  self.config['custom_ui'] = custom_ui
137 
138  self.config['interactive'] = interactive
139 
140  self.config['select_first'] = select_first
141 
142  self.config['search_all_languages'] = search_all_languages
143 
144  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", }
145 
146  # Channel details and search results
147  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}
148 
149  # Season and Episode detection regex patterns
150  self.s_e_Patterns = [
151  # Title: "s18 | e87"
152  re.compile('''^.+?[Ss](?P<seasno>[0-9]+).*.+?[Ee](?P<epno>[0-9]+).*$''', re.UNICODE),
153  # Description: "season 1, episode 5"
154  re.compile('''^.+?season\\ (?P<seasno>[0-9]+).*.+?episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
155  # Thubnail: "http://media.thewb.com/thewb/images/thumbs/firefly/01/firefly_01_07.jpg"
156  re.compile('''(?P<seriesname>[^_]+)\\_(?P<seasno>[0-9]+)\\_(?P<epno>[0-9]+).*$''', re.UNICODE),
157  ]
158 
159  self.channel_icon = '%SHAREDIR%/mythnetvision/icons/hulu.png'
160 
161  self.config['image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
162  # end __init__()
163 
164 
169 
170  def getHuluConfig(self):
171  ''' Read the MNV Hulu grabber "hulu_config.xml" configuration file
172  return nothing
173  '''
174  # Read the grabber hulu_config.xml configuration file
175  url = 'file://%s/nv_python_libs/configs/XML/hulu_config.xml' % (baseProcessingDir, )
176  if not os.path.isfile(url[7:]):
177  raise HuluConfigFileError(self.error_messages['HuluConfigFileError'] % (url[7:], ))
178 
179  if self.config['debug_enabled']:
180  print(url)
181  print()
182  try:
183  self.hulu_config = etree.parse(url)
184  except Exception as e:
185  raise HuluUrlError(self.error_messages['HuluUrlError'] % (url, errormsg))
186  return
187  # end getHuluConfig()
188 
189 
191  '''Read the hulu_config.xml and user preference hulu.xml file.
192  If the hulu.xml file does not exist then copy the default.
193  return nothing
194  '''
195  # Get hulu_config.xml
196  self.getHuluConfig()
197 
198  # Check if the hulu.xml file exists
199  userPreferenceFile = self.hulu_config.find('userPreferenceFile').text
200  if userPreferenceFile[0] == '~':
201  self.hulu_config.find('userPreferenceFile').text = "%s%s" % (os.path.expanduser("~"), userPreferenceFile[1:])
202 
203  # If the user config file does not exists then copy one the default
204  if not os.path.isfile(self.hulu_config.find('userPreferenceFile').text):
205  # Make the necessary directories if they do not already exist
206  prefDir = self.hulu_config.find('userPreferenceFile').text.replace('/hulu.xml', '')
207  if not os.path.isdir(prefDir):
208  os.makedirs(prefDir)
209  defaultConfig = '%s/nv_python_libs/configs/XML/defaultUserPrefs/hulu.xml' % (baseProcessingDir, )
210  shutil.copy2(defaultConfig, self.hulu_config.find('userPreferenceFile').text)
211 
212  # Read the grabber hulu_config.xml configuration file
213  url = 'file://%s' % (self.hulu_config.find('userPreferenceFile').text, )
214  if self.config['debug_enabled']:
215  print(url)
216  print()
217  try:
218  self.userPrefs = etree.parse(url)
219  except Exception as e:
220  raise HuluUrlError(self.error_messages['HuluUrlError'] % (url, errormsg))
221  return
222  # end getUserPreferences()
223 
224  def getSeasonEpisode(self, title, desc=None, thumbnail=None):
225  ''' Check is there is any season or episode number information in an item's title
226  return array of season and/or episode numbers, Series name (only if title empty)
227  return array with None values
228  '''
229  s_e = [None, None, None]
230  if title:
231  match = self.s_e_Patterns[0].match(title)
232  if match:
233  s_e[0], s_e[1] = match.groups()
234  if not s_e[0] and desc:
235  match = self.s_e_Patterns[1].match(desc)
236  if match:
237  s_e[0], s_e[1] = match.groups()
238  if thumbnail and not title:
239  filepath, filename = os.path.split( thumbnail.replace('http:/', '') )
240  match = self.s_e_Patterns[2].match(filename)
241  if match:
242  s_e[2], s_e[0], s_e[1] = match.groups()
243  s_e[0] = '%s' % int(s_e[0])
244  s_e[1] = '%s' % int(s_e[1])
245  s_e[2] = "".join([w.capitalize() for w in re.split(re.compile(r"[\W_]*"), s_e[2].replace('_', ' ').replace('-', ' '))])
246  return s_e
247  # end getSeasonEpisode()
248 
249 
254 
255 
256  def searchTitle(self, title, pagenumber, pagelen):
257  '''Key word video search of the Hulu web site
258  return an array of matching item elements
259  return
260  '''
261  # Save the origninal URL
262  orgUrl = self.hulu_config.find('searchURLS').xpath(".//href")[0].text
263 
264  url = self.hulu_config.find('searchURLS').xpath(".//href")[0].text.replace('PAGENUM', str(pagenumber)).replace('SEARCHTERM', urllib.parse.quote_plus(title.encode("utf-8")))
265 
266  if self.config['debug_enabled']:
267  print(url)
268  print()
269 
270  self.hulu_config.find('searchURLS').xpath(".//href")[0].text = url
271 
272  # Perform a search
273  try:
274  resultTree = self.common.getUrlData(self.hulu_config.find('searchURLS'))
275  except Exception as errormsg:
276  # Restore the origninal URL
277  self.hulu_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
278  raise HuluUrlDownloadError(self.error_messages['HuluUrlDownloadError'] % (errormsg))
279 
280  # Restore the origninal URL
281  self.hulu_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
282 
283  if resultTree is None:
284  raise HuluVideoNotFound("No Hulu Video matches found for search value (%s)" % title)
285 
286  searchResults = resultTree.xpath('//result//a[@href!="#"]')
287  if not len(searchResults):
288  raise HuluVideoNotFound("No Hulu Video matches found for search value (%s)" % title)
289 
290  if self.config['debug_enabled']:
291  print("resultTree: count(%s)" % len(searchResults))
292  print()
293 
294  # Hulu search results do not have a pubDate so use the current data time
295  # e.g. "Sun, 06 Jan 2008 21:44:36 GMT"
296  pubDate = datetime.datetime.now().strftime(self.common.pubDateFormat)
297 
298  # Translate the search results into MNV RSS item format
299  titleFilter = etree.XPath(".//img")
300  thumbnailFilter = etree.XPath(".//img")
301  itemLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
302  itemThumbnail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
303  itemDict = {}
304  for result in searchResults:
305  tmpLink = result.attrib['href']
306  if not tmpLink: # Make sure that this result actually has a video
307  continue
308  huluItem = etree.XML(self.common.mnvItem)
309  # Extract and massage data
310  link = self.common.ampReplace(tmpLink)
311  tmpTitleText = titleFilter(result)[0].attrib['alt'].strip()
312  tmpList = tmpTitleText.split(':')
313  title = self.common.massageText(tmpList[0].strip())
314  if len(tmpList) > 1:
315  description = self.common.massageText(tmpList[1].strip())
316  else:
317  description = ''
318 
319  # Insert data into a new item element
320  huluItem.find('title').text = title
321  huluItem.find('author').text = 'Hulu'
322  huluItem.find('pubDate').text = pubDate
323  huluItem.find('description').text = description
324  huluItem.find('link').text = link
325  itemThumbnail(huluItem)[0].attrib['url'] = self.common.ampReplace(thumbnailFilter(result)[0].attrib['src'])
326  itemLink(huluItem)[0].attrib['url'] = link
327  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = 'us'
328  s_e = self.getSeasonEpisode(title, description, itemThumbnail(huluItem)[0].attrib['url'])
329  if s_e[0]:
330  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
331  if s_e[1]:
332  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
333  if not title and s_e[2]:
334  huluItem.find('title').text = s_e[2]
335  itemDict[link] = huluItem
336 
337  if not len(list(itemDict.keys())):
338  raise HuluVideoNotFound("No Hulu Video matches found for search value (%s)" % title)
339 
340  # Set the number of search results returned
341  self.channel['channel_numresults'] = len(itemDict)
342 
343  # Check if there are any more pages
344  lastPage = resultTree.xpath('//result//a[@alt="Go to the last page"]')
345  morePages = False
346  if len(lastPage):
347  try:
348  if pagenumber < lastPage[0].text:
349  morePages = True
350  except:
351  pass
352 
353  return [itemDict, morePages]
354  # end searchTitle()
355 
356 
357  def searchForVideos(self, title, pagenumber):
358  """Common name for a video search. Used to interface with MythTV plugin NetVision
359  """
360  # Get the user preferences
361  try:
362  self.getUserPreferences()
363  except Exception as e:
364  sys.stderr.write('%s' % e)
365  sys.exit(1)
366 
367  if self.config['debug_enabled']:
368  print("self.userPrefs:")
369  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
370  print()
371 
372 
373  # Easier for debugging
374 # print self.searchTitle(title, pagenumber, self.page_limit)
375 # print
376 # sys.exit()
377 
378  try:
379  data = self.searchTitle(title, pagenumber, self.page_limit)
380  except HuluVideoNotFound as msg:
381  sys.stderr.write("%s\n" % msg)
382  sys.exit(0)
383  except HuluUrlError as msg:
384  sys.stderr.write('%s\n' % msg)
385  sys.exit(1)
386  except HuluHttpError as msg:
387  sys.stderr.write(self.error_messages['HuluHttpError'] % msg)
388  sys.exit(1)
389  except HuluRssError as msg:
390  sys.stderr.write(self.error_messages['HuluRssError'] % msg)
391  sys.exit(1)
392  except Exception as e:
393  sys.stderr.write("! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
394  sys.exit(1)
395 
396  if self.config['debug_enabled']:
397  print("Number of items returned by the search(%s)" % len(list(data[0].keys())))
398  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
399  print()
400 
401  # Create RSS element tree
402  rssTree = etree.XML(self.common.mnvRSS+'</rss>')
403 
404  # Set the paging values
405  itemCount = len(list(data[0].keys()))
406  if data[1]:
407  self.channel['channel_returned'] = itemCount
408  self.channel['channel_startindex'] = self.page_limit*(int(pagenumber)-1)
409  self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
410  else:
411  self.channel['channel_returned'] = itemCount
412  self.channel['channel_startindex'] = itemCount+(self.page_limit*(int(pagenumber)-1))
413  self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1))
414 
415  # Add the Channel element tree
416  channelTree = self.common.mnvChannelElement(self.channel)
417  rssTree.append(channelTree)
418 
419  lastKey = None
420  for key in sorted(data[0].keys()):
421  if lastKey != key:
422  channelTree.append(data[0][key])
423  lastKey = key
424 
425  # Output the MNV search results
426  sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
427  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
428  sys.exit(0)
429  # end searchForVideos()
430 
431  def displayTreeView(self):
432  '''Gather the Hulu feeds then get a max page of videos meta data in each of them
433  Display the results and exit
434  '''
435  # Get the user preferences
436  try:
437  self.getUserPreferences()
438  except Exception as e:
439  sys.stderr.write('%s' % e)
440  sys.exit(1)
441 
442  if self.config['debug_enabled']:
443  print("self.userPrefs:")
444  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
445  print()
446 
447  # Massage channel icon
448  self.channel_icon = self.common.ampReplace(self.channel_icon)
449 
450  # Create RSS element tree
451  rssTree = etree.XML(self.common.mnvRSS+'</rss>')
452 
453  # Add the Channel element tree
454  channelTree = self.common.mnvChannelElement(self.channel)
455  rssTree.append(channelTree)
456 
457  # Process any user specified searches
458  searchResultTree = []
459  searchFilter = etree.XPath("//item")
460  userSearchStrings = 'userSearchStrings'
461  if self.userPrefs.find(userSearchStrings) is not None:
462  userSearch = self.userPrefs.find(userSearchStrings).xpath('./userSearch')
463  if len(userSearch):
464  for searchDetails in userSearch:
465  try:
466  data = self.searchTitle(searchDetails.find('searchTerm').text, 1, self.page_limit)
467  except HuluVideoNotFound as msg:
468  sys.stderr.write("%s\n" % msg)
469  continue
470  except HuluUrlError as msg:
471  sys.stderr.write('%s\n' % msg)
472  continue
473  except HuluHttpError as msg:
474  sys.stderr.write(self.error_messages['HuluHttpError'] % msg)
475  continue
476  except HuluRssError as msg:
477  sys.stderr.write(self.error_messages['HuluRssError'] % msg)
478  continue
479  except Exception as e:
480  sys.stderr.write("! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
481  continue
482  dirElement = etree.XML('<directory></directory>')
483  dirElement.attrib['name'] = self.common.massageText(searchDetails.find('dirName').text)
484  dirElement.attrib['thumbnail'] = self.channel_icon
485  lastKey = None
486  for key in sorted(data[0].keys()):
487  if lastKey != key:
488  dirElement.append(data[0][key])
489  lastKey = key
490  channelTree.append(dirElement)
491  continue
492 
493  # Create a structure of feeds that can be concurrently downloaded
494  rssData = etree.XML('<xml></xml>')
495  for feedType in ['treeviewURLS', ]:
496  if self.userPrefs.find(feedType) is None:
497  continue
498  if not len(self.userPrefs.find(feedType).xpath('./url')):
499  continue
500  for rssFeed in self.userPrefs.find(feedType).xpath('./url'):
501  urlEnabled = rssFeed.attrib.get('enabled')
502  if urlEnabled == 'false':
503  continue
504  urlName = rssFeed.attrib.get('name')
505  if urlName:
506  uniqueName = '%s;%s' % (urlName, rssFeed.text)
507  else:
508  uniqueName = 'RSS;%s' % (rssFeed.text)
509  url = etree.XML('<url></url>')
510  etree.SubElement(url, "name").text = uniqueName
511  etree.SubElement(url, "href").text = rssFeed.text
512  etree.SubElement(url, "filter").text = "//channel/title"
513  etree.SubElement(url, "filter").text = "//item"
514  etree.SubElement(url, "parserType").text = 'xml'
515  rssData.append(url)
516 
517  if self.config['debug_enabled']:
518  print("rssData:")
519  sys.stdout.write(etree.tostring(rssData, encoding='UTF-8', pretty_print=True))
520  print()
521 
522  # Get the RSS Feed data
523  if rssData.find('url') is not None:
524  try:
525  resultTree = self.common.getUrlData(rssData)
526  except Exception as errormsg:
527  raise HuluUrlDownloadError(self.error_messages['HuluUrlDownloadError'] % (errormsg))
528  if self.config['debug_enabled']:
529  print("resultTree:")
530  sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
531  print()
532 
533  # Process each directory of the user preferences that have an enabled rss feed
534  itemFilter = etree.XPath('.//item', namespaces=self.common.namespaces)
535  titleFilter = etree.XPath('.//title', namespaces=self.common.namespaces)
536  linkFilter = etree.XPath('.//link', namespaces=self.common.namespaces)
537  descriptionFilter = etree.XPath('.//description', namespaces=self.common.namespaces)
538  authorFilter = etree.XPath('.//media:credit', namespaces=self.common.namespaces)
539  pubDateFilter = etree.XPath('.//pubDate', namespaces=self.common.namespaces)
540  feedFilter = etree.XPath('//url[text()=$url]')
541  descFilter2 = etree.XPath('.//p')
542  itemThumbNail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
543  itemDwnLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
544  itemLanguage = etree.XPath('.//media:content', namespaces=self.common.namespaces)
545  itemDuration = etree.XPath('.//media:content', namespaces=self.common.namespaces)
546  rssName = etree.XPath('title', namespaces=self.common.namespaces)
547  categoryDir = None
548  categoryElement = None
549  for result in resultTree.findall('results'):
550  names = result.find('name').text.split(';')
551  names[0] = self.common.massageText(names[0])
552  if names[0] == 'RSS':
553  names[0] = self.common.massageText(rssName(result.find('result'))[0].text.replace('Hulu - ', ''))
554  count = 0
555  urlMax = None
556  url = feedFilter(self.userPrefs, url=names[1])
557  if len(url):
558  if url[0].attrib.get('max'):
559  try:
560  urlMax = int(url[0].attrib.get('max'))
561  except:
562  pass
563  elif url[0].getparent().attrib.get('globalmax'):
564  try:
565  urlMax = int(url[0].getparent().attrib.get('globalmax'))
566  except:
567  pass
568  if urlMax == 0:
569  urlMax = None
570  channelThumbnail = self.channel_icon
571  channelLanguage = 'en'
572  # Create a new directory and/or subdirectory if required
573  if names[0] != categoryDir:
574  if categoryDir is not None:
575  channelTree.append(categoryElement)
576  categoryElement = etree.XML('<directory></directory>')
577  categoryElement.attrib['name'] = names[0]
578  categoryElement.attrib['thumbnail'] = self.channel_icon
579  categoryDir = names[0]
580 
581  if self.config['debug_enabled']:
582  print("Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names))
583  print()
584 
585  # Convert each RSS item into a MNV item
586  for itemData in itemFilter(result.find('result')):
587  huluItem = etree.XML(self.common.mnvItem)
588  link = self.common.ampReplace(linkFilter(itemData)[0].text)
589  # Convert the pubDate "Sat, 01 May 2010 08:18:05 -0000" to a MNV pubdate format
590  pubdate = pubDateFilter(itemData)[0].text[:-5]+'GMT'
591 
592  # Extract and massage data also insert data into a new item element
593  huluItem.find('title').text = self.common.massageText(titleFilter(itemData)[0].text.strip())
594  if authorFilter(itemData)[0].text:
595  huluItem.find('author').text = self.common.massageText(authorFilter(itemData)[0].text.strip())
596  else:
597  huluItem.find('author').text = 'Hulu'
598  huluItem.find('pubDate').text = pubdate
599  description = etree.HTML(etree.tostring(descriptionFilter(itemData)[0], method="text", encoding=str).strip())
600  if descFilter2(description)[0].text is not None:
601  huluItem.find('description').text = self.common.massageText(descFilter2(description)[0].text.strip())
602  else:
603  huluItem.find('description').text = ''
604  for e in descFilter2(description)[1]:
605  eText = etree.tostring(e, method="text", encoding=str)
606  if not eText:
607  continue
608  if eText.startswith('Duration: '):
609  eText = eText.replace('Duration: ', '').strip()
610  videoSeconds = False
611  videoDuration = eText.split(':')
612  try:
613  if len(videoDuration) == 1:
614  videoSeconds = int(videoDuration[0])
615  elif len(videoDuration) == 2:
616  videoSeconds = int(videoDuration[0])*60+int(videoDuration[1])
617  elif len(videoDuration) == 3:
618  videoSeconds = int(videoDuration[0])*3600+int(videoDuration[1])*60+int(videoDuration[2])
619  if videoSeconds:
620  itemDwnLink(huluItem)[0].attrib['duration'] = str(videoSeconds)
621  except:
622  pass
623  elif eText.startswith('Rating: '):
624  eText = eText.replace('Rating: ', '').strip()
625  videoRating = eText.split(' ')
626  huluItem.find('rating').text = videoRating[0]
627  continue
628  huluItem.find('link').text = link
629  itemDwnLink(huluItem)[0].attrib['url'] = link
630  try:
631  itemThumbNail(huluItem)[0].attrib['url'] = self.common.ampReplace(itemThumbNail(itemData)[0].attrib['url'])
632  except IndexError:
633  pass
634  itemLanguage(huluItem)[0].attrib['lang'] = channelLanguage
635  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = 'us'
636  s_e = self.getSeasonEpisode(huluItem.find('title').text, huluItem.find('description').text, itemThumbNail(huluItem)[0].attrib['url'])
637  if s_e[0]:
638  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
639  if s_e[1]:
640  etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
641  if not huluItem.find('title').text and s_e[2]:
642  huluItem.find('title').text = s_e[2]
643  categoryElement.append(huluItem)
644  if urlMax: # Check of the maximum items to processes has been met
645  count+=1
646  if count > urlMax:
647  break
648 
649  # Add the last directory processed
650  if categoryElement is not None:
651  if categoryElement.xpath('.//item') is not None:
652  channelTree.append(categoryElement)
653 
654  # Check that there was at least some items
655  if len(rssTree.xpath('//item')):
656  # Output the MNV search results
657  sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
658  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
659 
660  sys.exit(0)
661  # end displayTreeView()
662 # end Videos() class
nv_python_libs.hulu.hulu_api.Videos.getSeasonEpisode
def getSeasonEpisode(self, title, desc=None, thumbnail=None)
Definition: hulu_api.py:224
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:218
nv_python_libs.hulu.hulu_api.Videos.channel_icon
channel_icon
Definition: hulu_api.py:150
nv_python_libs.hulu.hulu_api.Videos.searchTitle
def searchTitle(self, title, pagenumber, pagelen)
End of Utility functions.
Definition: hulu_api.py:256
nv_python_libs.hulu.hulu_api.Videos.getHuluConfig
def getHuluConfig(self)
Start - Utility functions.
Definition: hulu_api.py:170
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:79
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:138
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:135
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:125
nv_python_libs.hulu.hulu_api.Videos.getUserPreferences
def getUserPreferences(self)
Definition: hulu_api.py:190
nv_python_libs.hulu.hulu_api.Videos.log_name
log_name
Definition: hulu_api.py:123
nv_python_libs.hulu.hulu_api.Videos.config
config
Definition: hulu_api.py:112
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:141
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:431
nv_python_libs.hulu.hulu_api.Videos
Definition: hulu_api.py:71
nv_python_libs.hulu.hulu_api.Videos.hulu_config
hulu_config
Definition: hulu_api.py:183
nv_python_libs.hulu.hulu_api.Videos.common
common
Definition: hulu_api.py:120
nv_python_libs.hulu.hulu_api.Videos.searchForVideos
def searchForVideos(self, title, pagenumber)
Definition: hulu_api.py:357
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