MythTV  master
bbciplayer_api.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 # ----------------------
4 # Name: bbciplayer_api - Simple-to-use Python interface to the BBC iPlayer RSS feeds
5 # (http://www.bbc.co.uk)
6 # Python Script
7 # Author: R.D. Vaughan
8 # Purpose: This python script is intended to perform a variety of utility functions to
9 # search and access text metadata, video and image URLs from BBC iPlayer Web site.
10 #
11 # License:Creative Commons GNU GPL v2
12 # (http://creativecommons.org/licenses/GPL/2.0/)
13 #-------------------------------------
14 __title__ ="bbciplayer_api - Simple-to-use Python interface to the BBC iPlayer RSS feeds (http://www.bbc.co.uk)"
15 __author__="R.D. Vaughan"
16 __purpose__='''
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 the BBC iPlayer Web site. These routines process RSS feeds
19 provided by BBC (http://www.bbc.co.uk). The specific BBC iPlayer RSS feeds that are processed are controled through a user XML preference file usually found at
20 "~/.mythtv/MythNetvision/userGrabberPrefs/bbciplayer.xml"
21 '''
22 
23 __version__="v0.1.3"
24 # 0.1.0 Initial development
25 # 0.1.1 Changed the logger to only output to stderr rather than a file
26 # 0.1.2 Fixed incorrect URL creation for RSS feed Web pages
27 # Restricted custom HTML web pages to Video media only. Audio will only play from its Web page.
28 # Add the "customhtml" tag to search and treeviews
29 # Removed the need for python MythTV bindings and added "%SHAREDIR%" to icon directory path
30 # 0.1.3 Fixed search due to BBC Web site changes
31 
32 import os, struct, sys, re, time, datetime, shutil, urllib, re
33 import logging
34 from socket import gethostname, gethostbyname
35 from threading import Thread
36 from copy import deepcopy
37 from operator import itemgetter, attrgetter
38 from MythTV import MythXML
39 from bbciplayer_exceptions import (BBCUrlError, BBCHttpError, BBCRssError, BBCVideoNotFound, BBCConfigFileError, BBCUrlDownloadError)
40 
41 class OutStreamEncoder(object):
42  """Wraps a stream with an encoder"""
43  def __init__(self, outstream, encoding=None):
44  self.out = outstream
45  if not encoding:
46  self.encoding = sys.getfilesystemencoding()
47  else:
48  self.encoding = encoding
49 
50  def write(self, obj):
51  """Wraps the output stream, encoding Unicode strings with the specified encoding"""
52  if isinstance(obj, unicode):
53  try:
54  self.out.write(obj.encode(self.encoding))
55  except IOError:
56  pass
57  else:
58  try:
59  self.out.write(obj)
60  except IOError:
61  pass
62 
63  def __getattr__(self, attr):
64  """Delegate everything but write to the stream"""
65  return getattr(self.out, attr)
66 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
67 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
68 
69 
70 try:
71  from StringIO import StringIO
72  from lxml import etree
73 except Exception, e:
74  sys.stderr.write(u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
75  sys.exit(1)
76 
77 # Check that the lxml library is current enough
78 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html)
79 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later"
80 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package
81 version = ''
82 for digit in etree.LIBXML_VERSION:
83  version+=str(digit)+'.'
84 version = version[:-1]
85 if version < '2.7.2':
86  sys.stderr.write(u'''
87 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
88  At least "libxml" version 2.7.2 must be installed. Your version is (%s).
89 ''' % version)
90  sys.exit(1)
91 
92 
93 class Videos(object):
94  """Main interface to http://www.bbciplayer.com/
95  This is done to support a common naming framework for all python Netvision plugins no matter their site
96  target.
97 
98  Supports search methods
99  The apikey is a not required to access http://www.bbciplayer.com/
100  """
101  def __init__(self,
102  apikey,
103  mythtv = True,
104  interactive = False,
105  select_first = False,
106  debug = False,
107  custom_ui = None,
108  language = None,
109  search_all_languages = False,
110  ):
111  """apikey (str/unicode):
112  Specify the target site API key. Applications need their own key in some cases
113 
114  mythtv (True/False):
115  When True, the returned meta data is being returned has the key and values massaged to match MythTV
116  When False, the returned meta data is being returned matches what target site returned
117 
118  interactive (True/False): (This option is not supported by all target site apis)
119  When True, uses built-in console UI is used to select the correct show.
120  When False, the first search result is used.
121 
122  select_first (True/False): (This option is not supported currently implemented in any grabbers)
123  Automatically selects the first series search result (rather
124  than showing the user a list of more than one series).
125  Is overridden by interactive = False, or specifying a custom_ui
126 
127  debug (True/False):
128  shows verbose debugging information
129 
130  custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
131  A callable subclass of interactive class (overrides interactive option)
132 
133  language (2 character language abbreviation): (This option is not supported by all target site apis)
134  The language of the returned data. Is also the language search
135  uses. Default is "en" (English). For full list, run..
136 
137  search_all_languages (True/False): (This option is not supported by all target site apis)
138  By default, a Netvision grabber will only search in the language specified using
139  the language option. When this is True, it will search for the
140  show in any language
141 
142  """
143  self.config = {}
144  self.mythxml = MythXML()
145 
146  if apikey is not None:
147  self.config['apikey'] = apikey
148  else:
149  pass # BBC does not require an apikey
150 
151  self.config['debug_enabled'] = debug # show debugging messages
152  self.common = common
153  self.common.debug = debug # Set the common function debug level
154 
155  self.log_name = u'BBCiPlayer_Grabber'
156  self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
157  self.logger = self.common.logger # Setups the logger (self.log.debug() etc)
159  self.config['custom_ui'] = custom_ui
160 
161  self.config['interactive'] = interactive
162 
163  self.config['select_first'] = select_first
165  self.config['search_all_languages'] = search_all_languages
166 
167  self.error_messages = {'BBCUrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'BBCHttpError': u"! Error: An HTTP communications error with the BBC was raised (%s)\n", 'BBCRssError': u"! Error: Invalid RSS meta data\nwas received from the BBC error (%s). Skipping item.\n", 'BBCVideoNotFound': u"! Error: Video search with the BBC did not return any results (%s)\n", 'BBCConfigFileError': u"! Error: bbc_config.xml file missing\nit should be located in and named as (%s).\n", 'BBCUrlDownloadError': u"! Error: Downloading a RSS feed or Web page (%s).\n", }
168 
169  # Channel details and search results
170  self.channel = {'channel_title': u'BBC iPlayer', 'channel_link': u'http://www.bbc.co.uk', 'channel_description': u"BBC iPlayer is our service that lets you catch up with radio and television programmes from the past week.", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0}
172  # XPath parsers used to detect a video type of item
173  self.countryCodeParsers = [
174  etree.XPath('.//a[@class="episode-title title-link cta-video"]', namespaces=self.common.namespaces),
175  etree.XPath('.//div[@class="feature video"]', namespaces=self.common.namespaces),
176  etree.XPath('.//atm:category[@term="TV"]', namespaces=self.common.namespaces),
177  ]
178 
179  # Season and Episode detection regex patterns
180  self.s_e_Patterns = [
181  # "Series 7 - Episode 4" or "Series 7 - Episode 4" or "Series 7: On Holiday: Episode 10"
182  re.compile(u'''^.+?Series\\ (?P<seasno>[0-9]+).*.+?Episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
183  # Series 5 - 1
184  re.compile(u'''^.+?Series\\ (?P<seasno>[0-9]+)\\ \\-\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
185  # Series 1 - Warriors of Kudlak - Part 2
186  re.compile(u'''^.+?Series\\ (?P<seasno>[0-9]+).*.+?Part\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
187  # Series 3: Programme 3
188  re.compile(u'''^.+?Series\\ (?P<seasno>[0-9]+)\\:\\ Programme\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
189  # Series 3:
190  re.compile(u'''^.+?Series\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
191  # Episode 1
192  re.compile(u'''^.+?Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
193  ]
194 
195  self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/bbciplayer.jpg'
196 
197  self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
198  # end __init__()
199 
200 
205 
206  def getBBCConfig(self):
207  ''' Read the MNV BBC iPlayer grabber "bbc_config.xml" configuration file
208  return nothing
209  '''
210  # Read the grabber bbciplayer_config.xml configuration file
211  url = u'file://%s/nv_python_libs/configs/XML/bbc_config.xml' % (baseProcessingDir, )
212  if not os.path.isfile(url[7:]):
213  raise BBCConfigFileError(self.error_messages['BBCConfigFileError'] % (url[7:], ))
214 
215  if self.config['debug_enabled']:
216  print url
217  print
218  try:
219  self.bbciplayer_config = etree.parse(url)
220  except Exception, e:
221  raise BBCUrlError(self.error_messages['BBCUrlError'] % (url, errormsg))
222  return
223  # end getBBCConfig()
224 
225 
227  '''Read the bbciplayer_config.xml and user preference bbciplayer.xml file.
228  If the bbciplayer.xml file does not exist then copy the default.
229  return nothing
230  '''
231  # Get bbciplayer_config.xml
232  self.getBBCConfig()
233 
234  # Check if the bbciplayer.xml file exists
235  userPreferenceFile = self.bbciplayer_config.find('userPreferenceFile').text
236  if userPreferenceFile[0] == '~':
237  self.bbciplayer_config.find('userPreferenceFile').text = u"%s%s" % (os.path.expanduser(u"~"), userPreferenceFile[1:])
238 
239  # If the user config file does not exists then copy one from the default
240  if not os.path.isfile(self.bbciplayer_config.find('userPreferenceFile').text):
241  # Make the necessary directories if they do not already exist
242  prefDir = self.bbciplayer_config.find('userPreferenceFile').text.replace(u'/bbciplayer.xml', u'')
243  if not os.path.isdir(prefDir):
244  os.makedirs(prefDir)
245  defaultConfig = u'%s/nv_python_libs/configs/XML/defaultUserPrefs/bbciplayer.xml' % (baseProcessingDir, )
246  shutil.copy2(defaultConfig, self.bbciplayer_config.find('userPreferenceFile').text)
247 
248  # Read the grabber bbciplayer_config.xml configuration file
249  url = u'file://%s' % (self.bbciplayer_config.find('userPreferenceFile').text, )
250  if self.config['debug_enabled']:
251  print url
252  print
253  try:
254  self.userPrefs = etree.parse(url)
255  except Exception, e:
256  raise BBCUrlError(self.error_messages['BBCUrlError'] % (url, errormsg))
257  return
258  # end getUserPreferences()
259 
260  def setCountry(self, item):
261  '''Parse the item information (HTML or RSS/XML) to identify if the content is a video or
262  audio file. Set the contry code if a video is detected as it can only be played in the "UK"
263  return "uk" if a video type was detected.
264  return None if a video type was NOT detected.
265  '''
266  countryCode = None
267  for xpathP in self.countryCodeParsers:
268  if len(xpathP(item)):
269  countryCode = u'uk'
270  break
271  return countryCode
272  # end setCountry()
273 
274 
275  def getSeasonEpisode(self, title):
276  ''' Check is there is any season or episode number information in an item's title
277  return array of season and/or episode numbers
278  return array with None values
279  '''
280  s_e = [None, None]
281  for index in range(len(self.s_e_Patterns)):
282  match = self.s_e_Patterns[index].match(title)
283  if not match:
284  continue
285  if index < 4:
286  s_e[0], s_e[1] = match.groups()
287  break
288  elif index == 4:
289  s_e[0] = match.groups()[0]
290  break
291  elif index == 5:
292  s_e[1] = match.groups()[0]
293  break
294  return s_e
295  # end getSeasonEpisode()
296 
297 
302 
303  def processVideoUrl(self, url):
304  playerUrl = self.mythxml.getInternetContentUrl("nv_python_libs/configs/HTML/bbciplayer.html", \
305  url)
306  return playerUrl
307 
308  def searchTitle(self, title, pagenumber, pagelen):
309  '''Key word video search of the BBC iPlayer web site
310  return an array of matching item elements
311  return
312  '''
313  # Save the origninal URL
314  orgUrl = self.bbciplayer_config.find('searchURLS').xpath(".//href")[0].text
315 
316  try:
317  searchVar = u'/?q=%s&page=%s' % (urllib.quote(title.encode("utf-8")), pagenumber)
318  except UnicodeDecodeError:
319  searchVar = u'/?q=%s&page=%s' % (urllib.quote(title), pagenumber)
320 
321  url = self.bbciplayer_config.find('searchURLS').xpath(".//href")[0].text+searchVar
322 
323  if self.config['debug_enabled']:
324  print url
325  print
326 
327  self.bbciplayer_config.find('searchURLS').xpath(".//href")[0].text = url
328 
329  # Perform a search
330  try:
331  resultTree = self.common.getUrlData(self.bbciplayer_config.find('searchURLS'), pageFilter=self.bbciplayer_config.find('searchURLS').xpath(".//pageFilter")[0].text)
332  except Exception, errormsg:
333  # Restore the origninal URL
334  self.bbciplayer_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
335  raise BBCUrlDownloadError(self.error_messages['BBCUrlDownloadError'] % (errormsg))
336 
337  # Restore the origninal URL
338  self.bbciplayer_config.find('searchURLS').xpath(".//href")[0].text = orgUrl
339 
340  if resultTree is None:
341  raise BBCVideoNotFound(u"No BBC Video matches found for search value (%s)" % title)
342 
343  searchResults = resultTree.xpath('//result//li')
344  if not len(searchResults):
345  raise BBCVideoNotFound(u"No BBC Video matches found for search value (%s)" % title)
346 
347  # BBC iPlayer search results fo not have a pubDate so use the current data time
348  # e.g. "Sun, 06 Jan 2008 21:44:36 GMT"
349  pubDate = datetime.datetime.now().strftime(self.common.pubDateFormat)
350 
351  # Set the display type for the link (Fullscreen, Web page, Game Console)
352  if self.userPrefs.find('displayURL') != None:
353  urlType = self.userPrefs.find('displayURL').text
354  else:
355  urlType = u'fullscreen'
356 
357  # Translate the search results into MNV RSS item format
358  audioFilter = etree.XPath('contains(./@class,"audio") or contains(./../../@class,"audio")')
359  linkFilter = etree.XPath(u".//div[@class='episode-info ']//a")
360  titleFilter = etree.XPath(u".//div[@class='episode-info ']//a")
361  descFilter = etree.XPath(u".//div[@class='episode-info ']//p[@class='episode-synopsis']")
362  thumbnailFilter = etree.XPath(u".//span[@class='episode-image cta-play']//img")
363  itemDict = {}
364  for result in searchResults:
365  tmpLink = linkFilter(result)
366  if not len(tmpLink): # Make sure that this result actually has a video
367  continue
368  bbciplayerItem = etree.XML(self.common.mnvItem)
369  # Is this an Audio or Video item (true/false)
370  audioTF = audioFilter(result)
371  # Extract and massage data
372  link = tmpLink[0].attrib['href']
373  if urlType == 'bigscreen':
374  link = u'http://www.bbc.co.uk/iplayer/bigscreen%s' % link.replace(u'/iplayer',u'')
375  elif urlType == 'bbcweb':
376  link = u'http://www.bbc.co.uk'+ link
377  else:
378  if not audioTF:
379  link = link.replace(u'/iplayer/episode/', u'')
380  index = link.find(u'/')
381  link = link[:index]
382  link = self.processVideoUrl(link);
383  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text = 'true'
384  else: # Audio media will not play in the embedded HTML page
385  link = u'http://www.bbc.co.uk'+ link
386  link = self.common.ampReplace(link)
387 
388  title = self.common.massageText(titleFilter(result)[0].attrib['title'].strip())
389  description = self.common.massageText(etree.tostring(descFilter(result)[0], method="text", encoding=unicode).strip())
390 
391  # Insert data into a new item element
392  bbciplayerItem.find('title').text = title
393  bbciplayerItem.find('author').text = u'BBC'
394  bbciplayerItem.find('pubDate').text = pubDate
395  bbciplayerItem.find('description').text = description
396  bbciplayerItem.find('link').text = link
397  bbciplayerItem.xpath('.//media:thumbnail', namespaces=self.common.namespaces)[0].attrib['url'] = self.common.ampReplace(thumbnailFilter(result)[0].attrib['src'])
398  bbciplayerItem.xpath('.//media:content', namespaces=self.common.namespaces)[0].attrib['url'] = link
399  # Videos are only viewable in the UK so add a country indicator if this is a video
400  if audioTF:
401  countCode = None
402  else:
403  countCode = u'uk'
404  if countCode:
405  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = countCode
406  s_e = self.getSeasonEpisode(title)
407  if s_e[0]:
408  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
409  if s_e[1]:
410  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
411  itemDict[title.lower()] = bbciplayerItem
412 
413  if not len(itemDict.keys()):
414  raise BBCVideoNotFound(u"No BBC Video matches found for search value (%s)" % title)
415 
416  # Set the number of search results returned
417  self.channel['channel_numresults'] = len(itemDict)
418 
419  return [itemDict, resultTree.xpath('//pageInfo')[0].text]
420  # end searchTitle()
421 
422 
423  def searchForVideos(self, title, pagenumber):
424  """Common name for a video search. Used to interface with MythTV plugin NetVision
425  """
426  # Get the user preferences
427  try:
428  self.getUserPreferences()
429  except Exception, e:
430  sys.stderr.write(u'%s' % e)
431  sys.exit(1)
432 
433  if self.config['debug_enabled']:
434  print "self.userPrefs:"
435  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
436  print
437 
438 
439  # Easier for debugging
440 # print self.searchTitle(title, pagenumber, self.page_limit)
441 # print
442 # sys.exit()
443 
444  try:
445  data = self.searchTitle(title, pagenumber, self.page_limit)
446  except BBCVideoNotFound, msg:
447  sys.stderr.write(u"%s\n" % msg)
448  sys.exit(0)
449  except BBCUrlError, msg:
450  sys.stderr.write(u'%s\n' % msg)
451  sys.exit(1)
452  except BBCHttpError, msg:
453  sys.stderr.write(self.error_messages['BBCHttpError'] % msg)
454  sys.exit(1)
455  except BBCRssError, msg:
456  sys.stderr.write(self.error_messages['BBCRssError'] % msg)
457  sys.exit(1)
458  except Exception, e:
459  sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
460  sys.exit(1)
461 
462  # Create RSS element tree
463  rssTree = etree.XML(self.common.mnvRSS+u'</rss>')
464 
465  # Set the paging values
466  itemCount = len(data[0].keys())
467  if data[1] == 'true':
468  self.channel['channel_returned'] = itemCount
469  self.channel['channel_startindex'] = itemCount
470  self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
471  else:
472  self.channel['channel_returned'] = itemCount+(self.page_limit*(int(pagenumber)-1))
473  self.channel['channel_startindex'] = self.channel['channel_returned']
474  self.channel['channel_numresults'] = self.channel['channel_returned']
475 
476  # Add the Channel element tree
477  channelTree = self.common.mnvChannelElement(self.channel)
478  rssTree.append(channelTree)
479 
480  lastKey = None
481  for key in sorted(data[0].keys()):
482  if lastKey != key:
483  channelTree.append(data[0][key])
484  lastKey = key
485 
486  # Output the MNV search results
487  sys.stdout.write(u'<?xml version="1.0" encoding="UTF-8"?>\n')
488  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
489  sys.exit(0)
490  # end searchForVideos()
491 
492  def displayTreeView(self):
493  '''Gather the BBC iPlayer feeds then get a max page of videos meta data in each of them
494  Display the results and exit
495  '''
496  # Get the user preferences
497  try:
498  self.getUserPreferences()
499  except Exception, e:
500  sys.stderr.write(u'%s' % e)
501  sys.exit(1)
502 
503  if self.config['debug_enabled']:
504  print "self.userPrefs:"
505  sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
506  print
507 
508  # Massage channel icon
509  self.channel_icon = self.common.ampReplace(self.channel_icon)
510 
511  # Create RSS element tree
512  rssTree = etree.XML(self.common.mnvRSS+u'</rss>')
513 
514  # Add the Channel element tree
515  channelTree = self.common.mnvChannelElement(self.channel)
516  rssTree.append(channelTree)
517 
518  # Process any user specified searches
519  searchResultTree = []
520  searchFilter = etree.XPath(u"//item")
521  userSearchStrings = u'userSearchStrings'
522  if self.userPrefs.find(userSearchStrings) != None:
523  userSearch = self.userPrefs.find(userSearchStrings).xpath('./userSearch')
524  if len(userSearch):
525  for searchDetails in userSearch:
526  try:
527  data = self.searchTitle(searchDetails.find('searchTerm').text, 1, self.page_limit)
528  except BBCVideoNotFound, msg:
529  sys.stderr.write(u"%s\n" % msg)
530  continue
531  except BBCUrlError, msg:
532  sys.stderr.write(u'%s\n' % msg)
533  continue
534  except BBCHttpError, msg:
535  sys.stderr.write(self.error_messages['BBCHttpError'] % msg)
536  continue
537  except BBCRssError, msg:
538  sys.stderr.write(self.error_messages['BBCRssError'] % msg)
539  continue
540  except Exception, e:
541  sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
542  continue
543  dirElement = etree.XML(u'<directory></directory>')
544  dirElement.attrib['name'] = self.common.massageText(searchDetails.find('dirName').text)
545  dirElement.attrib['thumbnail'] = self.channel_icon
546  lastKey = None
547  for key in sorted(data[0].keys()):
548  if lastKey != key:
549  dirElement.append(data[0][key])
550  lastKey = key
551  channelTree.append(dirElement)
552  continue
553 
554  # Create a structure of feeds that can be concurrently downloaded
555  rssData = etree.XML(u'<xml></xml>')
556  for feedType in [u'treeviewURLS', u'userFeeds']:
557  if self.userPrefs.find(feedType) == None:
558  continue
559  if not len(self.userPrefs.find(feedType).xpath('./url')):
560  continue
561  for rssFeed in self.userPrefs.find(feedType).xpath('./url'):
562  urlEnabled = rssFeed.attrib.get('enabled')
563  if urlEnabled == 'false':
564  continue
565  urlName = rssFeed.attrib.get('name')
566  if urlName:
567  uniqueName = u'%s;%s' % (urlName, rssFeed.text)
568  else:
569  uniqueName = u'RSS;%s' % (rssFeed.text)
570  url = etree.XML(u'<url></url>')
571  etree.SubElement(url, "name").text = uniqueName
572  etree.SubElement(url, "href").text = rssFeed.text
573  etree.SubElement(url, "filter").text = u"atm:title"
574  etree.SubElement(url, "filter").text = u"//atm:entry"
575  etree.SubElement(url, "parserType").text = u'xml'
576  rssData.append(url)
577 
578  if self.config['debug_enabled']:
579  print "rssData:"
580  sys.stdout.write(etree.tostring(rssData, encoding='UTF-8', pretty_print=True))
581  print
582 
583  # Get the RSS Feed data
584  if rssData.find('url') != None:
585  try:
586  resultTree = self.common.getUrlData(rssData)
587  except Exception, errormsg:
588  raise BBCUrlDownloadError(self.error_messages['BBCUrlDownloadError'] % (errormsg))
589  if self.config['debug_enabled']:
590  print "resultTree:"
591  sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
592  print
593 
594  # Set the display type for the link (Fullscreen, Web page, Game Console)
595  if self.userPrefs.find('displayURL') != None:
596  urlType = self.userPrefs.find('displayURL').text
597  else:
598  urlType = u'fullscreen'
599 
600  # Process each directory of the user preferences that have an enabled rss feed
601  feedFilter = etree.XPath('//url[text()=$url]')
602  itemFilter = etree.XPath('.//atm:entry', namespaces=self.common.namespaces)
603  titleFilter = etree.XPath('.//atm:title', namespaces=self.common.namespaces)
604  mediaFilter = etree.XPath('.//atm:category[@term="TV"]', namespaces=self.common.namespaces)
605  linkFilter = etree.XPath('.//atm:link', namespaces=self.common.namespaces)
606  descFilter1 = etree.XPath('.//atm:content', namespaces=self.common.namespaces)
607  descFilter2 = etree.XPath('.//p')
608  itemThumbNail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
609  creationDate = etree.XPath('.//atm:updated', namespaces=self.common.namespaces)
610  itemDwnLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
611  itemLanguage = etree.XPath('.//media:content', namespaces=self.common.namespaces)
612  rssName = etree.XPath('atm:title', namespaces=self.common.namespaces)
613  categoryDir = None
614  categoryElement = None
615  itemAuthor = u'BBC'
616  for result in resultTree.findall('results'):
617  names = result.find('name').text.split(u';')
618  names[0] = self.common.massageText(names[0])
619  if names[0] == 'RSS':
620  names[0] = self.common.massageText(rssName(result.find('result'))[0].text.replace(u'BBC iPlayer - ', u''))
621  count = 0
622  urlMax = None
623  url = feedFilter(self.userPrefs, url=names[1])
624  if len(url):
625  if url[0].attrib.get('max'):
626  try:
627  urlMax = int(url[0].attrib.get('max'))
628  except:
629  pass
630  elif url[0].getparent().attrib.get('globalmax'):
631  try:
632  urlMax = int(url[0].getparent().attrib.get('globalmax'))
633  except:
634  pass
635  if urlMax == 0:
636  urlMax = None
637  channelThumbnail = self.channel_icon
638  channelLanguage = u'en'
639  # Create a new directory and/or subdirectory if required
640  if names[0] != categoryDir:
641  if categoryDir != None:
642  channelTree.append(categoryElement)
643  categoryElement = etree.XML(u'<directory></directory>')
644  categoryElement.attrib['name'] = names[0]
645  categoryElement.attrib['thumbnail'] = self.channel_icon
646  categoryDir = names[0]
647 
648  if self.config['debug_enabled']:
649  print "Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names)
650  print
651 
652  # Create a list of item elements in pubDate order to that the items are processed in
653  # date and time order
654  itemDict = [(pd.text, pd.getparent()) for pd in creationDate(result)]
655  itemList = sorted(itemDict, key=itemgetter(0), reverse=True)
656  # Convert each RSS item into a MNV item
657  for tupleDate in itemList:
658  itemData = tupleDate[1]
659  bbciplayerItem = etree.XML(self.common.mnvItem)
660  tmpLink = linkFilter(itemData)
661  if len(tmpLink): # Make sure that this result actually has a video
662  link = tmpLink[0].attrib['href']
663  if urlType == 'bigscreen':
664  link = link.replace(u'/iplayer/', u'/iplayer/bigscreen/')
665  elif urlType == 'bbcweb':
666  pass # Link does not need adjustments
667  else:
668  if len(mediaFilter(itemData)):
669  link = link.replace(u'http://www.bbc.co.uk/iplayer/episode/', u'')
670  index = link.find(u'/')
671  link = link[:index]
672  link = self.processVideoUrl(link);
673  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml").text = 'true'
674  else:
675  pass # Radio media cannot be played within the embedded weg page
676  else:
677  continue
678  # Convert the pubDate "2010-03-22T07:56:21Z" to a MNV pubdate format
679  pubdate = creationDate(itemData)
680  if len(pubdate):
681  pubdate = pubdate[0].text
682  pubdate = time.strptime(pubdate, '%Y-%m-%dT%H:%M:%SZ')
683  pubdate = time.strftime(self.common.pubDateFormat, pubdate)
684  else:
685  pubdate = datetime.datetime.now().strftime(self.common.pubDateFormat)
686 
687  # Extract and massage data also insert data into a new item element
688  bbciplayerItem.find('title').text = self.common.massageText(titleFilter(itemData)[0].text.strip())
689  bbciplayerItem.find('author').text = itemAuthor
690  bbciplayerItem.find('pubDate').text = pubdate
691  description = etree.HTML(etree.tostring(descFilter1(itemData)[0], method="text", encoding=unicode).strip())
692  description = etree.tostring(descFilter2(description)[1], method="text", encoding=unicode).strip()
693  bbciplayerItem.find('description').text = self.common.massageText(description)
694  bbciplayerItem.find('link').text = link
695  itemDwnLink(bbciplayerItem)[0].attrib['url'] = link
696  try:
697  itemThumbNail(bbciplayerItem)[0].attrib['url'] = self.common.ampReplace(itemThumbNail(itemData)[0].attrib['url'])
698  except IndexError:
699  pass
700  itemLanguage(bbciplayerItem)[0].attrib['lang'] = channelLanguage
701  # Videos are only viewable in the UK so add a country indicator if this is a video
702  countCode = self.setCountry(itemData)
703  if countCode:
704  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = countCode
705  s_e = self.getSeasonEpisode(bbciplayerItem.find('title').text)
706  if s_e[0]:
707  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
708  if s_e[1]:
709  etree.SubElement(bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
710  categoryElement.append(bbciplayerItem)
711  if urlMax: # Check of the maximum items to processes has been met
712  count+=1
713  if count > urlMax:
714  break
715 
716  # Add the last directory processed
717  if categoryElement != None:
718  if categoryElement.xpath('.//item') != None:
719  channelTree.append(categoryElement)
720 
721  # Check that there was at least some items
722  if len(rssTree.xpath('//item')):
723  # Output the MNV search results
724  sys.stdout.write(u'<?xml version="1.0" encoding="UTF-8"?>\n')
725  sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
726 
727  sys.exit(0)
728  # end displayTreeView()
729 # end Videos() class
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)
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
def getBBCConfig(self)
Start - Utility functions.
def searchForVideos(self, title, pagenumber)
def processVideoUrl(self, url)
End of Utility functions.
def searchTitle(self, title, pagenumber, pagelen)