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