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)
78 for digit
in etree.LIBXML_VERSION:
79 version+=str(digit)+
'.'
80 version = version[:-1]
83 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
84 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
90 """Main interface to http://www.rev3.com/
91 This is done to support a common naming framework for all python Netvision plugins no matter their site
94 Supports search methods
95 The apikey is a not required to access http://www.rev3.com/
101 select_first = False,
105 search_all_languages = False,
107 """apikey (str/unicode):
108 Specify the target site API key. Applications need their own key in some cases
111 When True, the returned meta data is being returned has the key and values massaged to match MythTV
112 When False, the returned meta data is being returned matches what target site returned
114 interactive (True/False): (This option is not supported by all target site apis)
115 When True, uses built-in console UI is used to select the correct show.
116 When False, the first search result is used.
118 select_first (True/False): (This option is not supported currently implemented in any grabbers)
119 Automatically selects the first series search result (rather
120 than showing the user a list of more than one series).
121 Is overridden by interactive = False, or specifying a custom_ui
124 shows verbose debugging information
126 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
127 A callable subclass of interactive class (overrides interactive option)
129 language (2 character language abbreviation): (This option is not supported by all target site apis)
130 The language of the returned data. Is also the language search
131 uses. Default is "en" (English). For full list, run..
133 search_all_languages (True/False): (This option is not supported by all target site apis)
134 By default, a Netvision grabber will only search in the language specified using
135 the language option. When this is True, it will search for the
141 if apikey
is not None:
142 self.
config[
'apikey'] = apikey
146 self.
config[
'debug_enabled'] = debug
154 self.
config[
'custom_ui'] = custom_ui
158 self.
config[
'select_first'] = select_first
160 self.
config[
'search_all_languages'] = search_all_languages
162 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", }
165 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}
170 re.compile(
'''^.+?Season\\ (?P<seasno>[0-9]+).*.+?Episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
172 re.compile(
'''^.+?Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
174 re.compile(
'''Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
176 re.compile(
'''^.+?--(?P<seasno>[0-9]+)--.*$''', re.UNICODE),
179 self.
FullScreen =
'http://revision3.com/show/popupPlayer?video_id=%s&quality=high&offset=0'
183 self.
channel_icon =
'%SHAREDIR%/mythnetvision/icons/rev3.png'
193 ''' Read the MNV Revision3 grabber "rev3_config.xml" configuration file
197 url =
'file://%s/nv_python_libs/configs/XML/rev3_config.xml' % (baseProcessingDir, )
198 if not os.path.isfile(url[7:]):
201 if self.
config[
'debug_enabled']:
206 except Exception
as e:
213 '''Read the rev3_config.xml and user preference rev3.xml file.
214 If the rev3.xml file does not exist then create it.
215 If the rev3.xml file is too old then update it.
223 if userPreferenceFile[0] ==
'~':
224 self.
rev3_config.
find(
'userPreferenceFile').text =
"%s%s" % (os.path.expanduser(
"~"), userPreferenceFile[1:])
227 url =
'file://%s' % (self.
rev3_config.
find(
'userPreferenceFile').text, )
228 if self.
config[
'debug_enabled']:
233 except Exception
as e:
236 nextUpdateSecs = int(self.
userPrefs.
find(
'updateDuration').text)*86400
237 nextUpdate = time.localtime(os.path.getmtime(self.
rev3_config.
find(
'userPreferenceFile').text)+nextUpdateSecs)
238 now = time.localtime()
251 ''' Create or update the rev3.xml user preferences file
254 userRev3 = etree.XML(
'''
257 The shows are split into three top level directories which represent how Rev3 categories
258 their videos. Each top level directory has one or more shows. Each show has one or more
259 MP4 formats that can be downloaded. The formats represent various video quality levels.
260 Initially only three shows are enabled. You must change a show's "mp4Format" element's
261 "enabled" attribute to 'true'. When you change the attribute to 'true' that show's RSS feed
262 will be added to the Rev3 tree view. You could activate more than one MP4 format but that
263 would result in duplicates. With the exception of "Tekzilla" which is a show that has
264 both a weekly and daily video RSS feed within the same show element.
265 When the Rev3 Tree view is created it will have links to the video's web page but also a
266 link to the MP4 file that you can download through the MythNetvision interface.
267 If a show moves from one top level directory to another your show sections will be
268 preserved as long as that format is available in the new top level directory.
269 Updates to the "rev3.xml" file is made every X number of days as determined by the value of
270 the "updateDuration" element in this file. The default is every 3 days.
272 <!-- Number of days between updates to the config file -->
273 <updateDuration>3</updateDuration>
277 "globalmax" (optional) Is a way to limit the number of items processed per RSS feed for all
278 treeview URLs. A value of zero (0) means there are no limitions.
279 "max" (optional) Is a way to limit the number of items processed for an individual RSS feed.
280 This value will override any "globalmax" setting. A value of zero (0) means
281 there are no limitions and would be the same if the attribute was no included at all.
282 "enabled" If you want to remove a RSS feed then change the "enabled" attribute to "false".
284 See details: http://revision3.com/blog/2010/03/11/pick-your-favorite-shows-create-a-personalized-feed/
285 Once you sign up and create your personal RSS feed replace the url in the example below with the
286 Revision3 "Your RSS Feed Address" URL and change the "enabled" element attribute to "true".
288 <treeviewURLS globalmax="0">
289 <url enabled="false">http://revision3.com/feed/user/EXAMPLE</url>
297 if self.
config[
'debug_enabled']:
298 print(
"create(%s)" % create)
300 sys.stdout.write(etree.tostring(linksTree, encoding=
'UTF-8', pretty_print=
True))
304 showData = etree.XML(
'<xml></xml>')
305 complexFilter =
"//div[@class='subscribe_rss']//div//p[.='MP4']/..//a"
306 for result
in linksTree.xpath(
'//results'):
307 tmpDirectory = etree.XML(
'<directory></directory>')
308 dirName = result.find(
'name').text
309 tmpDirectory.attrib[
'name'] = dirName
311 if self.
config[
'debug_enabled']:
312 print(
"Results: #Items(%s) for (%s)" % (len(result.xpath(
'.//a')), dirName))
315 for anchor
in result.xpath(
'.//a'):
317 if dirName ==
'Shows':
318 showURL = anchor.attrib.get(
'href')
319 showFilter = complexFilter
320 tmpName = anchor.text
321 elif dirName ==
'Revision3 Beta':
322 tmpName = etree.tostring(anchor, method=
"text", encoding=str)
323 showURL =
'http://revision3beta.com%sfeed/' % anchor.attrib.get(
'href')
325 elif dirName ==
'Archived Shows':
326 showURL =
'http://revision3.com%s' % anchor.attrib.get(
'href')
327 showFilter = complexFilter
328 tmpName = anchor.text
329 if tmpName ==
'Revision3 Beta':
331 if showURL
is not None:
332 url = etree.SubElement(tmpDirectory,
"url")
333 etree.SubElement(url,
"name").text = tmpName
334 etree.SubElement(url,
"href").text = showURL
335 etree.SubElement(url,
"filter").text = showFilter
336 etree.SubElement(url,
"parserType").text =
'html'
337 if tmpDirectory.find(
'url')
is not None:
338 showData.append(tmpDirectory)
340 if self.
config[
'debug_enabled']:
342 sys.stdout.write(etree.tostring(showData, encoding=
'UTF-8', pretty_print=
True))
346 for directory
in showData.findall(
'directory'):
351 tmpDirectory = etree.XML(
'<directory></directory>')
352 tmpDirectory.attrib[
'name'] = directory.attrib[
'name']
353 if directory.attrib[
'name'] ==
'Revision3 Beta':
354 for show
in directory.findall(
'url'):
355 tmpShow = etree.XML(
'<show></show>')
356 tmpShow.attrib[
'name'] = show.find(
'name').text
357 mp4Format = etree.SubElement(tmpShow,
"mp4Format")
359 mp4Format.attrib[
'enabled'] =
'true'
362 mp4Format.attrib[
'enabled'] =
'false'
363 mp4Format.attrib[
'name'] =
'Web Only'
364 mp4Format.attrib[
'rss'] = show.find(
'href').text
365 tmpDirectory.append(tmpShow)
367 showResults = self.
common.getUrlData(directory)
368 for show
in showResults.xpath(
'//results'):
369 tmpShow = etree.XML(
'<show></show>')
370 tmpShow.attrib[
'name'] = show.find(
'name').text
372 if self.
config[
'debug_enabled']:
373 print(
"Results: #Items(%s) for (%s)" % (len(show.xpath(
'.//a')), tmpShow.attrib[
'name']))
376 for format
in show.xpath(
'.//a'):
377 link =
'http://revision3.com%s' % format.attrib[
'href']
380 if link.find(
'/tekzilla/') != -1
and link.find(
'?subshow=false') == -1:
382 mp4Format = etree.SubElement(tmpShow,
"mp4Format")
384 mp4Format.attrib[
'enabled'] =
'true'
387 mp4Format.attrib[
'enabled'] =
'false'
388 mp4Format.attrib[
'name'] = format.text
389 mp4Format.attrib[
'rss'] = link
390 if tmpShow.find(
'mp4Format')
is not None:
391 tmpDirectory.append(tmpShow)
394 if tmpDirectory.find(
'show')
is not None:
395 userRev3.append(tmpDirectory)
397 if self.
config[
'debug_enabled']:
398 print(
"Before any merging userRev3:")
399 sys.stdout.write(etree.tostring(userRev3, encoding=
'UTF-8', pretty_print=
True))
404 userRev3.find(
'updateDuration').text = self.
userPrefs.
find(
'updateDuration').text
405 for mp4Format
in self.
userPrefs.xpath(
"//mp4Format[@enabled='true']"):
406 showName = mp4Format.getparent().attrib[
'name']
407 mp4name = mp4Format.attrib[
'name']
408 elements = userRev3.xpath(
"//show[@name=$showName]/mp4Format[@name=$mp4name]", showName=showName, mp4name=mp4name)
410 elements[0].attrib[
'enabled'] =
'true'
412 if self.
config[
'debug_enabled']:
413 print(
"After any merging userRev3:")
414 sys.stdout.write(etree.tostring(userRev3, encoding=
'UTF-8', pretty_print=
True))
418 prefDir = self.
rev3_config.
find(
'userPreferenceFile').text.replace(
'/rev3.xml',
'')
419 if not os.path.isdir(prefDir):
422 fd.write(
'<userRev3>\n'+
''.join(etree.tostring(element, encoding=
'UTF-8', pretty_print=
True)
for element
in userRev3)+
'</userRev3>')
428 except Exception
as e:
434 ''' Check is there is any season or episode number information in an item's title
435 return array of season and/or episode numbers
436 return array with None values
449 s_e[0], s_e[1] = match.groups()
452 s_e[1] =
'%s' % int(match.groups()[0])
458 ''' Read the Web page to find the video ID number used for fullscreen autoplay
459 return the video ID number
460 return None if the number cannot be found
465 except Exception
as errormsg:
466 sys.stderr.write(
"! Error: The URL (%s) cause the exception error (%s)\n" % (link, errormsg))
469 if self.
config[
'debug_enabled']:
470 print(
"Raw unfiltered URL imput:")
471 sys.stdout.write(etree.tostring(eTree, encoding=
'UTF-8', pretty_print=
True))
480 except AssertionError
as e:
481 sys.stderr.write(
"No filter results for VideoID from url(%s)\n" % link)
482 sys.stderr.write(
"! Error:(%s)\n" % e)
486 if tmpVideoID[0].
get(
'id'):
487 videoID = tmpVideoID[0].attrib[
'id'].strip().replace(
'player-',
'')
499 '''Key word video search of the Rev3 web site
500 return an array of matching item elements
504 searchVar =
'?q=%s' % (urllib.parse.quote(title.encode(
"utf-8")).replace(
' ',
'+'))
505 except UnicodeDecodeError:
506 searchVar =
'?q=%s' % (urllib.parse.quote(title).replace(
' ',
'+'))
507 url = self.
rev3_config.
find(
'searchURLS').xpath(
".//href")[0].text+searchVar
509 if self.
config[
'debug_enabled']:
518 except Exception
as errormsg:
521 if resultTree
is None:
522 raise Rev3VideoNotFound(
"No Rev3 Video matches found for search value (%s)" % title)
524 searchResults = resultTree.xpath(
'//result//li[@class="video"]')
525 if not len(searchResults):
526 raise Rev3VideoNotFound(
"No Rev3 Video matches found for search value (%s)" % title)
529 self.
channel[
'channel_numresults'] = len(searchResults)
533 pubDate = datetime.datetime.now().strftime(self.
common.pubDateFormat)
536 thumbnailFilter = etree.XPath(
'.//a[@class="thumbnail"]/img/@src')
537 titleFilter = etree.XPath(
'.//a[@class="title"]')
538 descFilter = etree.XPath(
'.//div[@class="description"]')
539 itemThumbNail = etree.XPath(
'.//media:thumbnail', namespaces=self.
common.namespaces)
540 itemDwnLink = etree.XPath(
'.//media:content', namespaces=self.
common.namespaces)
542 for result
in searchResults:
543 if len(titleFilter(result)):
544 rev3Item = etree.XML(self.
common.mnvItem)
546 thumbNail = self.
common.ampReplace(thumbnailFilter(result)[0])
547 title = self.
common.massageText(titleFilter(result)[0].text.strip())
548 tmpDesc = etree.tostring(descFilter(result)[0], method=
"text", encoding=str).strip()
549 index = tmpDesc.find(
'–')
551 tmpDesc = tmpDesc[index+1:].strip()
552 description = self.
common.massageText(tmpDesc)
553 link = self.
common.ampReplace(titleFilter(result)[0].attrib[
'href'])
556 rev3Item.find(
'title').text = title
557 rev3Item.find(
'author').text = author
558 rev3Item.find(
'pubDate').text = pubDate
559 rev3Item.find(
'description').text = description
560 rev3Item.find(
'link').text = link
561 itemThumbNail(rev3Item)[0].attrib[
'url'] = thumbNail
562 itemDwnLink(rev3Item)[0].attrib[
'url'] = link
565 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
567 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
568 itemDict[title.lower()] = rev3Item
570 if not len(list(itemDict.keys())):
571 raise Rev3VideoNotFound(
"No Rev3 Video matches found for search value (%s)" % title)
573 return [itemDict, resultTree.xpath(
'//pageInfo')[0].text]
578 """Common name for a video search. Used to interface with MythTV plugin NetVision
583 if self.
config[
'debug_enabled']:
584 print(
"self.rev3_config:")
585 sys.stdout.write(etree.tostring(self.
rev3_config, encoding=
'UTF-8', pretty_print=
True))
594 data = self.
searchTitle(title, pagenumber, self.page_limit)
595 except Rev3VideoNotFound
as msg:
596 sys.stderr.write(
"%s\n" % msg)
598 except Rev3UrlError
as msg:
599 sys.stderr.write(
'%s\n' % msg)
601 except Rev3HttpError
as msg:
604 except Rev3RssError
as msg:
607 except Exception
as e:
608 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
612 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
615 itemCount = len(list(data[0].keys()))
616 if data[1] ==
'true':
617 self.
channel[
'channel_returned'] = itemCount
618 self.
channel[
'channel_startindex'] = itemCount
619 self.
channel[
'channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
621 self.
channel[
'channel_returned'] = itemCount+(self.page_limit*(int(pagenumber)-1))
622 self.
channel[
'channel_startindex'] = self.
channel[
'channel_returned']
623 self.
channel[
'channel_numresults'] = self.
channel[
'channel_returned']
627 rssTree.append(channelTree)
630 for key
in sorted(data[0].keys()):
632 channelTree.append(data[0][key])
636 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
637 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
642 '''Gather the Revision3 feeds then get a max page of videos meta data in each of them
643 Display the results and exit
645 personalFeed =
"Personal Feed"
650 except Exception
as e:
651 sys.stderr.write(
'%s' % e)
654 if self.
config[
'debug_enabled']:
655 print(
"self.userPrefs:")
656 sys.stdout.write(etree.tostring(self.
userPrefs, encoding=
'UTF-8', pretty_print=
True))
660 rssFeeds = self.
userPrefs.xpath(
"//mp4Format[@enabled='true']")
661 personalFeeds = self.
userPrefs.xpath(
"//treeviewURLS//url[@enabled='true']")
662 if not len(rssFeeds)
and not len(personalFeeds):
663 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)
667 showData = etree.XML(
'<xml></xml>')
668 for feed
in personalFeeds:
669 rssFeeds.append(feed)
671 for rssFeed
in rssFeeds:
672 if rssFeed.getparent().tag ==
'treeviewURLS':
673 uniqueName =
'%s:%s' % (personalFeed, count)
676 uniqueName =
'%s:%s:%s' % (rssFeed.getparent().getparent().attrib[
'name'], rssFeed.getparent().attrib[
'name'], rssFeed.attrib[
'name'])
677 url = etree.XML(
'<url></url>')
678 etree.SubElement(url,
"name").text = uniqueName
679 if uniqueName.startswith(personalFeed):
680 etree.SubElement(url,
"href").text = rssFeed.text
682 etree.SubElement(url,
"href").text = rssFeed.attrib[
'rss']
683 etree.SubElement(url,
"filter").text =
"//channel"
684 etree.SubElement(url,
"parserType").text =
'xml'
687 if self.
config[
'debug_enabled']:
689 sys.stdout.write(etree.tostring(showData, encoding=
'UTF-8', pretty_print=
True))
694 resultTree = self.
common.getUrlData(showData)
695 except Exception
as errormsg:
698 if resultTree
is None:
701 if self.
config[
'debug_enabled']:
703 sys.stdout.write(etree.tostring(resultTree, encoding=
'UTF-8', pretty_print=
True))
707 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
711 rssTree.append(channelTree)
714 itemFilter = etree.XPath(
'.//item')
715 channelFilter = etree.XPath(
'./result/channel')
716 imageFilter = etree.XPath(
'.//image/url')
717 itemDwnLink =
'.//media:content'
718 itemThumbNail =
'.//media:thumbnail'
719 itemDuration =
'.//media:content'
720 itemLanguage =
'.//media:content'
723 categoryElement = etree.XML(
'<directory></directory>')
724 itemAuthor =
'Revision3'
725 for result
in resultTree.findall(
'results'):
726 names = result.find(
'name').text.split(
':')
727 for index
in range(len(names)):
728 names[index] = self.
common.massageText(names[index])
729 channel = channelFilter(result)[0]
730 if channel.find(
'image')
is not None:
731 channelThumbnail = self.
common.ampReplace(imageFilter(channel)[0].text)
733 channelThumbnail = self.
common.ampReplace(channel.find(
'link').text.replace(
'/watch/',
'/images/')+
'100.jpg')
734 channelLanguage =
'en'
735 if channel.find(
'language')
is not None:
736 channelLanguage = channel.find(
'language').text[:2]
738 if names[0] != categoryDir:
739 if categoryDir
is not None:
740 channelTree.append(categoryElement)
741 categoryElement = etree.XML(
'<directory></directory>')
742 if names[0] == personalFeed:
743 categoryElement.attrib[
'name'] = channel.find(
'title').text
745 categoryElement.attrib[
'name'] = names[0]
747 categoryDir = names[0]
748 if names[1] != showDir:
749 if names[0] == personalFeed:
750 showElement = categoryElement
752 showElement = etree.SubElement(categoryElement,
"directory")
753 if names[2] ==
'Web Only':
754 showElement.attrib[
'name'] =
'%s' % (names[1])
756 showElement.attrib[
'name'] =
'%s: %s' % (names[1], names[2])
757 showElement.attrib[
'thumbnail'] = channelThumbnail
760 if self.
config[
'debug_enabled']:
761 print(
"Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names))
765 for itemData
in itemFilter(result):
766 rev3Item = etree.XML(self.
common.mnvItem)
768 rev3Item.find(
'title').text = self.
common.massageText(itemData.find(
'title').text.strip())
769 rev3Item.find(
'author').text = itemAuthor
770 rev3Item.find(
'pubDate').text = self.
common.massageText(itemData.find(
'pubDate').text)
771 rev3Item.find(
'description').text = self.
common.massageText(itemData.find(
'description').text.strip())
772 link = self.
common.ampReplace(itemData.find(
'link').text)
773 downLoadLink = self.
common.ampReplace(itemData.xpath(itemDwnLink, namespaces=self.
common.namespaces)[0].attrib[
'url'])
777 if names[0] ==
'Shows' or names[0] == personalFeed:
778 fullScreenVideoID = self.
getVideoID(itemData.find(
'link').text)
779 if fullScreenVideoID:
780 if link == downLoadLink:
784 rev3Item.find(
'link').text = self.
common.ampReplace(link)
785 rev3Item.xpath(itemDwnLink, namespaces=self.
common.namespaces)[0].attrib[
'url'] = downLoadLink
787 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'))
791 rev3Item.xpath(itemDuration, namespaces=self.
common.namespaces)[0].attrib[
'duration'] = itemData.xpath(itemDuration, namespaces=self.
common.namespaces)[0].attrib[
'duration']
794 rev3Item.xpath(itemLanguage, namespaces=self.
common.namespaces)[0].attrib[
'lang'] = channelLanguage
795 if rev3Item.xpath(itemThumbNail, namespaces=self.
common.namespaces)[0].
get(
'url'):
796 s_e = self.
getSeasonEpisode(rev3Item.find(
'title').text, rev3Item.xpath(itemThumbNail, namespaces=self.
common.namespaces)[0].attrib[
'url'])
798 s_e = self.
getSeasonEpisode(rev3Item.find(
'title').text, rev3Item.xpath(itemDwnLink, namespaces=self.
common.namespaces)[0].attrib[
'url'])
800 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
802 etree.SubElement(rev3Item,
"{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
803 if s_e[0]
and s_e[1]:
804 rev3Item.find(
'title').text =
'S%02dE%02d: %s' % (int(s_e[0]), int (s_e[1]), rev3Item.find(
'title').text)
806 rev3Item.find(
'title').text =
'S%02d: %s' % (int(s_e[0]), rev3Item.find(
'title').text)
808 rev3Item.find(
'title').text =
'Ep%03d: %s' % (int(s_e[1]), rev3Item.find(
'title').text)
809 showElement.append(rev3Item)
812 if categoryElement.xpath(
'.//item')
is not None:
813 channelTree.append(categoryElement)
816 if len(rssTree.xpath(
'//item')):
818 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
819 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))