MythTV  master
pbs_api.py
Go to the documentation of this file.
1 # -*- coding: UTF-8 -*-
2 
3 # ----------------------
4 # Name: pbs_api - Simple-to-use Python interface to the PBS RSS feeds
5 # (http://video.pbs.org/)
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 PBS Web site.
10 #
11 # License:Creative Commons GNU GPL v2
12 # (http://creativecommons.org/licenses/GPL/2.0/)
13 #-------------------------------------
14 __title__ ="pbs_api - Simple-to-use Python interface to the PBS videos (http://video.pbs.org/)"
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 PBS Web site. These routines process videos
19 provided by PBS (http://video.pbs.org/). The specific PBS RSS feeds that are processed are controled through a user XML preference file usually found at
20 "~/.mythtv/MythNetvision/userGrabberPrefs/pbs.xml"
21 '''
22 
23 __version__="v0.1.1"
24 # 0.1.0 Initial development
25 # 0.1.1 Debug code was should have been commented out. This has been corrected.
26 
27 import os, struct, sys, re, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
28 
29 import logging
30 from threading import Thread
31 from copy import deepcopy
32 from operator import itemgetter, attrgetter
33 
34 from .pbs_exceptions import (PBSUrlError, PBSHttpError, PBSRssError, PBSVideoNotFound, PBSConfigFileError, PBSUrlDownloadError)
35 import io
36 
37 class OutStreamEncoder(object):
38  """Wraps a stream with an encoder"""
39  def __init__(self, outstream, encoding=None):
40  self.out = outstream
41  if not encoding:
42  self.encoding = sys.getfilesystemencoding()
43  else:
44  self.encoding = encoding
45 
46  def write(self, obj):
47  """Wraps the output stream, encoding Unicode strings with the specified encoding"""
48  if isinstance(obj, str):
49  obj = obj.encode(self.encoding)
50  self.out.buffer.write(obj)
51 
52  def __getattr__(self, attr):
53  """Delegate everything but write to the stream"""
54  return getattr(self.out, attr)
55 
56 if isinstance(sys.stdout, io.TextIOWrapper):
57  sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
58  sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
59 
60 
61 try:
62  from io import StringIO
63  from lxml import etree
64 except Exception as e:
65  sys.stderr.write('\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
66  sys.exit(1)
67 
68 # Check that the lxml library is current enough
69 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html)
70 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later"
71 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package
72 version = ''
73 for digit in etree.LIBXML_VERSION:
74  version+=str(digit)+'.'
75 version = version[:-1]
76 if version < '2.7.2':
77  sys.stderr.write('''
78 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
79  At least "libxml" version 2.7.2 must be installed. Your version is (%s).
80 ''' % version)
81  sys.exit(1)
82 
83 # Used for debugging
84 #import nv_python_libs.mashups.mashups_api as target
85 try:
86  '''Import the python mashups support classes
87  '''
88  import nv_python_libs.mashups.mashups_api as mashups_api
89 except Exception as e:
90  sys.stderr.write('''
91 The subdirectory "nv_python_libs/mashups" containing the modules mashups_api and
92 mashups_exceptions.py (v0.1.0 or greater),
93 They should have been included with the distribution of pbs.py.
94 Error(%s)
95 ''' % e)
96  sys.exit(1)
97 if mashups_api.__version__ < '0.1.0':
98  sys.stderr.write("\n! Error: Your current installed mashups_api.py version is (%s)\nYou must at least have version (0.1.0) or higher.\n" % mashups_api.__version__)
99  sys.exit(1)
100 
101 
102 class Videos(object):
103  """Main interface to http://video.pbs.org/
104  This is done to support a common naming framework for all python Netvision plugins no matter their
105  site target.
106 
107  Supports search methods
108  The apikey is a not required to access http://video.pbs.org/
109  """
110  def __init__(self,
111  apikey,
112  mythtv = True,
113  interactive = False,
114  select_first = False,
115  debug = False,
116  custom_ui = None,
117  language = None,
118  search_all_languages = False,
119  ):
120  """apikey (str/unicode):
121  Specify the target site API key. Applications need their own key in some cases
122 
123  mythtv (True/False):
124  When True, the returned meta data is being returned has the key and values massaged to match MythTV
125  When False, the returned meta data is being returned matches what target site returned
126 
127  interactive (True/False): (This option is not supported by all target site apis)
128  When True, uses built-in console UI is used to select the correct show.
129  When False, the first search result is used.
130 
131  select_first (True/False): (This option is not supported currently implemented in any grabbers)
132  Automatically selects the first series search result (rather
133  than showing the user a list of more than one series).
134  Is overridden by interactive = False, or specifying a custom_ui
135 
136  debug (True/False):
137  shows verbose debugging information
138 
139  custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
140  A callable subclass of interactive class (overrides interactive option)
141 
142  language (2 character language abbreviation): (This option is not supported by all target site apis)
143  The language of the returned data. Is also the language search
144  uses. Default is "en" (English). For full list, run..
145 
146  search_all_languages (True/False): (This option is not supported by all target site apis)
147  By default, a Netvision grabber will only search in the language specified using
148  the language option. When this is True, it will search for the
149  show in any language
150 
151  """
152  self.config = {}
153 
154  if apikey is not None:
155  self.config['apikey'] = apikey
156  else:
157  pass # PBS does not require an apikey
158 
159  self.config['debug_enabled'] = debug # show debugging messages
160  self.common = common
161  self.common.debug = debug # Set the common function debug level
162 
163  self.log_name = 'PBS_Grabber'
164  self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
165  self.logger = self.common.logger # Setups the logger (self.log.debug() etc)
166 
167  self.config['custom_ui'] = custom_ui
168 
169  self.config['interactive'] = interactive
170 
171  self.config['select_first'] = select_first
172 
173  self.config['search_all_languages'] = search_all_languages
174 
175  self.error_messages = {'PBSUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'PBSHttpError': "! Error: An HTTP communications error with the PBS was raised (%s)\n", 'PBSRssError': "! Error: Invalid RSS meta data\nwas received from the PBS error (%s). Skipping item.\n", 'PBSVideoNotFound': "! Error: Video search with the PBS did not return any results (%s)\n", 'PBSConfigFileError': "! Error: pbs_config.xml file missing\nit should be located in and named as (%s).\n", 'PBSUrlDownloadError': "! Error: Downloading a RSS feed or Web page (%s).\n", }
176 
177  # Channel details and search results
178  self.channel = {'channel_title': 'PBS', 'channel_link': 'http://video.pbs.org/', 'channel_description': "Discover award-winning programming ‚Äì right at your fingertips ‚Äì on PBS Video. Catch the episodes you may have missed and watch your favorite shows whenever you want.", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0}
179 
180  self.channel_icon = '%SHAREDIR%/mythnetvision/icons/pbs.png'
181 
182  self.config['image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
183 
184  # Initialize Mashups api variables
185  mashups_api.common = self.common
186  self.mashups_api = mashups_api.Videos('')
187  self.mashups_api.channel = self.channel
188  if language:
189  self.mashups_api.config['language'] = self.config['language']
190  self.mashups_api.config['debug_enabled'] = self.config['debug_enabled']
191  self.mashups_api.getUserPreferences = self.getUserPreferences
192  # end __init__()
193 
194 
199 
200  def getPBSConfig(self):
201  ''' Read the MNV PBS grabber "pbs_config.xml" configuration file
202  return nothing
203  '''
204  # Read the grabber pbs_config.xml configuration file
205  url = 'file://%s/nv_python_libs/configs/XML/pbs_config.xml' % (baseProcessingDir, )
206  if not os.path.isfile(url[7:]):
207  raise PBSConfigFileError(self.error_messages['PBSConfigFileError'] % (url[7:], ))
208 
209  if self.config['debug_enabled']:
210  print(url)
211  print()
212  try:
213  self.pbs_config = etree.parse(url)
214  except Exception as e:
215  raise PBSUrlError(self.error_messages['PBSUrlError'] % (url, errormsg))
216  return
217  # end getPBSConfig()
218 
219 
221  '''Read the pbs_config.xml and user preference pbs.xml file.
222  If the pbs.xml file does not exist then create it.
223  If the pbs.xml file is too old then update it.
224  return nothing
225  '''
226  # Get pbs_config.xml
227  self.getPBSConfig()
228 
229  # Check if the pbs.xml file exists
230  userPreferenceFile = self.pbs_config.find('userPreferenceFile').text
231  if userPreferenceFile[0] == '~':
232  self.pbs_config.find('userPreferenceFile').text = "%s%s" % (os.path.expanduser("~"), userPreferenceFile[1:])
233  if os.path.isfile(self.pbs_config.find('userPreferenceFile').text):
234  # Read the grabber pbs_config.xml configuration file
235  url = 'file://%s' % (self.pbs_config.find('userPreferenceFile').text, )
236  if self.config['debug_enabled']:
237  print(url)
238  print()
239  try:
240  self.userPrefs = etree.parse(url)
241  except Exception as e:
242  raise PBSUrlError(self.error_messages['PBSUrlError'] % (url, errormsg))
243  # Check if the pbs.xml file is too old
244  nextUpdateSecs = int(self.userPrefs.find('updateDuration').text)*86400 # seconds in a day
245  nextUpdate = time.localtime(os.path.getmtime(self.pbs_config.find('userPreferenceFile').text)+nextUpdateSecs)
246  now = time.localtime()
247  if nextUpdate > now or self.Search:
248  self.mashups_api.userPrefs = self.userPrefs
249  return
250  create = False
251  else:
252  create = True
253 
254  # If required create/update the pbs.xml file
255  self.updatePBS(create)
256  return
257  # end getUserPreferences()
258 
259  def updatePBS(self, create=False):
260  ''' Create or update the pbs.xml user preferences file
261  return nothing
262  '''
263  userPBS = '''
264 <userPBS>
265 <!--
266  All PBS shows that have represented on the http://video.pbs.org/ web page are included
267  in as directories. A user may enable it disable an individual show so that it will be
268  included in the treeview. By default ALL shows are enabled.
269  NOTE: As the search is based on the treeview data disabling shows will also reduve the
270  number of possible search results .
271  Updates to the "pbs.xml" file is made every X number of days as determined by the value of
272  the "updateDuration" element in this file. The default is every 3 days.
273 -->
274 <!-- Number of days between updates to the config file -->
275 <updateDuration>3</updateDuration>
276 
277 <!--
278  The PBS Search
279  "enabled" If you want to remove a source URL then change the "enabled" attribute to "false".
280  "xsltFile" The XSLT file name that is used to translate data into MNV item format
281  "type" The source type "xml", "html" and "xhtml"
282  "url" The link that is used to retrieve the information from the Internet
283  "pageFunction" Identifies a XPath extension function that returns the start page/index for the
284  specific source.
285  "mnvsearch" (optional) Identifies that search items are to include items from the MNV table using the
286  mnvsearch_api.py functions. This attributes value must match the "feedtitle" value
287  as it is in the "internetcontentarticles" table. When present the "xsltFile",
288  "url" and "pageFunction" attributes are left empty as they will be ignored.
289 -->
290 <search name="PBS Search">
291  <subDirectory name="PBS">
292  <sourceURL enabled="true" name="PBS" xsltFile="" type="xml" url="" pageFunction="" mnvsearch="PBS"/>
293  </subDirectory>
294 </search>
295 
296 <!--
297  The PBS Video RSS feed and HTML URLs.
298  "globalmax" (optional) Is a way to limit the number of items processed per source for all
299  treeview URLs. A value of zero (0) means there are no limitations.
300  "max" (optional) Is a way to limit the number of items processed for an individual sourceURL.
301  This value will override any "globalmax" setting. A value of zero (0) means
302  there are no limitations and would be the same if the attribute was no included at all.
303  "enabled" If you want to remove a source URL then change the "enabled" attribute to "false".
304  "xsltFile" The XSLT file name that is used to translate data into MNV item format
305  "type" The source type "xml", "html" and "xhtml"
306  "url" The link that is used to retrieve the information from the Internet
307  "parameter" (optional) Specifies source specific parameter that are passed to the XSLT stylesheet.
308  Multiple parameters require the use of key/value pairs. Example:
309  parameter="key1:value1;key2:value2" with the ";" as the separator value.
310 -->
311 
312 '''
313 
314  # Get the current show links from the PBS web site
315  showData = self.common.getUrlData(self.pbs_config.find('treeviewUrls'))
316 
317  if self.config['debug_enabled']:
318  print("create(%s)" % create)
319  print("showData:")
320  sys.stdout.write(etree.tostring(showData, encoding='UTF-8', pretty_print=True))
321  print()
322 
323  # If there is any data then add to new pbs.xml element tree
324  showsDir = showData.xpath('//directory')
325  if len(showsDir):
326  for dirctory in showsDir:
327  userPBS+=etree.tostring(dirctory, encoding='UTF-8', pretty_print=True)
328  userPBS+='</userPBS>'
329  userPBS = etree.XML(userPBS)
330 
331  if self.config['debug_enabled']:
332  print("Before any merging userPBS:")
333  sys.stdout.write(etree.tostring(userPBS, encoding='UTF-8', pretty_print=True))
334  print()
335 
336  # If there was an existing pbs.xml file then add any relevant user settings to this new pbs.xml
337  if not create:
338  userPBS.find('updateDuration').text = self.userPrefs.find('updateDuration').text
339  for showElement in self.userPrefs.xpath("//sourceURL[@enabled='false']"):
340  showName = showElement.getparent().attrib['name']
341  sourceName = showElement.attrib['name']
342  elements = userPBS.xpath("//sourceURL[@name=$showName]", showName=showName, sourceName=sourceName)
343  if len(elements):
344  elements[0].attrib['enabled'] = 'false'
345 
346  if self.config['debug_enabled']:
347  print("After any merging userPBS:")
348  sys.stdout.write(etree.tostring(userPBS, encoding='UTF-8', pretty_print=True))
349  print()
350 
351  # Save the pbs.xml file
352  prefDir = self.pbs_config.find('userPreferenceFile').text.replace('/pbs.xml', '')
353  if not os.path.isdir(prefDir):
354  os.makedirs(prefDir)
355  fd = open(self.pbs_config.find('userPreferenceFile').text, 'w')
356  fd.write('<userPBS>\n'+''.join(etree.tostring(element, encoding='UTF-8', pretty_print=True) for element in userPBS)+'</userPBS>')
357  fd.close()
358 
359  # Read the refreshed user config file
360  try:
361  self.userPrefs = etree.parse(self.pbs_config.find('userPreferenceFile').text)
362  self.mashups_api.userPrefs = self.userPrefs
363  except Exception as e:
364  raise PBSUrlError(self.error_messages['PBSUrlError'] % (url, errormsg))
365  return
366  # end updatePBS()
367 
368 
373 
374  def searchForVideos(self, title, pagenumber):
375  """Common name for a video search. Used to interface with MythTV plugin NetVision
376  """
377  self.mashups_api.page_limit = self.page_limit
378  self.mashups_api.grabber_title = self.grabber_title
379  self.mashups_api.mashup_title = self.mashup_title
380  self.mashups_api.channel_icon = self.channel_icon
381  self.mashups_api.mashup_title = 'pbs'
382 
383  # Easier for debugging
384 # self.mashups_api.searchForVideos(title, pagenumber)
385 # print
386 # sys.exit()
387 
388  try:
389  self.Search = True
390  self.mashups_api.Search = True
391  self.mashups_api.searchForVideos(title, pagenumber)
392  except Exception as e:
393  sys.stderr.write("! Error: During a PBS Video search (%s)\nError(%s)\n" % (title, e))
394  sys.exit(1)
395 
396  sys.exit(0)
397  # end searchForVideos()
398 
399  def displayTreeView(self):
400  '''Gather all videos for each PBS show
401  Display the results and exit
402  '''
403  self.mashups_api.page_limit = self.page_limit
404  self.mashups_api.grabber_title = self.grabber_title
405  self.mashups_api.mashup_title = self.mashup_title
406  self.mashups_api.channel_icon = self.channel_icon
407  self.mashups_api.mashup_title = 'pbs'
408 
409  # Easier for debugging
410 # self.mashups_api.displayTreeView()
411 # print
412 # sys.exit(1)
413 
414  try:
415  self.Search = False
416  self.mashups_api.Search = False
418  except Exception as e:
419  sys.stderr.write("! Error: During a PBS Video treeview\nError(%s)\n" % (e))
420  sys.exit(1)
421 
422  sys.exit(0)
423  # end displayTreeView()
424 # end Videos() class
nv_python_libs.pbs.pbs_api.OutStreamEncoder.__getattr__
def __getattr__(self, attr)
Definition: pbs_api.py:52
nv_python_libs.pbs.pbs_api.Videos.mashups_api
mashups_api
Definition: pbs_api.py:177
nv_python_libs.pbs.pbs_api.Videos.userPrefs
userPrefs
Definition: pbs_api.py:240
nv_python_libs.pbs.pbs_api.Videos.channel_icon
channel_icon
Definition: pbs_api.py:171
nv_python_libs.pbs.pbs_api.Videos.getPBSConfig
def getPBSConfig(self)
Start - Utility functions.
Definition: pbs_api.py:200
nv_python_libs.pbs.pbs_api.Videos.Search
Search
Definition: pbs_api.py:389
nv_python_libs.pbs.pbs_api.OutStreamEncoder.__init__
def __init__(self, outstream, encoding=None)
Definition: pbs_api.py:39
nv_python_libs.pbs.pbs_api.Videos.getUserPreferences
def getUserPreferences(self)
Definition: pbs_api.py:220
nv_python_libs.pbs.pbs_api.Videos.pbs_config
pbs_config
Definition: pbs_api.py:213
nv_python_libs.pbs.pbs_api.Videos.logger
logger
Definition: pbs_api.py:156
nv_python_libs.pbs.pbs_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: pbs_api.py:110
nv_python_libs.mashups.mashups_api
Definition: mashups_api.py:1
nv_python_libs.pbs.pbs_api.OutStreamEncoder.write
def write(self, obj)
Definition: pbs_api.py:46
nv_python_libs.pbs.pbs_api.Videos.log_name
log_name
Definition: pbs_api.py:154
nv_python_libs.pbs.pbs_api.Videos.searchForVideos
def searchForVideos(self, title, pagenumber)
End of Utility functions.
Definition: pbs_api.py:374
nv_python_libs.pbs.pbs_api.Videos
Definition: pbs_api.py:102
nv_python_libs.pbs.pbs_api.Videos.common
common
Definition: pbs_api.py:151
nv_python_libs.pbs.pbs_api.Videos.displayTreeView
def displayTreeView(self)
Definition: pbs_api.py:399
nv_python_libs.pbs.pbs_exceptions.PBSConfigFileError
Definition: pbs_exceptions.py:42
nv_python_libs.pbs.pbs_api.OutStreamEncoder.out
out
Definition: pbs_api.py:40
nv_python_libs.pbs.pbs_api.Videos.config
config
Definition: pbs_api.py:143
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.pbs.pbs_api.OutStreamEncoder.encoding
encoding
Definition: pbs_api.py:42
nv_python_libs.pbs.pbs_exceptions.PBSUrlError
Definition: pbs_exceptions.py:22
nv_python_libs.pbs.pbs_api.Videos.updatePBS
def updatePBS(self, create=False)
Definition: pbs_api.py:259
nv_python_libs.pbs.pbs_api.Videos.channel
channel
Definition: pbs_api.py:169
nv_python_libs.pbs.pbs_api.Videos.error_messages
error_messages
Definition: pbs_api.py:166
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.pbs.pbs_api.OutStreamEncoder
Definition: pbs_api.py:37