14 __title__ =
"rev3_api - Simple-to-use Python interface to the Revision3 RSS feeds (http://revision3.com/)"
15 __author__=
"R.D. Vaughan"
17 This python script is intended to perform a variety of utility functions to search and access text
18 meta data, video and image URLs from rev3. These routines process RSS feeds provided by Revision3
19 (http://revision3.com/). The specific Revision3 RSS feeds that are processed are controled through
20 a user XML preference file usually found at "~/.mythtv/MythNetvision/userGrabberPrefs/rev3.xml"
33 import os, struct, sys, re, time, datetime, urllib.request, urllib.parse, urllib.error, re
35 from socket
import gethostname, gethostbyname
36 from threading
import Thread
37 from copy
import deepcopy
39 from .rev3_exceptions
import (Rev3UrlError, Rev3HttpError, Rev3RssError, Rev3VideoNotFound, Rev3ConfigFileError, Rev3UrlDownloadError)
43 """Wraps a stream with an encoder"""
52 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
53 if isinstance(obj, str):
55 self.
out.buffer.write(obj)
58 """Delegate everything but write to the stream"""
59 return getattr(self.
out, attr)
61 if isinstance(sys.stdout, io.TextIOWrapper):
67 from io
import StringIO
68 from lxml
import etree
69 except Exception
as e:
70 sys.stderr.write(
'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
75 """Main interface to http://www.rev3.com/
76 This is done to support a common naming framework for all python Netvision plugins no matter their site
79 Supports search methods
80 The apikey is a not required to access http://www.rev3.com/
90 search_all_languages = False,
92 """apikey (str/unicode):
93 Specify the target site API key. Applications need their own key in some cases
96 When True, the returned meta data is being returned has the key and values massaged to match MythTV
97 When False, the returned meta data is being returned matches what target site returned
99 interactive (True/False): (This option is not supported by all target site apis)
100 When True, uses built-in console UI is used to select the correct show.
101 When False, the first search result is used.
103 select_first (True/False): (This option is not supported currently implemented in any grabbers)
104 Automatically selects the first series search result (rather
105 than showing the user a list of more than one series).
106 Is overridden by interactive = False, or specifying a custom_ui
109 shows verbose debugging information
111 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
112 A callable subclass of interactive class (overrides interactive option)
114 language (2 character language abbreviation): (This option is not supported by all target site apis)
115 The language of the returned data. Is also the language search
116 uses. Default is "en" (English). For full list, run..
118 search_all_languages (True/False): (This option is not supported by all target site apis)
119 By default, a Netvision grabber will only search in the language specified using
120 the language option. When this is True, it will search for the
126 if apikey
is not None:
127 self.
config[
'apikey'] = apikey
131 self.
config[
'debug_enabled'] = debug
139 self.
config[
'custom_ui'] = custom_ui
143 self.
config[
'select_first'] = select_first
145 self.
config[
'search_all_languages'] = search_all_languages
147 self.
error_messages = {
'Rev3UrlError':
"! Error: The URL (%s) cause the exception error (%s)\n",
'Rev3HttpError':
"! Error: An HTTP communications error with Rev3 was raised (%s)\n",
'Rev3RssError':
"! Error: Invalid RSS meta data\nwas received from Rev3 error (%s). Skipping item.\n",
'Rev3VideoNotFound':
"! Error: Video search with Rev3 did not return any results (%s)\n",
'Rev3ConfigFileError':
"! Error: rev3_config.xml file missing\nit should be located in and named as (%s).\n",
'Rev3UrlDownloadError':
"! Error: Downloading a RSS feed or Web page (%s).\n", }
150 self.
channel = {
'channel_title':
'Revision3',
'channel_link':
'http://revision3.com/',
'channel_description':
"Revision3 is the leading television network for the internet generation.",
'channel_numresults': 0,
'channel_returned': 1,
'channel_startindex': 0}
155 re.compile(
'''^.+?Season\\ (?P<seasno>[0-9]+).*.+?Episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
157 re.compile(
'''^.+?Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
159 re.compile(
'''Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
161 re.compile(
'''^.+?--(?P<seasno>[0-9]+)--.*$''', re.UNICODE),
164 self.
FullScreen =
'http://revision3.com/show/popupPlayer?video_id=%s&quality=high&offset=0'
168 self.
channel_icon =
'%SHAREDIR%/mythnetvision/icons/rev3.png'
178 ''' Read the MNV Revision3 grabber "rev3_config.xml" configuration file
182 url =
'file://%s/nv_python_libs/configs/XML/rev3_config.xml' % (baseProcessingDir, )
183 if not os.path.isfile(url[7:]):
186 if self.
config[
'debug_enabled']:
191 except Exception
as e:
198 '''Read the rev3_config.xml and user preference rev3.xml file.
199 If the rev3.xml file does not exist then create it.
200 If the rev3.xml file is too old then update it.
208 if userPreferenceFile[0] ==
'~':
209 self.
rev3_config.
find(
'userPreferenceFile').text =
"%s%s" % (os.path.expanduser(
"~"), userPreferenceFile[1:])
212 url =
'file://%s' % (self.
rev3_config.
find(
'userPreferenceFile').text, )
213 if self.
config[
'debug_enabled']:
218 except Exception
as e:
221 nextUpdateSecs = int(self.
userPrefs.
find(
'updateDuration').text)*86400
222 nextUpdate = time.localtime(os.path.getmtime(self.
rev3_config.
find(
'userPreferenceFile').text)+nextUpdateSecs)
223 now = time.localtime()
236 ''' Create or update the rev3.xml user preferences file
239 userRev3 = etree.XML(
'''
242 The shows are split into three top level directories which represent how Rev3 categories
243 their videos. Each top level directory has one or more shows. Each show has one or more
244 MP4 formats that can be downloaded. The formats represent various video quality levels.
245 Initially only three shows are enabled. You must change a show's "mp4Format" element's
246 "enabled" attribute to 'true'. When you change the attribute to 'true' that show's RSS feed
247 will be added to the Rev3 tree view. You could activate more than one MP4 format but that
248 would result in duplicates. With the exception of "Tekzilla" which is a show that has
249 both a weekly and daily video RSS feed within the same show element.
250 When the Rev3 Tree view is created it will have links to the video's web page but also a
251 link to the MP4 file that you can download through the MythNetvision interface.
252 If a show moves from one top level directory to another your show sections will be
253 preserved as long as that format is available in the new top level directory.
254 Updates to the "rev3.xml" file is made every X number of days as determined by the value of
255 the "updateDuration" element in this file. The default is every 3 days.
257 <!-- Number of days between updates to the config file -->
258 <updateDuration>3</updateDuration>
262 "globalmax" (optional) Is a way to limit the number of items processed per RSS feed for all
263 treeview URLs. A value of zero (0) means there are no limitions.
264 "max" (optional) Is a way to limit the number of items processed for an individual RSS feed.
265 This value will override any "globalmax" setting. A value of zero (0) means
266 there are no limitions and would be the same if the attribute was no included at all.
267 "enabled" If you want to remove a RSS feed then change the "enabled" attribute to "false".
269 See details: http://revision3.com/blog/2010/03/11/pick-your-favorite-shows-create-a-personalized-feed/
270 Once you sign up and create your personal RSS feed replace the url in the example below with the
271 Revision3 "Your RSS Feed Address" URL and change the "enabled" element attribute to "true".
273 <treeviewURLS globalmax="0">
274 <url enabled="false">http://revision3.com/feed/user/EXAMPLE</url>
282 if self.
config[
'debug_enabled']:
283 print(
"create(%s)" % create)
285 sys.stdout.write(etree.tostring(linksTree, encoding=
'UTF-8', pretty_print=
True))
289 showData = etree.XML(
'<xml></xml>')
290 complexFilter =
"//div[@class='subscribe_rss']//div//p[.='MP4']/..//a"
291 for result
in linksTree.xpath(
'//results'):
292 tmpDirectory = etree.XML(
'<directory></directory>')
293 dirName = result.find(
'name').text
294 tmpDirectory.attrib[
'name'] = dirName
296 if self.
config[
'debug_enabled']:
297 print(
"Results: #Items(%s) for (%s)" % (len(result.xpath(
'.//a')), dirName))
300 for anchor
in result.xpath(
'.//a'):
302 if dirName ==
'Shows':
303 showURL = anchor.attrib.get(
'href')
304 showFilter = complexFilter
305 tmpName = anchor.text
306 elif dirName ==
'Revision3 Beta':
307 tmpName = etree.tostring(anchor, method=
"text", encoding=str)
308 showURL =
'http://revision3beta.com%sfeed/' % anchor.attrib.get(
'href')
310 elif dirName ==
'Archived Shows':
311 showURL =
'http://revision3.com%s' % anchor.attrib.get(
'href')
312 showFilter = complexFilter
313 tmpName = anchor.text
314 if tmpName ==
'Revision3 Beta':
316 if showURL
is not None:
317 url = etree.SubElement(tmpDirectory,
"url")
318 etree.SubElement(url,
"name").text = tmpName
319 etree.SubElement(url,
"href").text = showURL
320 etree.SubElement(url,
"filter").text = showFilter
321 etree.SubElement(url,
"parserType").text =
'html'
322 if tmpDirectory.find(
'url')
is not None:
323 showData.append(tmpDirectory)
325 if self.
config[
'debug_enabled']:
327 sys.stdout.write(etree.tostring(showData, encoding=
'UTF-8', pretty_print=
True))
331 for directory
in showData.findall(
'directory'):
336 tmpDirectory = etree.XML(
'<directory></directory>')
337 tmpDirectory.attrib[
'name'] = directory.attrib[
'name']
338 if directory.attrib[
'name'] ==
'Revision3 Beta':
339 for show
in directory.findall(
'url'):
340 tmpShow = etree.XML(
'<show></show>')
341 tmpShow.attrib[
'name'] = show.find(
'name').text
342 mp4Format = etree.SubElement(tmpShow,
"mp4Format")
344 mp4Format.attrib[
'enabled'] =
'true'
347 mp4Format.attrib[
'enabled'] =
'false'
348 mp4Format.attrib[
'name'] =
'Web Only'
349 mp4Format.attrib[
'rss'] = show.find(
'href').text
350 tmpDirectory.append(tmpShow)
352 showResults = self.
common.getUrlData(directory)
353 for show
in showResults.xpath(
'//results'):
354 tmpShow = etree.XML(
'<show></show>')
355 tmpShow.attrib[
'name'] = show.find(
'name').text
357 if self.
config[
'debug_enabled']:
358 print(
"Results: #Items(%s) for (%s)" % (len(show.xpath(
'.//a')), tmpShow.attrib[
'name']))
361 for format
in show.xpath(
'.//a'):
362 link =
'http://revision3.com%s' % format.attrib[
'href']
365 if link.find(
'/tekzilla/') != -1
and link.find(
'?subshow=false') == -1:
367 mp4Format = etree.SubElement(tmpShow,
"mp4Format")
369 mp4Format.attrib[
'enabled'] =
'true'
372 mp4Format.attrib[
'enabled'] =
'false'
373 mp4Format.attrib[
'name'] = format.text
374 mp4Format.attrib[
'rss'] = link
375 if tmpShow.find(
'mp4Format')
is not None:
376 tmpDirectory.append(tmpShow)
379 if tmpDirectory.find(
'show')
is not None:
380 userRev3.append(tmpDirectory)
382 if self.
config[
'debug_enabled']:
383 print(
"Before any merging userRev3:")
384 sys.stdout.write(etree.tostring(userRev3, encoding=
'UTF-8', pretty_print=
True))
389 userRev3.find(
'updateDuration').text = self.
userPrefs.
find(
'updateDuration').text
390 for mp4Format
in self.
userPrefs.xpath(
"//mp4Format[@enabled='true']"):
391 showName = mp4Format.getparent().attrib[
'name']
392 mp4name = mp4Format.attrib[
'name']
393 elements = userRev3.xpath(
"//show[@name=$showName]/mp4Format[@name=$mp4name]", showName=showName, mp4name=mp4name)
395 elements[0].attrib[
'enabled'] =
'true'
397 if self.
config[
'debug_enabled']:
398 print(
"After any merging userRev3:")
399 sys.stdout.write(etree.tostring(userRev3, encoding=
'UTF-8', pretty_print=
True))
403 prefDir = self.
rev3_config.
find(
'userPreferenceFile').text.replace(
'/rev3.xml',
'')
404 if not os.path.isdir(prefDir):
407 fd.write(
'<userRev3>\n'+
''.join(etree.tostring(element, encoding=
'UTF-8', pretty_print=
True)
for element
in userRev3)+
'</userRev3>')
413 except Exception
as e:
419 ''' Check is there is any season or episode number information in an item's title
420 return array of season and/or episode numbers
421 return array with None values
434 s_e[0], s_e[1] = match.groups()
437 s_e[1] =
'%s' % int(match.groups()[0])
443 ''' Read the Web page to find the video ID number used for fullscreen autoplay
444 return the video ID number
445 return None if the number cannot be found
450 except Exception
as errormsg:
451 sys.stderr.write(
"! Error: The URL (%s) cause the exception error (%s)\n" % (link, errormsg))
454 if self.
config[
'debug_enabled']:
455 print(
"Raw unfiltered URL imput:")
456 sys.stdout.write(etree.tostring(eTree, encoding=
'UTF-8', pretty_print=
True))
465 except AssertionError
as e:
466 sys.stderr.write(
"No filter results for VideoID from url(%s)\n" % link)
467 sys.stderr.write(
"! Error:(%s)\n" % e)
471 if tmpVideoID[0].
get(
'id'):
472 videoID = tmpVideoID[0].attrib[
'id'].strip().replace(
'player-',
'')
484 '''Key word video search of the Rev3 web site
485 return an array of matching item elements
489 searchVar =
'?q=%s' % (urllib.parse.quote(title.encode(
"utf-8")).replace(
' ',
'+'))
490 except UnicodeDecodeError:
491 searchVar =
'?q=%s' % (urllib.parse.quote(title).replace(
' ',
'+'))
492 url = self.
rev3_config.
find(
'searchURLS').xpath(
".//href")[0].text+searchVar
494 if self.
config[
'debug_enabled']:
503 except Exception
as errormsg:
506 if resultTree
is None:
507 raise Rev3VideoNotFound(
"No Rev3 Video matches found for search value (%s)" % title)
509 searchResults = resultTree.xpath(
'//result//li[@class="video"]')
510 if not len(searchResults):
511 raise Rev3VideoNotFound(
"No Rev3 Video matches found for search value (%s)" % title)
514 self.
channel[
'channel_numresults'] = len(searchResults)
518 pubDate = datetime.datetime.now().strftime(self.
common.pubDateFormat)
521 thumbnailFilter = etree.XPath(
'.//a[@class="thumbnail"]/img/@src')
522 titleFilter = etree.XPath(
'.//a[@class="title"]')
523 descFilter = etree.XPath(
'.//div[@class="description"]')
524 itemThumbNail = etree.XPath(
'.//media:thumbnail', namespaces=self.
common.namespaces)
525 itemDwnLink = etree.XPath(
'.//media:content', namespaces=self.
common.namespaces)
527 for result
in searchResults:
528 if len(titleFilter(result)):
529 rev3Item = etree.XML(self.
common.mnvItem)
531 thumbNail = self.
common.ampReplace(thumbnailFilter(result)[0])
532 title = self.
common.massageText(titleFilter(result)[0].text.strip())
533 tmpDesc = etree.tostring(descFilter(result)[0], method=
"text", encoding=str).strip()
534 index = tmpDesc.find(
'–')
536 tmpDesc = tmpDesc[index+1:].strip()
537 description = self.
common.massageText(tmpDesc)
538 link = self.
common.ampReplace(titleFilter(result)[0].attrib[
'href'])
541 rev3Item.find(
'title').text = title
542 rev3Item.find(
'author').text = author
543 rev3Item.find(
'pubDate').text = pubDate
544 rev3Item.find(
'description').text = description
545 rev3Item.find(
'link').text = link
546 itemThumbNail(rev3Item)[0].attrib[
'url'] = thumbNail
547 itemDwnLink(rev3Item)[0].attrib[
'url'] = link
550 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
552 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
553 itemDict[title.lower()] = rev3Item
555 if not len(list(itemDict.keys())):
556 raise Rev3VideoNotFound(
"No Rev3 Video matches found for search value (%s)" % title)
558 return [itemDict, resultTree.xpath(
'//pageInfo')[0].text]
563 """Common name for a video search. Used to interface with MythTV plugin NetVision
568 if self.
config[
'debug_enabled']:
569 print(
"self.rev3_config:")
570 sys.stdout.write(etree.tostring(self.
rev3_config, encoding=
'UTF-8', pretty_print=
True))
579 data = self.
searchTitle(title, pagenumber, self.page_limit)
580 except Rev3VideoNotFound
as msg:
581 sys.stderr.write(
"%s\n" % msg)
583 except Rev3UrlError
as msg:
584 sys.stderr.write(
'%s\n' % msg)
586 except Rev3HttpError
as msg:
589 except Rev3RssError
as msg:
592 except Exception
as e:
593 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
597 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
600 itemCount = len(list(data[0].keys()))
601 if data[1] ==
'true':
602 self.
channel[
'channel_returned'] = itemCount
603 self.
channel[
'channel_startindex'] = itemCount
604 self.
channel[
'channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
606 self.
channel[
'channel_returned'] = itemCount+(self.page_limit*(int(pagenumber)-1))
607 self.
channel[
'channel_startindex'] = self.
channel[
'channel_returned']
608 self.
channel[
'channel_numresults'] = self.
channel[
'channel_returned']
612 rssTree.append(channelTree)
615 for key
in sorted(data[0].keys()):
617 channelTree.append(data[0][key])
621 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
622 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
627 '''Gather the Revision3 feeds then get a max page of videos meta data in each of them
628 Display the results and exit
630 personalFeed =
"Personal Feed"
635 except Exception
as e:
636 sys.stderr.write(
'%s' % e)
639 if self.
config[
'debug_enabled']:
640 print(
"self.userPrefs:")
641 sys.stdout.write(etree.tostring(self.
userPrefs, encoding=
'UTF-8', pretty_print=
True))
645 rssFeeds = self.
userPrefs.xpath(
"//mp4Format[@enabled='true']")
646 personalFeeds = self.
userPrefs.xpath(
"//treeviewURLS//url[@enabled='true']")
647 if not len(rssFeeds)
and not len(personalFeeds):
648 sys.stderr.write(
'There are no mp4Format or personal RSS feed elements "enabled" in your "rev3.xml" user preferences\nfile (%s)\n' % self.
rev3_config.
find(
'userPreferenceFile').text)
652 showData = etree.XML(
'<xml></xml>')
653 for feed
in personalFeeds:
654 rssFeeds.append(feed)
656 for rssFeed
in rssFeeds:
657 if rssFeed.getparent().tag ==
'treeviewURLS':
658 uniqueName =
'%s:%s' % (personalFeed, count)
661 uniqueName =
'%s:%s:%s' % (rssFeed.getparent().getparent().attrib[
'name'], rssFeed.getparent().attrib[
'name'], rssFeed.attrib[
'name'])
662 url = etree.XML(
'<url></url>')
663 etree.SubElement(url,
"name").text = uniqueName
664 if uniqueName.startswith(personalFeed):
665 etree.SubElement(url,
"href").text = rssFeed.text
667 etree.SubElement(url,
"href").text = rssFeed.attrib[
'rss']
668 etree.SubElement(url,
"filter").text =
"//channel"
669 etree.SubElement(url,
"parserType").text =
'xml'
672 if self.
config[
'debug_enabled']:
674 sys.stdout.write(etree.tostring(showData, encoding=
'UTF-8', pretty_print=
True))
679 resultTree = self.
common.getUrlData(showData)
680 except Exception
as errormsg:
683 if resultTree
is None:
686 if self.
config[
'debug_enabled']:
688 sys.stdout.write(etree.tostring(resultTree, encoding=
'UTF-8', pretty_print=
True))
692 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
696 rssTree.append(channelTree)
699 itemFilter = etree.XPath(
'.//item')
700 channelFilter = etree.XPath(
'./result/channel')
701 imageFilter = etree.XPath(
'.//image/url')
702 itemDwnLink =
'.//media:content'
703 itemThumbNail =
'.//media:thumbnail'
704 itemDuration =
'.//media:content'
705 itemLanguage =
'.//media:content'
708 categoryElement = etree.XML(
'<directory></directory>')
709 itemAuthor =
'Revision3'
710 for result
in resultTree.findall(
'results'):
711 names = result.find(
'name').text.split(
':')
712 for index
in range(len(names)):
713 names[index] = self.
common.massageText(names[index])
714 channel = channelFilter(result)[0]
715 if channel.find(
'image')
is not None:
716 channelThumbnail = self.
common.ampReplace(imageFilter(channel)[0].text)
718 channelThumbnail = self.
common.ampReplace(channel.find(
'link').text.replace(
'/watch/',
'/images/')+
'100.jpg')
719 channelLanguage =
'en'
720 if channel.find(
'language')
is not None:
721 channelLanguage = channel.find(
'language').text[:2]
723 if names[0] != categoryDir:
724 if categoryDir
is not None:
725 channelTree.append(categoryElement)
726 categoryElement = etree.XML(
'<directory></directory>')
727 if names[0] == personalFeed:
728 categoryElement.attrib[
'name'] = channel.find(
'title').text
730 categoryElement.attrib[
'name'] = names[0]
732 categoryDir = names[0]
733 if names[1] != showDir:
734 if names[0] == personalFeed:
735 showElement = categoryElement
737 showElement = etree.SubElement(categoryElement,
"directory")
738 if names[2] ==
'Web Only':
739 showElement.attrib[
'name'] =
'%s' % (names[1])
741 showElement.attrib[
'name'] =
'%s: %s' % (names[1], names[2])
742 showElement.attrib[
'thumbnail'] = channelThumbnail
745 if self.
config[
'debug_enabled']:
746 print(
"Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names))
750 for itemData
in itemFilter(result):
751 rev3Item = etree.XML(self.
common.mnvItem)
753 rev3Item.find(
'title').text = self.
common.massageText(itemData.find(
'title').text.strip())
754 rev3Item.find(
'author').text = itemAuthor
755 rev3Item.find(
'pubDate').text = self.
common.massageText(itemData.find(
'pubDate').text)
756 rev3Item.find(
'description').text = self.
common.massageText(itemData.find(
'description').text.strip())
757 link = self.
common.ampReplace(itemData.find(
'link').text)
758 downLoadLink = self.
common.ampReplace(itemData.xpath(itemDwnLink, namespaces=self.
common.namespaces)[0].attrib[
'url'])
762 if names[0] ==
'Shows' or names[0] == personalFeed:
763 fullScreenVideoID = self.
getVideoID(itemData.find(
'link').text)
764 if fullScreenVideoID:
765 if link == downLoadLink:
769 rev3Item.find(
'link').text = self.
common.ampReplace(link)
770 rev3Item.xpath(itemDwnLink, namespaces=self.
common.namespaces)[0].attrib[
'url'] = downLoadLink
772 rev3Item.xpath(itemThumbNail, namespaces=self.
common.namespaces)[0].attrib[
'url'] = self.
common.ampReplace(itemData.xpath(itemThumbNail, namespaces=self.
common.namespaces)[0].attrib[
'url'].replace(
'--mini',
'--medium'))
776 rev3Item.xpath(itemDuration, namespaces=self.
common.namespaces)[0].attrib[
'duration'] = itemData.xpath(itemDuration, namespaces=self.
common.namespaces)[0].attrib[
'duration']
779 rev3Item.xpath(itemLanguage, namespaces=self.
common.namespaces)[0].attrib[
'lang'] = channelLanguage
780 if rev3Item.xpath(itemThumbNail, namespaces=self.
common.namespaces)[0].
get(
'url'):
781 s_e = self.
getSeasonEpisode(rev3Item.find(
'title').text, rev3Item.xpath(itemThumbNail, namespaces=self.
common.namespaces)[0].attrib[
'url'])
783 s_e = self.
getSeasonEpisode(rev3Item.find(
'title').text, rev3Item.xpath(itemDwnLink, namespaces=self.
common.namespaces)[0].attrib[
'url'])
785 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
787 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
788 if s_e[0]
and s_e[1]:
789 rev3Item.find(
'title').text =
'S%02dE%02d: %s' % (int(s_e[0]), int (s_e[1]), rev3Item.find(
'title').text)
791 rev3Item.find(
'title').text =
'S%02d: %s' % (int(s_e[0]), rev3Item.find(
'title').text)
793 rev3Item.find(
'title').text =
'Ep%03d: %s' % (int(s_e[1]), rev3Item.find(
'title').text)
794 showElement.append(rev3Item)
797 if categoryElement.xpath(
'.//item')
is not None:
798 channelTree.append(categoryElement)
801 if len(rssTree.xpath(
'//item')):
803 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
804 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))