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