14 __title__ =
"pbs_api - Simple-to-use Python interface to the PBS videos (http://video.pbs.org/)"
15 __author__=
"R.D. Vaughan"
17 This python script is intended to perform a variety of utility functions to search and access text
18 meta data, video and image URLs from 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"
27 import os, struct, sys, re, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
30 from threading
import Thread
31 from copy
import deepcopy
32 from operator
import itemgetter, attrgetter
34 from .pbs_exceptions
import (PBSUrlError, PBSHttpError, PBSRssError, PBSVideoNotFound, PBSConfigFileError, PBSUrlDownloadError)
38 """Wraps a stream with an encoder"""
47 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
48 if isinstance(obj, str):
50 self.
out.buffer.write(obj)
53 """Delegate everything but write to the stream"""
54 return getattr(self.
out, attr)
56 if isinstance(sys.stdout, io.TextIOWrapper):
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)
73 for digit
in etree.LIBXML_VERSION:
74 version+=str(digit)+
'.'
75 version = version[:-1]
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).
86 '''Import the python mashups support classes
89 except Exception
as e:
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.
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__)
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
107 Supports search methods
108 The apikey is a not required to access http://video.pbs.org/
114 select_first = False,
118 search_all_languages = False,
120 """apikey (str/unicode):
121 Specify the target site API key. Applications need their own key in some cases
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
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.
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
137 shows verbose debugging information
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)
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..
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
154 if apikey
is not None:
155 self.
config[
'apikey'] = apikey
159 self.
config[
'debug_enabled'] = debug
167 self.
config[
'custom_ui'] = custom_ui
171 self.
config[
'select_first'] = select_first
173 self.
config[
'search_all_languages'] = search_all_languages
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", }
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}
180 self.
channel_icon =
'%SHAREDIR%/mythnetvision/icons/pbs.png'
182 self.
config[
'image_extentions'] = [
"png",
"jpg",
"bmp"]
185 mashups_api.common = self.
common
201 ''' Read the MNV PBS 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:]):
209 if self.
config[
'debug_enabled']:
214 except Exception
as e:
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.
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):
235 url =
'file://%s' % (self.
pbs_config.
find(
'userPreferenceFile').text, )
236 if self.
config[
'debug_enabled']:
241 except Exception
as e:
244 nextUpdateSecs = int(self.
userPrefs.
find(
'updateDuration').text)*86400
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:
260 ''' Create or update the pbs.xml user preferences file
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.
274 <!-- Number of days between updates to the config file -->
275 <updateDuration>3</updateDuration>
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
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.
290 <search name="PBS Search">
291 <subDirectory name="PBS">
292 <sourceURL enabled="true" name="PBS" xsltFile="" type="xml" url="" pageFunction="" mnvsearch="PBS"/>
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.
317 if self.
config[
'debug_enabled']:
318 print(
"create(%s)" % create)
320 sys.stdout.write(etree.tostring(showData, encoding=
'UTF-8', pretty_print=
True))
324 showsDir = showData.xpath(
'//directory')
326 for dirctory
in showsDir:
327 userPBS+=etree.tostring(dirctory, encoding=
'UTF-8', pretty_print=
True)
328 userPBS+=
'</userPBS>'
329 userPBS = etree.XML(userPBS)
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))
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)
344 elements[0].attrib[
'enabled'] =
'false'
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))
352 prefDir = self.
pbs_config.
find(
'userPreferenceFile').text.replace(
'/pbs.xml',
'')
353 if not os.path.isdir(prefDir):
356 fd.write(
'<userPBS>\n'+
''.join(etree.tostring(element, encoding=
'UTF-8', pretty_print=
True)
for element
in userPBS)+
'</userPBS>')
363 except Exception
as e:
375 """Common name for a video search. Used to interface with MythTV plugin NetVision
378 self.
mashups_api.grabber_title = self.grabber_title
392 except Exception
as e:
393 sys.stderr.write(
"! Error: During a PBS Video search (%s)\nError(%s)\n" % (title, e))
400 '''Gather all videos for each PBS show
401 Display the results and exit
404 self.
mashups_api.grabber_title = self.grabber_title
418 except Exception
as e:
419 sys.stderr.write(
"! Error: During a PBS Video treeview\nError(%s)\n" % (e))