13__title__ =
"thewb_api - Simple-to-use Python interface to the The WB RSS feeds (http://www.thewb.com/)"
14__author__=
"R.D. Vaughan"
16This python script is intended to perform a variety of utility functions to search and access text
17meta data, video and image URLs from thewb. These routines process RSS feeds provided by The WB
18(http://www.thewb.com/). The specific "The WB" RSS feeds that are processed are controled through
19a user XML preference file usually found at "~/.mythtv/MythNetvision/userGrabberPrefs/thewb.xml"
28import os, struct, sys, re, time, datetime,
urllib.request, urllib.parse, urllib.error
30from socket
import gethostname, gethostbyname
31from threading
import Thread
32from copy
import deepcopy
34from .thewb_exceptions
import (TheWBUrlError, TheWBHttpError, TheWBRssError, TheWBVideoNotFound, TheWBConfigFileError, TheWBUrlDownloadError)
38 """Wraps a stream with an encoder"""
47 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
48 if isinstance(obj, str):
50 self.
out.buffer.write(obj)
53 """Delegate everything but write to the stream"""
54 return getattr(self.
out, attr)
61 from io
import StringIO
62 from lxml
import etree
64 sys.stderr.write(
'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
69 """Takes a string, checks if it is numeric.
72 >>> _can_int(
"A test")
87 """Main interface to http://www.thewb.com/
88 This is done to support a common naming framework
for all python Netvision plugins no matter their site
91 Supports search methods
92 The apikey
is a
not required to access http://www.thewb.com/
102 search_all_languages = False,
104 """apikey (str/unicode):
105 Specify the target site API key. Applications need their own key in some cases
108 When
True, the returned meta data
is being returned has the key
and values massaged to match MythTV
109 When
False, the returned meta data
is being returned matches what target site returned
111 interactive (
True/
False): (This option
is not supported by all target site apis)
112 When
True, uses built-
in console UI
is used to select the correct show.
113 When
False, the first search result
is used.
115 select_first (
True/
False): (This option
is not supported currently implemented
in any grabbers)
116 Automatically selects the first series search result (rather
117 than showing the user a list of more than one series).
118 Is overridden by interactive =
False,
or specifying a custom_ui
121 shows verbose debugging information
123 custom_ui (xx_ui.BaseUI subclass): (This option
is not supported currently implemented
in any grabbers)
124 A callable subclass of interactive
class (overrides interactive option)
126 language (2 character language abbreviation): (This option
is not supported by all target site apis)
127 The language of the returned data. Is also the language search
128 uses. Default
is "en" (English). For full list, run..
130 search_all_languages (
True/
False): (This option
is not supported by all target site apis)
131 By default, a Netvision grabber will only search
in the language specified using
132 the language option. When this
is True, it will search
for the
138 if apikey
is not None:
139 self.
config[
'apikey'] = apikey
143 self.
config[
'debug_enabled'] = debug
151 self.
config[
'custom_ui'] = custom_ui
153 self.
config[
'interactive'] = interactive
155 self.
config[
'select_first'] = select_first
157 self.
config[
'search_all_languages'] = search_all_languages
159 self.
error_messages = {
'TheWBUrlError':
"! Error: The URL (%s) cause the exception error (%s)\n",
'TheWBHttpError':
"! Error: An HTTP communications error with The WB was raised (%s)\n",
'TheWBRssError':
"! Error: Invalid RSS meta data\nwas received from The WB error (%s). Skipping item.\n",
'TheWBVideoNotFound':
"! Error: Video search with The WB did not return any results (%s)\n",
'TheWBConfigFileError':
"! Error: thewb_config.xml file missing\nit should be located in and named as (%s).\n",
'TheWBUrlDownloadError':
"! Error: Downloading a RSS feed or Web page (%s).\n", }
162 self.
channel = {
'channel_title':
'The WB',
'channel_link':
'http://www.thewb.com/',
'channel_description':
"Watch full episodes of your favorite shows on The WB.com, like Friends, The O.C., Veronica Mars, Pushing Daisies, Smallville, Buffy The Vampire Slayer, One Tree Hill and Gilmore Girls.",
'channel_numresults': 0,
'channel_returned': 1,
'channel_startindex': 0}
168 re.compile(
'''Season\\ (?P<seasno>[0-9]+)\\:\\ Ep.\\ (?P<epno>[0-9]+)\\ \\((?P<hours>[0-9]+)\\:(?P<minutes>[0-9]+)\\:(?P<seconds>[0-9]+).*$''', re.UNICODE),
170 re.compile(
'''Season\\ (?P<seasno>[0-9]+)\\:\\ Ep.\\ (?P<epno>[0-9]+)\\ \\((?P<minutes>[0-9]+)\\:(?P<seconds>[0-9]+).*$''', re.UNICODE),
172 re.compile(
'''Season\\ (?P<seasno>[0-9]+)\\:\\ Ep.\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
174 re.compile(
'''Ep.\\ (?P<epno>[0-9]+)\\ \\((?P<hours>[0-9]+)\\:(?P<minutes>[0-9]+)\\:(?P<seconds>[0-9]+).*$''', re.UNICODE),
176 re.compile(
'''Ep.\\ (?P<epno>[0-9]+)\\ \\((?P<minutes>[0-9]+)\\:(?P<seconds>[0-9]+).*$''', re.UNICODE),
178 re.compile(
'''Ep.\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
191 ''' Check is there is any season or episode number information in an item's title
192 return array of season
and/
or episode numbers plus any duration
in minutes
and seconds
193 return array
with None values
200 return match.groups()
205 ''' Read the MNV The WB grabber "thewb_config.xml" configuration file
209 url =
'file://%s/nv_python_libs/configs/XML/thewb_config.xml' % (baseProcessingDir, )
210 if not os.path.isfile(url[7:]):
213 if self.
config[
'debug_enabled']:
218 except Exception
as e:
225 '''Read the thewb_config.xml and user preference thewb.xml file.
226 If the thewb.xml file does not exist then create it.
227 If the thewb.xml file
is too old then update it.
235 if userPreferenceFile[0] ==
'~':
236 self.
thewb_config.
find(
'userPreferenceFile').text =
"%s%s" % (os.path.expanduser(
"~"), userPreferenceFile[1:])
240 if self.
config[
'debug_enabled']:
245 except Exception
as e:
248 nextUpdateSecs = int(self.
userPrefs.
find(
'updateDuration').text)*86400
249 nextUpdate = time.localtime(os.path.getmtime(self.
thewb_config.
find(
'userPreferenceFile').text)+nextUpdateSecs)
250 now = time.localtime()
263 ''' Create or update the thewb.xml user preferences file
267 url =
'file://%s/nv_python_libs/configs/XML/defaultUserPrefs/thewb.xml' % (baseProcessingDir, )
268 if not os.path.isfile(url[7:]):
271 if self.
config[
'debug_enabled']:
272 print(
'updateTheWB url(%s)' % url)
275 userTheWB = etree.parse(url)
276 except Exception
as e:
282 if self.
config[
'debug_enabled']:
283 print(
"create(%s)" % create)
285 sys.stdout.write(etree.tostring(linksTree, encoding=
'UTF-8', pretty_print=
True))
290 if not len(linksTree.xpath(
'//results//a')) > 10:
294 root = etree.XML(
'<xml></xml>')
295 for directory
in linksTree.xpath(
'//results'):
296 tmpDirectory = etree.SubElement(root,
'showDirectories')
297 tmpDirectory.attrib[
'name'] = directory.find(
'name').text
298 for show
in directory.xpath(
'.//a'):
301 if showName.lower().
find(
'dvd') != -1
or show.attrib[
'href'].lower().
find(
'dvd') != -1:
303 tmpShow = etree.XML(
'<url></url>')
304 tmpShow.attrib[
'enabled'] =
'true'
305 tmpShow.attrib[
'name'] = self.
common.massageText(showName.strip())
306 tmpShow.text = self.
common.ampReplace(show.attrib[
'href'].replace(
'/shows/',
'').replace(
'/',
'').strip())
307 tmpDirectory.append(tmpShow)
309 if self.
config[
'debug_enabled']:
310 print(
"Before any merging userTheWB:")
311 sys.stdout.write(etree.tostring(userTheWB, encoding=
'UTF-8', pretty_print=
True))
317 userTheWB.find(
'updateDuration').text = self.
userPrefs.
find(
'updateDuration').text
319 root.find(
'showDirectories').attrib[
'globalmax'] = self.
userPrefs.
find(
'showDirectories').attrib[
'globalmax']
320 for rss
in self.
userPrefs.xpath(
"//url[@enabled='false']"):
321 elements = root.xpath(
"//url[text()=$URL]", URL=rss.text.strip())
323 elements[0].attrib[
'enabled'] =
'false'
325 elements[0].attrib[
'max'] = rss.attrib[
'max']
327 if self.
config[
'debug_enabled']:
328 print(
"After any merging userTheWB:")
329 sys.stdout.write(etree.tostring(userTheWB, encoding=
'UTF-8', pretty_print=
True))
333 prefDir = self.
thewb_config.
find(
'userPreferenceFile').text.replace(
'/thewb.xml',
'')
334 if not os.path.isdir(prefDir):
337 fd.write(etree.tostring(userTheWB, encoding=
'UTF-8', pretty_print=
True)[:-len(
'</userTheWB>')-1]+
''.join(etree.tostring(element, encoding=
'UTF-8', pretty_print=
True)
for element
in root.xpath(
'/xml/*'))+
'</userTheWB>')
343 except Exception
as e:
355 def searchTitle(self, title, pagenumber, pagelen, ignoreError=False):
356 '''Key word video search of the TheWB web site
357 return an array of matching item elements
363 searchVar =
'?q=%s' % (urllib.parse.quote(title.encode(
"utf-8")).replace(
' ',
'+'))
364 except UnicodeDecodeError:
365 searchVar =
'?q=%s' % (urllib.parse.quote(title).replace(
' ',
'+'))
366 url = self.
thewb_config.
find(
'searchURLS').xpath(
".//href")[0].text+searchVar
368 if self.
config[
'debug_enabled']:
369 print(
"Search url(%s)" % url)
377 except Exception
as errormsg:
383 if self.
config[
'debug_enabled']:
384 print(
"resultTree count(%s)" % len(resultTree))
385 print(etree.tostring(resultTree, encoding=
'UTF-8', pretty_print=
True))
388 if resultTree
is None:
391 raise TheWBVideoNotFound(
"No TheWB.com Video matches found for search value (%s)" % title)
393 searchResults = resultTree.xpath(
'//result/div')
394 if not len(searchResults):
397 raise TheWBVideoNotFound(
"No TheWB.com Video matches found for search value (%s)" % title)
400 self.
channel[
'channel_numresults'] = len(searchResults)
404 pubDate = datetime.datetime.now().strftime(self.
common.pubDateFormat)
407 thumbNailFilter = etree.XPath(
'.//div[@class="overlay_thumb_area"]//img')
408 textFilter = etree.XPath(
'.//div[@class="overlay-bg-middle"]/p')
409 titleFilter = etree.XPath(
'.//div[@class="overlay_thumb_area"]//a[@title!=""]/@title')
410 descFilter = etree.XPath(
'.//div[@class="overlay-bg-middle"]/p[@class="overlay_extra overlay_spacer_top"]/text()')
411 linkFilter = etree.XPath(
'.//div[@class="overlay_thumb_area"]//a[@title!=""]/@href')
412 itemThumbNail = etree.XPath(
'.//media:thumbnail', namespaces=self.
common.namespaces)
413 itemDwnLink = etree.XPath(
'.//media:content', namespaces=self.
common.namespaces)
415 for result
in searchResults:
417 thewbItem = etree.XML(self.
common.mnvItem)
419 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text =
'us'
423 link =
'file://%s/nv_python_libs/configs/HTML/thewb.html?videocode=%s' % (baseProcessingDir, result.attrib[
'id'].replace(
'video_',
''))
424 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text =
'true'
425 descriptionElement = textFilter(result)[0]
430 for e
in descriptionElement.xpath(
'./*'):
432 eText = str(e.tail,
'UTF-8').strip()
435 if eText.startswith(
'Season ')
or eText.startswith(
'EP'):
439 infoList =
'S%02dE%02d' % (int(sed[0]), int(sed[1]))
440 seasonNum =
'%d' % int(sed[0])
441 episodeNum =
'%d' % int(sed[1])
443 videoSeconds = int(sed[2])*3600+int(sed[3])*60+int(sed[4])
444 itemDwnLink(thewbItem)[0].attrib[
'duration'] = str(videoSeconds)
446 videoSeconds = int(sed[2])*60+int(sed[3])
447 itemDwnLink(thewbItem)[0].attrib[
'duration'] = str(videoSeconds)
449 index = title.find(
':')
451 tmptitle =
'%s: %s %s' % (title[:index].strip(), infoList, title[index+1:].strip())
453 tmptitle =
'%s: %s' % (title.strip(), infoList)
456 title = self.
common.massageText(title.strip())
457 description = self.
common.massageText(descFilter(result)[0].strip())
460 thewbItem.find(
'title').text = title
461 thewbItem.find(
'author').text =
"The WB.com"
462 thewbItem.find(
'pubDate').text = pubDate
463 thewbItem.find(
'description').text = description
464 thewbItem.find(
'link').text = link
468 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = seasonNum
470 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = episodeNum
471 itemDict[title.lower()] = thewbItem
473 if not len(list(itemDict.keys())):
476 raise TheWBVideoNotFound(
"No TheWB Video matches found for search value (%s)" % title)
478 return [itemDict, resultTree.xpath(
'//pageInfo')[0].text]
483 """Common name for a video search. Used to interface with MythTV plugin NetVision
488 if self.
config[
'debug_enabled']:
489 print(
"self.thewb_config:")
490 sys.stdout.write(etree.tostring(self.
thewb_config, encoding=
'UTF-8', pretty_print=
True))
499 data = self.
searchTitle(title, pagenumber, self.page_limit)
500 except TheWBVideoNotFound
as msg:
501 sys.stderr.write(
"%s\n" % msg)
503 except TheWBUrlError
as msg:
504 sys.stderr.write(
'%s\n' % msg)
506 except TheWBHttpError
as msg:
509 except TheWBRssError
as msg:
512 except Exception
as e:
513 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
517 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
520 itemCount = len(list(data[0].keys()))
521 if data[1] ==
'true':
522 self.
channel[
'channel_returned'] = itemCount
523 self.
channel[
'channel_startindex'] = itemCount
524 self.
channel[
'channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
526 self.
channel[
'channel_returned'] = itemCount
527 self.
channel[
'channel_startindex'] = self.
channel[
'channel_returned']
528 self.
channel[
'channel_numresults'] = self.
channel[
'channel_returned']
532 rssTree.append(channelTree)
536 for key
in sorted(data[0].keys()):
538 channelTree.append(data[0][key])
542 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
543 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
548 '''Gather the The WB feeds then get a max page of videos meta data in each of them
549 Display the results and exit
554 except Exception
as e:
555 sys.stderr.write(
'%s\n' % e)
559 showFeeds = self.
userPrefs.xpath(
"//showDirectories//url[@enabled='true']")
560 totalFeeds = self.
userPrefs.xpath(
"//url[@enabled='true']")
562 if self.
config[
'debug_enabled']:
563 print(
"self.userPrefs show count(%s) total feed count(%s):" % (len(showFeeds), len(totalFeeds)))
564 sys.stdout.write(etree.tostring(self.
userPrefs, encoding=
'UTF-8', pretty_print=
True))
567 if not len(totalFeeds):
568 sys.stderr.write(
'There are no show or treeviewURLS elements "enabled" in your "thewb.xml" user preferences\nfile (%s)\n' % self.
thewb_config.
find(
'userPreferenceFile').text)
575 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
579 rssTree.append(channelTree)
583 if len(showFeeds)
is not None:
584 for searchDetails
in showFeeds:
586 data = self.
searchTitle(searchDetails.text.strip(), 1, self.page_limit, ignoreError=
True)
589 except TheWBVideoNotFound
as msg:
590 sys.stderr.write(
"%s\n" % msg)
592 except TheWBUrlError
as msg:
593 sys.stderr.write(
'%s\n' % msg)
595 except TheWBHttpError
as msg:
598 except TheWBRssError
as msg:
601 except Exception
as e:
602 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (searchDetails.text.strip(), e))
604 data.append(searchDetails.attrib[
'name'])
605 showItems[self.
common.massageText(searchDetails.text.strip())] = data
608 if self.
config[
'debug_enabled']:
609 print(
"After searches count(%s):" % len(showItems))
610 for key
in list(showItems.keys()):
611 print(
"Show(%s) name(%s) item count(%s)" % (key, showItems[key][2], len(showItems[key][0])))
615 for showNameKey
in list(showItems.keys()):
617 for key
in list(showItems[showNameKey][0].keys()):
618 tmpLink = showItems[showNameKey][0][key].
find(
'link').text.replace(self.
thewb_config.
find(
'searchURLS').xpath(
".//href")[0].text,
'')
619 if tmpLink.startswith(showNameKey):
620 tmpList[key] = showItems[showNameKey][0][key]
621 showItems[showNameKey][0] = tmpList
623 if self.
config[
'debug_enabled']:
624 print(
"After search filter of non-show items count(%s):" % len(showItems))
625 for key
in list(showItems.keys()):
626 print(
"Show(%s) name(%s) item count(%s)" % (key, showItems[key][2], len(showItems[key][0])))
630 rssData = etree.XML(
'<xml></xml>')
631 rssFeedsUrl =
'http://www.thewb.com/shows/feed/'
632 for feedType
in self.
userPrefs.findall(
'showDirectories'):
633 for rssFeed
in self.
userPrefs.xpath(
"//showDirectories/url[@enabled='true']"):
634 link = rssFeedsUrl+rssFeed.text
635 urlName = rssFeed.attrib.get(
'name')
637 uniqueName =
'%s;%s' % (urlName, link)
639 uniqueName =
'RSS;%s' % (link)
640 url = etree.XML(
'<url></url>')
641 etree.SubElement(url,
"name").text = uniqueName
642 etree.SubElement(url,
"href").text = link
643 etree.SubElement(url,
"filter").text =
"//channel/title"
644 etree.SubElement(url,
"filter").text =
"//item"
645 etree.SubElement(url,
"parserType").text =
'xml'
648 if self.
config[
'debug_enabled']:
650 sys.stdout.write(etree.tostring(rssData, encoding=
'UTF-8', pretty_print=
True))
668 if rssData.find(
'url')
is not None:
670 resultTree = self.
common.getUrlData(rssData)
671 except Exception
as errormsg:
674 if self.
config[
'debug_enabled']:
676 sys.stdout.write(etree.tostring(resultTree, encoding=
'UTF-8', pretty_print=
True))
680 for result
in resultTree.findall(
'results'):
681 names = result.find(
'name').text.split(
';')
682 names[0] = self.
common.massageText(names[0])
683 if names[0] ==
'RSS':
684 names[0] = self.
common.massageText(self.
rssName(result.find(
'result'))[0].text.strip())
687 urlName = result.find(
'url').text.replace(rssFeedsUrl,
'').strip()
692 if url[0].attrib.get(
'max'):
694 urlMax = int(url[0].attrib.get(
'max'))
697 elif url[0].getparent().attrib.get(
'globalmax'):
699 urlMax = int(url[0].getparent().attrib.get(
'globalmax'))
704 if self.
config[
'debug_enabled']:
705 print(
"Results: #Items(%s) for (%s)" % (len(self.
itemFilter(result)), names))
707 self.
createItems(showItems, result, urlName, names[0], urlMax=urlMax)
711 for key
in sorted(showItems.keys()):
712 if not len(showItems[key][0]):
715 directoryElement = etree.SubElement(channelTree,
'directory')
716 directoryElement.attrib[
'name'] = showItems[key][2]
717 directoryElement.attrib[
'thumbnail'] = self.
channel_icon
719 if self.
config[
'debug_enabled']:
720 print(
"Results: #Items(%s) for (%s)" % (len(showItems[key][0]), showItems[key][2]))
724 for itemKey
in sorted(showItems[key][0].keys()):
725 directoryElement.append(showItems[key][0][itemKey])
727 if self.
config[
'debug_enabled']:
728 print(
"Final results: #Items(%s)" % len(rssTree.xpath(
'//item')))
732 if len(rssTree.xpath(
'//item')):
734 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
735 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
740 def createItems(self, showItems, result, urlName, showName, urlMax=None):
741 '''Create a dictionary of MNV compliant RSS items from the results of a RSS feed show search.
742 Also merge with any items that were found by using the Web search. Identical items use the RSS
743 feed item data over the search item
as RSS provides better results.
744 return nothing
as the show item dictionary will have all the results
747 if not urlName
in list(showItems.keys()):
748 showItems[urlName] = [{},
None, showName]
753 newItem = etree.XML(self.
common.mnvItem)
755 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text =
'us'
757 tmpLink = self.
linkFilter(thewbItem)[0].text.strip()
758 link = self.
common.ampReplace(
'file://%s/nv_python_libs/configs/HTML/thewb.html?videocode=%s' % (baseProcessingDir, tmpLink[tmpLink.rfind(
'/')+1:]))
759 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text =
'true'
763 pubdate = pubdate[0].text[:-6]
764 pubdate = time.strptime(pubdate,
'%Y-%m-%dT%H:%M:%S')
765 pubdate = time.strftime(self.
common.pubDateFormat, pubdate)
767 pubdate = datetime.datetime.now().strftime(self.
common.pubDateFormat)
774 for eText
in descList:
777 eText = eText.strip().encode(
'UTF-8')
782 if eText.startswith(
'Season: ')
or eText.startswith(
'EP: '):
783 s_e = eText.replace(
'Season:',
'').replace(
', Episode:',
'').replace(
'EP:',
'').strip().split(
' ')
784 if len(s_e) == 1
and can_int(s_e[0].strip()):
785 eText =
'Ep(%02d)' % int(s_e[0].strip())
786 episodeNum = s_e[0].strip()
787 elif len(s_e) == 2
and can_int(s_e[0].strip())
and can_int(s_e[1].strip()):
788 eText =
'S%02dE%02d' % (int(s_e[0].strip()), int(s_e[1].strip()))
789 seasonNum = s_e[0].strip()
790 episodeNum = s_e[1].strip()
791 title = title.replace(
'-',
'–')
792 index = title.find(
'–')
794 tmptitle =
'%s: %s %s' % (title[:index].strip(), eText.strip(), title[index:].strip())
796 tmptitle =
'%s %s' % (title, eText.strip())
798 elif eText.startswith(
'Running Time: '):
799 videoDuration = eText.replace(
'Running Time: ',
'').strip().split(
':')
800 if not len(videoDuration):
804 if len(videoDuration) == 1:
805 videoSeconds = int(videoDuration[0])
806 elif len(videoDuration) == 2:
807 videoSeconds = int(videoDuration[0])*60+int(videoDuration[1])
808 elif len(videoDuration) == 3:
809 videoSeconds = int(videoDuration[0])*3600+int(videoDuration[1])*60+int(videoDuration[2])
811 self.
itemDwnLink(newItem)[0].attrib[
'duration'] = str(videoSeconds)
814 except UnicodeDecodeError:
819 title = self.
common.massageText(title.strip())
820 description = self.
common.massageText(description.strip())
822 newItem.find(
'title').text = title
824 newItem.find(
'pubDate').text = pubdate
825 newItem.find(
'description').text = description
826 newItem.find(
'link').text = link
834 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = seasonNum
836 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = episodeNum
838 showItems[urlName][0][title.lower()] = newItem
def __getattr__(self, attr)
def __init__(self, outstream, encoding=None)
def updateTheWB(self, create=False)
def searchForVideos(self, title, pagenumber)
def getUserPreferences(self)
def searchTitle(self, title, pagenumber, pagelen, ignoreError=False)
End of Utility functions.
def displayTreeView(self)
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
def createItems(self, showItems, result, urlName, showName, urlMax=None)
def getSeasonEpisode(self, title)
Start - Utility functions.
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)