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