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__='''
17This python script is intended to perform a variety of utility functions to search and access text
18meta data, video and image URLs from the Hulu Web site. These routines process RSS feeds
19provided 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
29import os, struct, sys, re, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
30import logging
31from socket import gethostname, gethostbyname
32from threading import Thread
33from copy import deepcopy
34from operator import itemgetter, attrgetter
35
36from .hulu_exceptions import (HuluUrlError, HuluHttpError, HuluRssError, HuluVideoNotFound, HuluConfigFileError, HuluUrlDownloadError)
37import io
38
39class 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
58if isinstance(sys.stdout, io.TextIOWrapper):
59 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
60 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
61
62
63try:
64 from io import StringIO
65 from lxml import etree
66except 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
71class 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
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
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
def __init__(self, outstream, encoding=None)
Definition: hulu_api.py:41
def getHuluConfig(self)
Start - Utility functions.
Definition: hulu_api.py:170
def searchForVideos(self, title, pagenumber)
Definition: hulu_api.py:357
def getSeasonEpisode(self, title, desc=None, thumbnail=None)
Definition: hulu_api.py:224
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:88
def searchTitle(self, title, pagenumber, pagelen)
End of Utility functions.
Definition: hulu_api.py:256
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)
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)