00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014 __title__ ="hulu_api - Simple-to-use Python interface to the Hulu RSS feeds (http://www.hulu.com/)"
00015 __author__="R.D. Vaughan"
00016 __purpose__='''
00017 This python script is intended to perform a variety of utility functions to search and access text
00018 meta data, video and image URLs from the Hulu Web site. These routines process RSS feeds
00019 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
00020 "~/.mythtv/MythNetvision/userGrabberPrefs/hulu.xml"
00021 '''
00022
00023 __version__="v0.1.3"
00024
00025
00026
00027
00028
00029 import os, struct, sys, re, time, datetime, shutil, urllib
00030 from string import capitalize
00031 import logging
00032 from socket import gethostname, gethostbyname
00033 from threading import Thread
00034 from copy import deepcopy
00035 from operator import itemgetter, attrgetter
00036
00037 from hulu_exceptions import (HuluUrlError, HuluHttpError, HuluRssError, HuluVideoNotFound, HuluConfigFileError, HuluUrlDownloadError)
00038
00039 class OutStreamEncoder(object):
00040 """Wraps a stream with an encoder"""
00041 def __init__(self, outstream, encoding=None):
00042 self.out = outstream
00043 if not encoding:
00044 self.encoding = sys.getfilesystemencoding()
00045 else:
00046 self.encoding = encoding
00047
00048 def write(self, obj):
00049 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
00050 if isinstance(obj, unicode):
00051 try:
00052 self.out.write(obj.encode(self.encoding))
00053 except IOError:
00054 pass
00055 else:
00056 try:
00057 self.out.write(obj)
00058 except IOError:
00059 pass
00060
00061 def __getattr__(self, attr):
00062 """Delegate everything but write to the stream"""
00063 return getattr(self.out, attr)
00064 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00065 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00066
00067
00068 try:
00069 from StringIO import StringIO
00070 from lxml import etree
00071 except Exception, e:
00072 sys.stderr.write(u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
00073 sys.exit(1)
00074
00075
00076
00077
00078
00079 version = ''
00080 for digit in etree.LIBXML_VERSION:
00081 version+=str(digit)+'.'
00082 version = version[:-1]
00083 if version < '2.7.2':
00084 sys.stderr.write(u'''
00085 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
00086 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
00087 ''' % version)
00088 sys.exit(1)
00089
00090
00091 class Videos(object):
00092 """Main interface to http://www.hulu.com/
00093 This is done to support a common naming framework for all python Netvision plugins no matter their
00094 site target.
00095
00096 Supports search methods
00097 The apikey is a not required to access http://www.hulu.com/
00098 """
00099 def __init__(self,
00100 apikey,
00101 mythtv = True,
00102 interactive = False,
00103 select_first = False,
00104 debug = False,
00105 custom_ui = None,
00106 language = None,
00107 search_all_languages = False,
00108 ):
00109 """apikey (str/unicode):
00110 Specify the target site API key. Applications need their own key in some cases
00111
00112 mythtv (True/False):
00113 When True, the returned meta data is being returned has the key and values massaged to match MythTV
00114 When False, the returned meta data is being returned matches what target site returned
00115
00116 interactive (True/False): (This option is not supported by all target site apis)
00117 When True, uses built-in console UI is used to select the correct show.
00118 When False, the first search result is used.
00119
00120 select_first (True/False): (This option is not supported currently implemented in any grabbers)
00121 Automatically selects the first series search result (rather
00122 than showing the user a list of more than one series).
00123 Is overridden by interactive = False, or specifying a custom_ui
00124
00125 debug (True/False):
00126 shows verbose debugging information
00127
00128 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
00129 A callable subclass of interactive class (overrides interactive option)
00130
00131 language (2 character language abbreviation): (This option is not supported by all target site apis)
00132 The language of the returned data. Is also the language search
00133 uses. Default is "en" (English). For full list, run..
00134
00135 search_all_languages (True/False): (This option is not supported by all target site apis)
00136 By default, a Netvision grabber will only search in the language specified using
00137 the language option. When this is True, it will search for the
00138 show in any language
00139
00140 """
00141 self.config = {}
00142
00143 if apikey is not None:
00144 self.config['apikey'] = apikey
00145 else:
00146 pass
00147
00148 self.config['debug_enabled'] = debug
00149 self.common = common
00150 self.common.debug = debug
00151
00152 self.log_name = u'Hulu_Grabber'
00153 self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
00154 self.logger = self.common.logger
00155
00156 self.config['custom_ui'] = custom_ui
00157
00158 self.config['interactive'] = interactive
00159
00160 self.config['select_first'] = select_first
00161
00162 self.config['search_all_languages'] = search_all_languages
00163
00164 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", }
00165
00166
00167 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}
00168
00169
00170 self.s_e_Patterns = [
00171
00172 re.compile(u'''^.+?[Ss](?P<seasno>[0-9]+).*.+?[Ee](?P<epno>[0-9]+).*$''', re.UNICODE),
00173
00174 re.compile(u'''^.+?season\ (?P<seasno>[0-9]+).*.+?episode\ (?P<epno>[0-9]+).*$''', re.UNICODE),
00175
00176 re.compile(u'''(?P<seriesname>[^_]+)\\_(?P<seasno>[0-9]+)\\_(?P<epno>[0-9]+).*$''', re.UNICODE),
00177 ]
00178
00179 self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/hulu.png'
00180
00181 self.config[u'image_extentions'] = ["png", "jpg", "bmp"]
00182
00183
00184
00185
00186
00187
00188
00189
00190 def getHuluConfig(self):
00191 ''' Read the MNV Hulu grabber "hulu_config.xml" configuration file
00192 return nothing
00193 '''
00194
00195 url = u'file://%s/nv_python_libs/configs/XML/hulu_config.xml' % (baseProcessingDir, )
00196 if not os.path.isfile(url[7:]):
00197 raise HuluConfigFileError(self.error_messages['HuluConfigFileError'] % (url[7:], ))
00198
00199 if self.config['debug_enabled']:
00200 print url
00201 print
00202 try:
00203 self.hulu_config = etree.parse(url)
00204 except Exception, e:
00205 raise HuluUrlError(self.error_messages['HuluUrlError'] % (url, errormsg))
00206 return
00207
00208
00209
00210 def getUserPreferences(self):
00211 '''Read the hulu_config.xml and user preference hulu.xml file.
00212 If the hulu.xml file does not exist then copy the default.
00213 return nothing
00214 '''
00215
00216 self.getHuluConfig()
00217
00218
00219 userPreferenceFile = self.hulu_config.find('userPreferenceFile').text
00220 if userPreferenceFile[0] == '~':
00221 self.hulu_config.find('userPreferenceFile').text = u"%s%s" % (os.path.expanduser(u"~"), userPreferenceFile[1:])
00222
00223
00224 if not os.path.isfile(self.hulu_config.find('userPreferenceFile').text):
00225
00226 prefDir = self.hulu_config.find('userPreferenceFile').text.replace(u'/hulu.xml', u'')
00227 if not os.path.isdir(prefDir):
00228 os.makedirs(prefDir)
00229 defaultConfig = u'%s/nv_python_libs/configs/XML/defaultUserPrefs/hulu.xml' % (baseProcessingDir, )
00230 shutil.copy2(defaultConfig, self.hulu_config.find('userPreferenceFile').text)
00231
00232
00233 url = u'file://%s' % (self.hulu_config.find('userPreferenceFile').text, )
00234 if self.config['debug_enabled']:
00235 print url
00236 print
00237 try:
00238 self.userPrefs = etree.parse(url)
00239 except Exception, e:
00240 raise HuluUrlError(self.error_messages['HuluUrlError'] % (url, errormsg))
00241 return
00242
00243
00244 def getSeasonEpisode(self, title, desc=None, thumbnail=None):
00245 ''' Check is there is any season or episode number information in an item's title
00246 return array of season and/or episode numbers, Series name (only if title empty)
00247 return array with None values
00248 '''
00249 s_e = [None, None, None]
00250 if title:
00251 match = self.s_e_Patterns[0].match(title)
00252 if match:
00253 s_e[0], s_e[1] = match.groups()
00254 if not s_e[0] and desc:
00255 match = self.s_e_Patterns[1].match(desc)
00256 if match:
00257 s_e[0], s_e[1] = match.groups()
00258 if thumbnail and not title:
00259 filepath, filename = os.path.split( thumbnail.replace(u'http:/', u'') )
00260 match = self.s_e_Patterns[2].match(filename)
00261 if match:
00262 s_e[2], s_e[0], s_e[1] = match.groups()
00263 s_e[0] = u'%s' % int(s_e[0])
00264 s_e[1] = u'%s' % int(s_e[1])
00265 s_e[2] = "".join([capitalize(w) for w in re.split(re.compile("[\W_]*"), s_e[2].replace(u'_', u' ').replace(u'-', u' '))])
00266 return s_e
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276 def searchTitle(self, title, pagenumber, pagelen):
00277 '''Key word video search of the Hulu web site
00278 return an array of matching item elements
00279 return
00280 '''
00281
00282 orgUrl = self.hulu_config.find('searchURLS').xpath(".//href")[0].text
00283
00284 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")))
00285
00286 if self.config['debug_enabled']:
00287 print url
00288 print
00289
00290 self.hulu_config.find('searchURLS').xpath(".//href")[0].text = url
00291
00292
00293 try:
00294 resultTree = self.common.getUrlData(self.hulu_config.find('searchURLS'))
00295 except Exception, errormsg:
00296
00297 self.hulu_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
00298 raise HuluUrlDownloadError(self.error_messages['HuluUrlDownloadError'] % (errormsg))
00299
00300
00301 self.hulu_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
00302
00303 if resultTree is None:
00304 raise HuluVideoNotFound(u"No Hulu Video matches found for search value (%s)" % title)
00305
00306 searchResults = resultTree.xpath('//result//a[@href!="#"]')
00307 if not len(searchResults):
00308 raise HuluVideoNotFound(u"No Hulu Video matches found for search value (%s)" % title)
00309
00310 if self.config['debug_enabled']:
00311 print "resultTree: count(%s)" % len(searchResults)
00312 print
00313
00314
00315
00316 pubDate = datetime.datetime.now().strftime(self.common.pubDateFormat)
00317
00318
00319 titleFilter = etree.XPath(u".//img")
00320 thumbnailFilter = etree.XPath(u".//img")
00321 itemLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
00322 itemThumbnail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
00323 itemDict = {}
00324 for result in searchResults:
00325 tmpLink = result.attrib['href']
00326 if not tmpLink:
00327 continue
00328 huluItem = etree.XML(self.common.mnvItem)
00329
00330 link = self.common.ampReplace(tmpLink)
00331 tmpTitleText = titleFilter(result)[0].attrib['alt'].strip()
00332 tmpList = tmpTitleText.split(u':')
00333 title = self.common.massageText(tmpList[0].strip())
00334 if len(tmpList) > 1:
00335 description = self.common.massageText(tmpList[1].strip())
00336 else:
00337 description = u''
00338
00339
00340 huluItem.find('title').text = title
00341 huluItem.find('author').text = u'Hulu'
00342 huluItem.find('pubDate').text = pubDate
00343 huluItem.find('description').text = description
00344 huluItem.find('link').text = link
00345 itemThumbnail(huluItem)[0].attrib['url'] = self.common.ampReplace(thumbnailFilter(result)[0].attrib['src'])
00346 itemLink(huluItem)[0].attrib['url'] = link
00347 etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = u'us'
00348 s_e = self.getSeasonEpisode(title, description, itemThumbnail(huluItem)[0].attrib['url'])
00349 if s_e[0]:
00350 etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
00351 if s_e[1]:
00352 etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
00353 if not title and s_e[2]:
00354 huluItem.find('title').text = s_e[2]
00355 itemDict[link] = huluItem
00356
00357 if not len(itemDict.keys()):
00358 raise HuluVideoNotFound(u"No Hulu Video matches found for search value (%s)" % title)
00359
00360
00361 self.channel['channel_numresults'] = len(itemDict)
00362
00363
00364 lastPage = resultTree.xpath('//result//a[@alt="Go to the last page"]')
00365 morePages = False
00366 if len(lastPage):
00367 try:
00368 if pagenumber < lastPage[0].text:
00369 morePages = True
00370 except:
00371 pass
00372
00373 return [itemDict, morePages]
00374
00375
00376
00377 def searchForVideos(self, title, pagenumber):
00378 """Common name for a video search. Used to interface with MythTV plugin NetVision
00379 """
00380
00381 try:
00382 self.getUserPreferences()
00383 except Exception, e:
00384 sys.stderr.write(u'%s' % e)
00385 sys.exit(1)
00386
00387 if self.config['debug_enabled']:
00388 print "self.userPrefs:"
00389 sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
00390 print
00391
00392
00393
00394
00395
00396
00397
00398 try:
00399 data = self.searchTitle(title, pagenumber, self.page_limit)
00400 except HuluVideoNotFound, msg:
00401 sys.stderr.write(u"%s\n" % msg)
00402 sys.exit(0)
00403 except HuluUrlError, msg:
00404 sys.stderr.write(u'%s\n' % msg)
00405 sys.exit(1)
00406 except HuluHttpError, msg:
00407 sys.stderr.write(self.error_messages['HuluHttpError'] % msg)
00408 sys.exit(1)
00409 except HuluRssError, msg:
00410 sys.stderr.write(self.error_messages['HuluRssError'] % msg)
00411 sys.exit(1)
00412 except Exception, e:
00413 sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
00414 sys.exit(1)
00415
00416 if self.config['debug_enabled']:
00417 print "Number of items returned by the search(%s)" % len(data[0].keys())
00418 sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
00419 print
00420
00421
00422 rssTree = etree.XML(self.common.mnvRSS+u'</rss>')
00423
00424
00425 itemCount = len(data[0].keys())
00426 if data[1]:
00427 self.channel['channel_returned'] = itemCount
00428 self.channel['channel_startindex'] = self.page_limit*(int(pagenumber)-1)
00429 self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
00430 else:
00431 self.channel['channel_returned'] = itemCount
00432 self.channel['channel_startindex'] = itemCount+(self.page_limit*(int(pagenumber)-1))
00433 self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1))
00434
00435
00436 channelTree = self.common.mnvChannelElement(self.channel)
00437 rssTree.append(channelTree)
00438
00439 lastKey = None
00440 for key in sorted(data[0].keys()):
00441 if lastKey != key:
00442 channelTree.append(data[0][key])
00443 lastKey = key
00444
00445
00446 sys.stdout.write(u'<?xml version="1.0" encoding="UTF-8"?>\n')
00447 sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
00448 sys.exit(0)
00449
00450
00451 def displayTreeView(self):
00452 '''Gather the Hulu feeds then get a max page of videos meta data in each of them
00453 Display the results and exit
00454 '''
00455
00456 try:
00457 self.getUserPreferences()
00458 except Exception, e:
00459 sys.stderr.write(u'%s' % e)
00460 sys.exit(1)
00461
00462 if self.config['debug_enabled']:
00463 print "self.userPrefs:"
00464 sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
00465 print
00466
00467
00468 self.channel_icon = self.common.ampReplace(self.channel_icon)
00469
00470
00471 rssTree = etree.XML(self.common.mnvRSS+u'</rss>')
00472
00473
00474 channelTree = self.common.mnvChannelElement(self.channel)
00475 rssTree.append(channelTree)
00476
00477
00478 searchResultTree = []
00479 searchFilter = etree.XPath(u"//item")
00480 userSearchStrings = u'userSearchStrings'
00481 if self.userPrefs.find(userSearchStrings) != None:
00482 userSearch = self.userPrefs.find(userSearchStrings).xpath('./userSearch')
00483 if len(userSearch):
00484 for searchDetails in userSearch:
00485 try:
00486 data = self.searchTitle(searchDetails.find('searchTerm').text, 1, self.page_limit)
00487 except HuluVideoNotFound, msg:
00488 sys.stderr.write(u"%s\n" % msg)
00489 continue
00490 except HuluUrlError, msg:
00491 sys.stderr.write(u'%s\n' % msg)
00492 continue
00493 except HuluHttpError, msg:
00494 sys.stderr.write(self.error_messages['HuluHttpError'] % msg)
00495 continue
00496 except HuluRssError, msg:
00497 sys.stderr.write(self.error_messages['HuluRssError'] % msg)
00498 continue
00499 except Exception, e:
00500 sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
00501 continue
00502 dirElement = etree.XML(u'<directory></directory>')
00503 dirElement.attrib['name'] = self.common.massageText(searchDetails.find('dirName').text)
00504 dirElement.attrib['thumbnail'] = self.channel_icon
00505 lastKey = None
00506 for key in sorted(data[0].keys()):
00507 if lastKey != key:
00508 dirElement.append(data[0][key])
00509 lastKey = key
00510 channelTree.append(dirElement)
00511 continue
00512
00513
00514 rssData = etree.XML(u'<xml></xml>')
00515 for feedType in [u'treeviewURLS', ]:
00516 if self.userPrefs.find(feedType) == None:
00517 continue
00518 if not len(self.userPrefs.find(feedType).xpath('./url')):
00519 continue
00520 for rssFeed in self.userPrefs.find(feedType).xpath('./url'):
00521 urlEnabled = rssFeed.attrib.get('enabled')
00522 if urlEnabled == 'false':
00523 continue
00524 urlName = rssFeed.attrib.get('name')
00525 if urlName:
00526 uniqueName = u'%s;%s' % (urlName, rssFeed.text)
00527 else:
00528 uniqueName = u'RSS;%s' % (rssFeed.text)
00529 url = etree.XML(u'<url></url>')
00530 etree.SubElement(url, "name").text = uniqueName
00531 etree.SubElement(url, "href").text = rssFeed.text
00532 etree.SubElement(url, "filter").text = u"//channel/title"
00533 etree.SubElement(url, "filter").text = u"//item"
00534 etree.SubElement(url, "parserType").text = u'xml'
00535 rssData.append(url)
00536
00537 if self.config['debug_enabled']:
00538 print "rssData:"
00539 sys.stdout.write(etree.tostring(rssData, encoding='UTF-8', pretty_print=True))
00540 print
00541
00542
00543 if rssData.find('url') != None:
00544 try:
00545 resultTree = self.common.getUrlData(rssData)
00546 except Exception, errormsg:
00547 raise HuluUrlDownloadError(self.error_messages['HuluUrlDownloadError'] % (errormsg))
00548 if self.config['debug_enabled']:
00549 print "resultTree:"
00550 sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
00551 print
00552
00553
00554 itemFilter = etree.XPath('.//item', namespaces=self.common.namespaces)
00555 titleFilter = etree.XPath('.//title', namespaces=self.common.namespaces)
00556 linkFilter = etree.XPath('.//link', namespaces=self.common.namespaces)
00557 descriptionFilter = etree.XPath('.//description', namespaces=self.common.namespaces)
00558 authorFilter = etree.XPath('.//media:credit', namespaces=self.common.namespaces)
00559 pubDateFilter = etree.XPath('.//pubDate', namespaces=self.common.namespaces)
00560 feedFilter = etree.XPath('//url[text()=$url]')
00561 descFilter2 = etree.XPath('.//p')
00562 itemThumbNail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
00563 itemDwnLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
00564 itemLanguage = etree.XPath('.//media:content', namespaces=self.common.namespaces)
00565 itemDuration = etree.XPath('.//media:content', namespaces=self.common.namespaces)
00566 rssName = etree.XPath('title', namespaces=self.common.namespaces)
00567 categoryDir = None
00568 categoryElement = None
00569 for result in resultTree.findall('results'):
00570 names = result.find('name').text.split(u';')
00571 names[0] = self.common.massageText(names[0])
00572 if names[0] == 'RSS':
00573 names[0] = self.common.massageText(rssName(result.find('result'))[0].text.replace(u'Hulu - ', u''))
00574 count = 0
00575 urlMax = None
00576 url = feedFilter(self.userPrefs, url=names[1])
00577 if len(url):
00578 if url[0].attrib.get('max'):
00579 try:
00580 urlMax = int(url[0].attrib.get('max'))
00581 except:
00582 pass
00583 elif url[0].getparent().attrib.get('globalmax'):
00584 try:
00585 urlMax = int(url[0].getparent().attrib.get('globalmax'))
00586 except:
00587 pass
00588 if urlMax == 0:
00589 urlMax = None
00590 channelThumbnail = self.channel_icon
00591 channelLanguage = u'en'
00592
00593 if names[0] != categoryDir:
00594 if categoryDir != None:
00595 channelTree.append(categoryElement)
00596 categoryElement = etree.XML(u'<directory></directory>')
00597 categoryElement.attrib['name'] = names[0]
00598 categoryElement.attrib['thumbnail'] = self.channel_icon
00599 categoryDir = names[0]
00600
00601 if self.config['debug_enabled']:
00602 print "Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names)
00603 print
00604
00605
00606 for itemData in itemFilter(result.find('result')):
00607 huluItem = etree.XML(self.common.mnvItem)
00608 link = self.common.ampReplace(linkFilter(itemData)[0].text)
00609
00610 pubdate = pubDateFilter(itemData)[0].text[:-5]+u'GMT'
00611
00612
00613 huluItem.find('title').text = self.common.massageText(titleFilter(itemData)[0].text.strip())
00614 if authorFilter(itemData)[0].text:
00615 huluItem.find('author').text = self.common.massageText(authorFilter(itemData)[0].text.strip())
00616 else:
00617 huluItem.find('author').text = u'Hulu'
00618 huluItem.find('pubDate').text = pubdate
00619 description = etree.HTML(etree.tostring(descriptionFilter(itemData)[0], method="text", encoding=unicode).strip())
00620 if descFilter2(description)[0].text != None:
00621 huluItem.find('description').text = self.common.massageText(descFilter2(description)[0].text.strip())
00622 else:
00623 huluItem.find('description').text = u''
00624 for e in descFilter2(description)[1]:
00625 eText = etree.tostring(e, method="text", encoding=unicode)
00626 if not eText:
00627 continue
00628 if eText.startswith(u'Duration: '):
00629 eText = eText.replace(u'Duration: ', u'').strip()
00630 videoSeconds = False
00631 videoDuration = eText.split(u':')
00632 try:
00633 if len(videoDuration) == 1:
00634 videoSeconds = int(videoDuration[0])
00635 elif len(videoDuration) == 2:
00636 videoSeconds = int(videoDuration[0])*60+int(videoDuration[1])
00637 elif len(videoDuration) == 3:
00638 videoSeconds = int(videoDuration[0])*3600+int(videoDuration[1])*60+int(videoDuration[2])
00639 if videoSeconds:
00640 itemDwnLink(huluItem)[0].attrib['duration'] = unicode(videoSeconds)
00641 except:
00642 pass
00643 elif eText.startswith(u'Rating: '):
00644 eText = eText.replace(u'Rating: ', u'').strip()
00645 videoRating = eText.split(u' ')
00646 huluItem.find('rating').text = videoRating[0]
00647 continue
00648 huluItem.find('link').text = link
00649 itemDwnLink(huluItem)[0].attrib['url'] = link
00650 try:
00651 itemThumbNail(huluItem)[0].attrib['url'] = self.common.ampReplace(itemThumbNail(itemData)[0].attrib['url'])
00652 except IndexError:
00653 pass
00654 itemLanguage(huluItem)[0].attrib['lang'] = channelLanguage
00655 etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = u'us'
00656 s_e = self.getSeasonEpisode(huluItem.find('title').text, huluItem.find('description').text, itemThumbNail(huluItem)[0].attrib['url'])
00657 if s_e[0]:
00658 etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
00659 if s_e[1]:
00660 etree.SubElement(huluItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
00661 if not huluItem.find('title').text and s_e[2]:
00662 huluItem.find('title').text = s_e[2]
00663 categoryElement.append(huluItem)
00664 if urlMax:
00665 count+=1
00666 if count > urlMax:
00667 break
00668
00669
00670 if categoryElement != None:
00671 if categoryElement.xpath('.//item') != None:
00672 channelTree.append(categoryElement)
00673
00674
00675 if len(rssTree.xpath('//item')):
00676
00677 sys.stdout.write(u'<?xml version="1.0" encoding="UTF-8"?>\n')
00678 sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
00679
00680 sys.exit(0)
00681
00682