13 __title__ =
"thewb_api - Simple-to-use Python interface to the The WB RSS feeds (http://www.thewb.com/)"
14 __author__=
"R.D. Vaughan"
16 This python script is intended to perform a variety of utility functions to search and access text
17 meta 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
19 a user XML preference file usually found at "~/.mythtv/MythNetvision/userGrabberPrefs/thewb.xml"
28 import os, struct, sys, re, time, datetime, urllib.request, urllib.parse, urllib.error
30 from socket
import gethostname, gethostbyname
31 from threading
import Thread
32 from copy
import deepcopy
34 from .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
63 except Exception
as e:
64 sys.stderr.write(
'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
72 for digit
in etree.LIBXML_VERSION:
73 version+=str(digit)+
'.'
74 version = version[:-1]
77 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
78 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
84 """Takes a string, checks if it is numeric.
87 >>> _can_int("A test")
102 """Main interface to http://www.thewb.com/
103 This is done to support a common naming framework for all python Netvision plugins no matter their site
106 Supports search methods
107 The apikey is a not required to access http://www.thewb.com/
113 select_first = False,
117 search_all_languages = False,
119 """apikey (str/unicode):
120 Specify the target site API key. Applications need their own key in some cases
123 When True, the returned meta data is being returned has the key and values massaged to match MythTV
124 When False, the returned meta data is being returned matches what target site returned
126 interactive (True/False): (This option is not supported by all target site apis)
127 When True, uses built-in console UI is used to select the correct show.
128 When False, the first search result is used.
130 select_first (True/False): (This option is not supported currently implemented in any grabbers)
131 Automatically selects the first series search result (rather
132 than showing the user a list of more than one series).
133 Is overridden by interactive = False, or specifying a custom_ui
136 shows verbose debugging information
138 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
139 A callable subclass of interactive class (overrides interactive option)
141 language (2 character language abbreviation): (This option is not supported by all target site apis)
142 The language of the returned data. Is also the language search
143 uses. Default is "en" (English). For full list, run..
145 search_all_languages (True/False): (This option is not supported by all target site apis)
146 By default, a Netvision grabber will only search in the language specified using
147 the language option. When this is True, it will search for the
153 if apikey
is not None:
154 self.
config[
'apikey'] = apikey
158 self.
config[
'debug_enabled'] = debug
166 self.
config[
'custom_ui'] = custom_ui
170 self.
config[
'select_first'] = select_first
172 self.
config[
'search_all_languages'] = search_all_languages
174 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", }
177 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}
183 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),
185 re.compile(
'''Season\\ (?P<seasno>[0-9]+)\\:\\ Ep.\\ (?P<epno>[0-9]+)\\ \\((?P<minutes>[0-9]+)\\:(?P<seconds>[0-9]+).*$''', re.UNICODE),
187 re.compile(
'''Season\\ (?P<seasno>[0-9]+)\\:\\ Ep.\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
189 re.compile(
'''Ep.\\ (?P<epno>[0-9]+)\\ \\((?P<hours>[0-9]+)\\:(?P<minutes>[0-9]+)\\:(?P<seconds>[0-9]+).*$''', re.UNICODE),
191 re.compile(
'''Ep.\\ (?P<epno>[0-9]+)\\ \\((?P<minutes>[0-9]+)\\:(?P<seconds>[0-9]+).*$''', re.UNICODE),
193 re.compile(
'''Ep.\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
196 self.
channel_icon =
'%SHAREDIR%/mythnetvision/icons/thewb.png'
206 ''' Check is there is any season or episode number information in an item's title
207 return array of season and/or episode numbers plus any duration in minutes and seconds
208 return array with None values
215 return match.groups()
220 ''' Read the MNV The WB grabber "thewb_config.xml" configuration file
224 url =
'file://%s/nv_python_libs/configs/XML/thewb_config.xml' % (baseProcessingDir, )
225 if not os.path.isfile(url[7:]):
228 if self.
config[
'debug_enabled']:
233 except Exception
as e:
240 '''Read the thewb_config.xml and user preference thewb.xml file.
241 If the thewb.xml file does not exist then create it.
242 If the thewb.xml file is too old then update it.
250 if userPreferenceFile[0] ==
'~':
251 self.
thewb_config.
find(
'userPreferenceFile').text =
"%s%s" % (os.path.expanduser(
"~"), userPreferenceFile[1:])
255 if self.
config[
'debug_enabled']:
260 except Exception
as e:
263 nextUpdateSecs = int(self.
userPrefs.
find(
'updateDuration').text)*86400
264 nextUpdate = time.localtime(os.path.getmtime(self.
thewb_config.
find(
'userPreferenceFile').text)+nextUpdateSecs)
265 now = time.localtime()
278 ''' Create or update the thewb.xml user preferences file
282 url =
'file://%s/nv_python_libs/configs/XML/defaultUserPrefs/thewb.xml' % (baseProcessingDir, )
283 if not os.path.isfile(url[7:]):
286 if self.
config[
'debug_enabled']:
287 print(
'updateTheWB url(%s)' % url)
290 userTheWB = etree.parse(url)
291 except Exception
as e:
297 if self.
config[
'debug_enabled']:
298 print(
"create(%s)" % create)
300 sys.stdout.write(etree.tostring(linksTree, encoding=
'UTF-8', pretty_print=
True))
305 if not len(linksTree.xpath(
'//results//a')) > 10:
309 root = etree.XML(
'<xml></xml>')
310 for directory
in linksTree.xpath(
'//results'):
311 tmpDirectory = etree.SubElement(root,
'showDirectories')
312 tmpDirectory.attrib[
'name'] = directory.find(
'name').text
313 for show
in directory.xpath(
'.//a'):
316 if showName.lower().
find(
'dvd') != -1
or show.attrib[
'href'].lower().
find(
'dvd') != -1:
318 tmpShow = etree.XML(
'<url></url>')
319 tmpShow.attrib[
'enabled'] =
'true'
320 tmpShow.attrib[
'name'] = self.
common.massageText(showName.strip())
321 tmpShow.text = self.
common.ampReplace(show.attrib[
'href'].replace(
'/shows/',
'').replace(
'/',
'').strip())
322 tmpDirectory.append(tmpShow)
324 if self.
config[
'debug_enabled']:
325 print(
"Before any merging userTheWB:")
326 sys.stdout.write(etree.tostring(userTheWB, encoding=
'UTF-8', pretty_print=
True))
332 userTheWB.find(
'updateDuration').text = self.
userPrefs.
find(
'updateDuration').text
334 root.find(
'showDirectories').attrib[
'globalmax'] = self.
userPrefs.
find(
'showDirectories').attrib[
'globalmax']
335 for rss
in self.
userPrefs.xpath(
"//url[@enabled='false']"):
336 elements = root.xpath(
"//url[text()=$URL]", URL=rss.text.strip())
338 elements[0].attrib[
'enabled'] =
'false'
340 elements[0].attrib[
'max'] = rss.attrib[
'max']
342 if self.
config[
'debug_enabled']:
343 print(
"After any merging userTheWB:")
344 sys.stdout.write(etree.tostring(userTheWB, encoding=
'UTF-8', pretty_print=
True))
348 prefDir = self.
thewb_config.
find(
'userPreferenceFile').text.replace(
'/thewb.xml',
'')
349 if not os.path.isdir(prefDir):
352 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>')
358 except Exception
as e:
370 def searchTitle(self, title, pagenumber, pagelen, ignoreError=False):
371 '''Key word video search of the TheWB web site
372 return an array of matching item elements
378 searchVar =
'?q=%s' % (urllib.parse.quote(title.encode(
"utf-8")).replace(
' ',
'+'))
379 except UnicodeDecodeError:
380 searchVar =
'?q=%s' % (urllib.parse.quote(title).replace(
' ',
'+'))
381 url = self.
thewb_config.
find(
'searchURLS').xpath(
".//href")[0].text+searchVar
383 if self.
config[
'debug_enabled']:
384 print(
"Search url(%s)" % url)
392 except Exception
as errormsg:
398 if self.
config[
'debug_enabled']:
399 print(
"resultTree count(%s)" % len(resultTree))
400 print(etree.tostring(resultTree, encoding=
'UTF-8', pretty_print=
True))
403 if resultTree
is None:
406 raise TheWBVideoNotFound(
"No TheWB.com Video matches found for search value (%s)" % title)
408 searchResults = resultTree.xpath(
'//result/div')
409 if not len(searchResults):
412 raise TheWBVideoNotFound(
"No TheWB.com Video matches found for search value (%s)" % title)
415 self.
channel[
'channel_numresults'] = len(searchResults)
419 pubDate = datetime.datetime.now().strftime(self.
common.pubDateFormat)
422 thumbNailFilter = etree.XPath(
'.//div[@class="overlay_thumb_area"]//img')
423 textFilter = etree.XPath(
'.//div[@class="overlay-bg-middle"]/p')
424 titleFilter = etree.XPath(
'.//div[@class="overlay_thumb_area"]//a[@title!=""]/@title')
425 descFilter = etree.XPath(
'.//div[@class="overlay-bg-middle"]/p[@class="overlay_extra overlay_spacer_top"]/text()')
426 linkFilter = etree.XPath(
'.//div[@class="overlay_thumb_area"]//a[@title!=""]/@href')
427 itemThumbNail = etree.XPath(
'.//media:thumbnail', namespaces=self.
common.namespaces)
428 itemDwnLink = etree.XPath(
'.//media:content', namespaces=self.
common.namespaces)
430 for result
in searchResults:
432 thewbItem = etree.XML(self.
common.mnvItem)
434 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text =
'us'
438 link =
'file://%s/nv_python_libs/configs/HTML/thewb.html?videocode=%s' % (baseProcessingDir, result.attrib[
'id'].replace(
'video_',
''))
439 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text =
'true'
440 descriptionElement = textFilter(result)[0]
445 for e
in descriptionElement.xpath(
'./*'):
447 eText = str(e.tail,
'UTF-8').strip()
450 if eText.startswith(
'Season ')
or eText.startswith(
'EP'):
454 infoList =
'S%02dE%02d' % (int(sed[0]), int(sed[1]))
455 seasonNum =
'%d' % int(sed[0])
456 episodeNum =
'%d' % int(sed[1])
458 videoSeconds = int(sed[2])*3600+int(sed[3])*60+int(sed[4])
459 itemDwnLink(thewbItem)[0].attrib[
'duration'] = str(videoSeconds)
461 videoSeconds = int(sed[2])*60+int(sed[3])
462 itemDwnLink(thewbItem)[0].attrib[
'duration'] = str(videoSeconds)
464 index = title.find(
':')
466 tmptitle =
'%s: %s %s' % (title[:index].strip(), infoList, title[index+1:].strip())
468 tmptitle =
'%s: %s' % (title.strip(), infoList)
471 title = self.
common.massageText(title.strip())
472 description = self.
common.massageText(descFilter(result)[0].strip())
475 thewbItem.find(
'title').text = title
476 thewbItem.find(
'author').text =
"The WB.com"
477 thewbItem.find(
'pubDate').text = pubDate
478 thewbItem.find(
'description').text = description
479 thewbItem.find(
'link').text = link
483 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = seasonNum
485 etree.SubElement(thewbItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = episodeNum
486 itemDict[title.lower()] = thewbItem
488 if not len(list(itemDict.keys())):
491 raise TheWBVideoNotFound(
"No TheWB Video matches found for search value (%s)" % title)
493 return [itemDict, resultTree.xpath(
'//pageInfo')[0].text]
498 """Common name for a video search. Used to interface with MythTV plugin NetVision
503 if self.
config[
'debug_enabled']:
504 print(
"self.thewb_config:")
505 sys.stdout.write(etree.tostring(self.
thewb_config, encoding=
'UTF-8', pretty_print=
True))
514 data = self.
searchTitle(title, pagenumber, self.page_limit)
515 except TheWBVideoNotFound
as msg:
516 sys.stderr.write(
"%s\n" % msg)
518 except TheWBUrlError
as msg:
519 sys.stderr.write(
'%s\n' % msg)
521 except TheWBHttpError
as msg:
524 except TheWBRssError
as msg:
527 except Exception
as e:
528 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
532 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
535 itemCount = len(list(data[0].keys()))
536 if data[1] ==
'true':
537 self.
channel[
'channel_returned'] = itemCount
538 self.
channel[
'channel_startindex'] = itemCount
539 self.
channel[
'channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
541 self.
channel[
'channel_returned'] = itemCount
542 self.
channel[
'channel_startindex'] = self.
channel[
'channel_returned']
543 self.
channel[
'channel_numresults'] = self.
channel[
'channel_returned']
547 rssTree.append(channelTree)
551 for key
in sorted(data[0].keys()):
553 channelTree.append(data[0][key])
557 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
558 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
563 '''Gather the The WB feeds then get a max page of videos meta data in each of them
564 Display the results and exit
569 except Exception
as e:
570 sys.stderr.write(
'%s\n' % e)
574 showFeeds = self.
userPrefs.xpath(
"//showDirectories//url[@enabled='true']")
575 totalFeeds = self.
userPrefs.xpath(
"//url[@enabled='true']")
577 if self.
config[
'debug_enabled']:
578 print(
"self.userPrefs show count(%s) total feed count(%s):" % (len(showFeeds), len(totalFeeds)))
579 sys.stdout.write(etree.tostring(self.
userPrefs, encoding=
'UTF-8', pretty_print=
True))
582 if not len(totalFeeds):
583 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)
590 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
594 rssTree.append(channelTree)
598 if len(showFeeds)
is not None:
599 for searchDetails
in showFeeds:
601 data = self.
searchTitle(searchDetails.text.strip(), 1, self.page_limit, ignoreError=
True)
604 except TheWBVideoNotFound
as msg:
605 sys.stderr.write(
"%s\n" % msg)
607 except TheWBUrlError
as msg:
608 sys.stderr.write(
'%s\n' % msg)
610 except TheWBHttpError
as msg:
613 except TheWBRssError
as msg:
616 except Exception
as e:
617 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (searchDetails.text.strip(), e))
619 data.append(searchDetails.attrib[
'name'])
620 showItems[self.
common.massageText(searchDetails.text.strip())] = data
623 if self.
config[
'debug_enabled']:
624 print(
"After searches 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 for showNameKey
in list(showItems.keys()):
632 for key
in list(showItems[showNameKey][0].keys()):
633 tmpLink = showItems[showNameKey][0][key].
find(
'link').text.replace(self.
thewb_config.
find(
'searchURLS').xpath(
".//href")[0].text,
'')
634 if tmpLink.startswith(showNameKey):
635 tmpList[key] = showItems[showNameKey][0][key]
636 showItems[showNameKey][0] = tmpList
638 if self.
config[
'debug_enabled']:
639 print(
"After search filter of non-show items count(%s):" % len(showItems))
640 for key
in list(showItems.keys()):
641 print(
"Show(%s) name(%s) item count(%s)" % (key, showItems[key][2], len(showItems[key][0])))
645 rssData = etree.XML(
'<xml></xml>')
646 rssFeedsUrl =
'http://www.thewb.com/shows/feed/'
647 for feedType
in self.
userPrefs.findall(
'showDirectories'):
648 for rssFeed
in self.
userPrefs.xpath(
"//showDirectories/url[@enabled='true']"):
649 link = rssFeedsUrl+rssFeed.text
650 urlName = rssFeed.attrib.get(
'name')
652 uniqueName =
'%s;%s' % (urlName, link)
654 uniqueName =
'RSS;%s' % (link)
655 url = etree.XML(
'<url></url>')
656 etree.SubElement(url,
"name").text = uniqueName
657 etree.SubElement(url,
"href").text = link
658 etree.SubElement(url,
"filter").text =
"//channel/title"
659 etree.SubElement(url,
"filter").text =
"//item"
660 etree.SubElement(url,
"parserType").text =
'xml'
663 if self.
config[
'debug_enabled']:
665 sys.stdout.write(etree.tostring(rssData, encoding=
'UTF-8', pretty_print=
True))
683 if rssData.find(
'url')
is not None:
685 resultTree = self.
common.getUrlData(rssData)
686 except Exception
as errormsg:
689 if self.
config[
'debug_enabled']:
691 sys.stdout.write(etree.tostring(resultTree, encoding=
'UTF-8', pretty_print=
True))
695 for result
in resultTree.findall(
'results'):
696 names = result.find(
'name').text.split(
';')
697 names[0] = self.
common.massageText(names[0])
698 if names[0] ==
'RSS':
699 names[0] = self.
common.massageText(self.
rssName(result.find(
'result'))[0].text.strip())
702 urlName = result.find(
'url').text.replace(rssFeedsUrl,
'').strip()
707 if url[0].attrib.get(
'max'):
709 urlMax = int(url[0].attrib.get(
'max'))
712 elif url[0].getparent().attrib.get(
'globalmax'):
714 urlMax = int(url[0].getparent().attrib.get(
'globalmax'))
719 if self.
config[
'debug_enabled']:
720 print(
"Results: #Items(%s) for (%s)" % (len(self.
itemFilter(result)), names))
722 self.
createItems(showItems, result, urlName, names[0], urlMax=urlMax)
726 for key
in sorted(showItems.keys()):
727 if not len(showItems[key][0]):
730 directoryElement = etree.SubElement(channelTree,
'directory')
731 directoryElement.attrib[
'name'] = showItems[key][2]
732 directoryElement.attrib[
'thumbnail'] = self.
channel_icon
734 if self.
config[
'debug_enabled']:
735 print(
"Results: #Items(%s) for (%s)" % (len(showItems[key][0]), showItems[key][2]))
739 for itemKey
in sorted(showItems[key][0].keys()):
740 directoryElement.append(showItems[key][0][itemKey])
742 if self.
config[
'debug_enabled']:
743 print(
"Final results: #Items(%s)" % len(rssTree.xpath(
'//item')))
747 if len(rssTree.xpath(
'//item')):
749 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
750 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
755 def createItems(self, showItems, result, urlName, showName, urlMax=None):
756 '''Create a dictionary of MNV compliant RSS items from the results of a RSS feed show search.
757 Also merge with any items that were found by using the Web search. Identical items use the RSS
758 feed item data over the search item as RSS provides better results.
759 return nothing as the show item dictionary will have all the results
762 if not urlName
in list(showItems.keys()):
763 showItems[urlName] = [{},
None, showName]
768 newItem = etree.XML(self.
common.mnvItem)
770 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text =
'us'
772 tmpLink = self.
linkFilter(thewbItem)[0].text.strip()
773 link = self.
common.ampReplace(
'file://%s/nv_python_libs/configs/HTML/thewb.html?videocode=%s' % (baseProcessingDir, tmpLink[tmpLink.rfind(
'/')+1:]))
774 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text =
'true'
778 pubdate = pubdate[0].text[:-6]
779 pubdate = time.strptime(pubdate,
'%Y-%m-%dT%H:%M:%S')
780 pubdate = time.strftime(self.
common.pubDateFormat, pubdate)
782 pubdate = datetime.datetime.now().strftime(self.
common.pubDateFormat)
789 for eText
in descList:
792 eText = eText.strip().encode(
'UTF-8')
797 if eText.startswith(
'Season: ')
or eText.startswith(
'EP: '):
798 s_e = eText.replace(
'Season:',
'').replace(
', Episode:',
'').replace(
'EP:',
'').strip().split(
' ')
799 if len(s_e) == 1
and can_int(s_e[0].strip()):
800 eText =
'Ep(%02d)' % int(s_e[0].strip())
801 episodeNum = s_e[0].strip()
802 elif len(s_e) == 2
and can_int(s_e[0].strip())
and can_int(s_e[1].strip()):
803 eText =
'S%02dE%02d' % (int(s_e[0].strip()), int(s_e[1].strip()))
804 seasonNum = s_e[0].strip()
805 episodeNum = s_e[1].strip()
806 title = title.replace(
'-',
'–')
807 index = title.find(
'–')
809 tmptitle =
'%s: %s %s' % (title[:index].strip(), eText.strip(), title[index:].strip())
811 tmptitle =
'%s %s' % (title, eText.strip())
813 elif eText.startswith(
'Running Time: '):
814 videoDuration = eText.replace(
'Running Time: ',
'').strip().split(
':')
815 if not len(videoDuration):
819 if len(videoDuration) == 1:
820 videoSeconds = int(videoDuration[0])
821 elif len(videoDuration) == 2:
822 videoSeconds = int(videoDuration[0])*60+int(videoDuration[1])
823 elif len(videoDuration) == 3:
824 videoSeconds = int(videoDuration[0])*3600+int(videoDuration[1])*60+int(videoDuration[2])
826 self.
itemDwnLink(newItem)[0].attrib[
'duration'] = str(videoSeconds)
829 except UnicodeDecodeError:
834 title = self.
common.massageText(title.strip())
835 description = self.
common.massageText(description.strip())
837 newItem.find(
'title').text = title
839 newItem.find(
'pubDate').text = pubdate
840 newItem.find(
'description').text = description
841 newItem.find(
'link').text = link
849 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = seasonNum
851 etree.SubElement(newItem,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = episodeNum
853 showItems[urlName][0][title.lower()] = newItem