14__title__ =
"pbs_api - Simple-to-use Python interface to the PBS videos (http://video.pbs.org/)"
15__author__=
"R.D. Vaughan"
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 PBS Web site. These routines process videos
19provided 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"
27import os, struct, sys, re, time, datetime, shutil,
urllib.request, urllib.parse, urllib.error
30from threading
import Thread
31from copy
import deepcopy
32from operator
import itemgetter, attrgetter
34from .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)
56if isinstance(sys.stdout, io.TextIOWrapper):
62 from io
import StringIO
63 from lxml
import etree
65 sys.stderr.write(
'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
72 '''Import the python mashups support classes
77The subdirectory "nv_python_libs/mashups" containing the modules mashups_api and
78mashups_exceptions.py (v0.1.0 or greater),
79They should have been included with the distribution of pbs.py.
83if mashups_api.__version__ <
'0.1.0':
84 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__)
89 """Main interface to http://video.pbs.org/
90 This is done to support a common naming framework
for all python Netvision plugins no matter their
93 Supports search methods
94 The apikey
is a
not required to access http://video.pbs.org/
100 select_first = False,
104 search_all_languages = False,
106 """apikey (str/unicode):
107 Specify the target site API key. Applications need their own key in some cases
110 When
True, the returned meta data
is being returned has the key
and values massaged to match MythTV
111 When
False, the returned meta data
is being returned matches what target site returned
113 interactive (
True/
False): (This option
is not supported by all target site apis)
114 When
True, uses built-
in console UI
is used to select the correct show.
115 When
False, the first search result
is used.
117 select_first (
True/
False): (This option
is not supported currently implemented
in any grabbers)
118 Automatically selects the first series search result (rather
119 than showing the user a list of more than one series).
120 Is overridden by interactive =
False,
or specifying a custom_ui
123 shows verbose debugging information
125 custom_ui (xx_ui.BaseUI subclass): (This option
is not supported currently implemented
in any grabbers)
126 A callable subclass of interactive
class (overrides interactive option)
128 language (2 character language abbreviation): (This option
is not supported by all target site apis)
129 The language of the returned data. Is also the language search
130 uses. Default
is "en" (English). For full list, run..
132 search_all_languages (
True/
False): (This option
is not supported by all target site apis)
133 By default, a Netvision grabber will only search
in the language specified using
134 the language option. When this
is True, it will search
for the
140 if apikey
is not None:
141 self.
config[
'apikey'] = apikey
145 self.
config[
'debug_enabled'] = debug
153 self.
config[
'custom_ui'] = custom_ui
155 self.
config[
'interactive'] = interactive
157 self.
config[
'select_first'] = select_first
159 self.
config[
'search_all_languages'] = search_all_languages
161 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", }
164 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}
168 self.
config[
'image_extentions'] = [
"png",
"jpg",
"bmp"]
171 mashups_api.common = self.
common
187 ''' Read the MNV PBS grabber "pbs_config.xml" configuration file
191 url =
'file://%s/nv_python_libs/configs/XML/pbs_config.xml' % (baseProcessingDir, )
192 if not os.path.isfile(url[7:]):
195 if self.
config[
'debug_enabled']:
200 except Exception
as e:
207 '''Read the pbs_config.xml and user preference pbs.xml file.
208 If the pbs.xml file does not exist then create it.
209 If the pbs.xml file
is too old then update it.
216 userPreferenceFile = self.
pbs_config.
find(
'userPreferenceFile').text
217 if userPreferenceFile[0] ==
'~':
218 self.
pbs_config.
find(
'userPreferenceFile').text =
"%s%s" % (os.path.expanduser(
"~"), userPreferenceFile[1:])
219 if os.path.isfile(self.
pbs_config.
find(
'userPreferenceFile').text):
221 url =
'file://%s' % (self.
pbs_config.
find(
'userPreferenceFile').text, )
222 if self.
config[
'debug_enabled']:
227 except Exception
as e:
230 nextUpdateSecs = int(self.
userPrefs.
find(
'updateDuration').text)*86400
231 nextUpdate = time.localtime(os.path.getmtime(self.
pbs_config.
find(
'userPreferenceFile').text)+nextUpdateSecs)
232 now = time.localtime()
233 if nextUpdate > now
or self.
Search:
246 ''' Create or update the pbs.xml user preferences file
252 All PBS shows that have represented on the http://video.pbs.org/ web page are included
253 in as directories. A user may enable it disable an individual show so that it will be
254 included
in the treeview. By default ALL shows are enabled.
255 NOTE: As the search
is based on the treeview data disabling shows will also reduve the
256 number of possible search results .
257 Updates to the
"pbs.xml" file
is made every X number of days
as determined by the value of
258 the
"updateDuration" element
in this file. The default
is every 3 days.
260<!-- Number of days between updates to the config file -->
261<updateDuration>3</updateDuration>
265 "enabled" If you want to remove a source URL then change the
"enabled" attribute to
"false".
266 "xsltFile" The XSLT file name that
is used to translate data into MNV item format
267 "type" The source type
"xml",
"html" and "xhtml"
268 "url" The link that
is used to retrieve the information
from the Internet
269 "pageFunction" Identifies a XPath extension function that returns the start page/index
for the
271 "mnvsearch" (optional) Identifies that search items are to include items
from the MNV table using the
272 mnvsearch_api.py functions. This attributes value must match the
"feedtitle" value
273 as it
is in the
"internetcontentarticles" table. When present the
"xsltFile",
274 "url" and "pageFunction" attributes are left empty
as they will be ignored.
276<search name=
"PBS Search">
277 <subDirectory name=
"PBS">
278 <sourceURL enabled=
"true" name=
"PBS" xsltFile=
"" type=
"xml" url=
"" pageFunction=
"" mnvsearch=
"PBS"/>
283 The PBS Video RSS feed
and HTML URLs.
284 "globalmax" (optional) Is a way to limit the number of items processed per source
for all
285 treeview URLs. A value of zero (0) means there are no limitations.
286 "max" (optional) Is a way to limit the number of items processed
for an individual sourceURL.
287 This value will override any
"globalmax" setting. A value of zero (0) means
288 there are no limitations
and would be the same
if the attribute was no included at all.
289 "enabled" If you want to remove a source URL then change the
"enabled" attribute to
"false".
290 "xsltFile" The XSLT file name that
is used to translate data into MNV item format
291 "type" The source type
"xml",
"html" and "xhtml"
292 "url" The link that
is used to retrieve the information
from the Internet
293 "parameter" (optional) Specifies source specific parameter that are passed to the XSLT stylesheet.
294 Multiple parameters require the use of key/value pairs. Example:
295 parameter=
"key1:value1;key2:value2" with the
";" as the separator value.
303 if self.
config[
'debug_enabled']:
304 print(
"create(%s)" % create)
306 sys.stdout.write(etree.tostring(showData, encoding=
'UTF-8', pretty_print=
True))
310 showsDir = showData.xpath(
'//directory')
312 for dirctory
in showsDir:
313 userPBS+=etree.tostring(dirctory, encoding=
'UTF-8', pretty_print=
True)
314 userPBS+=
'</userPBS>'
315 userPBS = etree.XML(userPBS)
317 if self.
config[
'debug_enabled']:
318 print(
"Before any merging userPBS:")
319 sys.stdout.write(etree.tostring(userPBS, encoding=
'UTF-8', pretty_print=
True))
324 userPBS.find(
'updateDuration').text = self.
userPrefs.
find(
'updateDuration').text
325 for showElement
in self.
userPrefs.xpath(
"//sourceURL[@enabled='false']"):
326 showName = showElement.getparent().attrib[
'name']
327 sourceName = showElement.attrib[
'name']
328 elements = userPBS.xpath(
"//sourceURL[@name=$showName]", showName=showName, sourceName=sourceName)
330 elements[0].attrib[
'enabled'] =
'false'
332 if self.
config[
'debug_enabled']:
333 print(
"After any merging userPBS:")
334 sys.stdout.write(etree.tostring(userPBS, encoding=
'UTF-8', pretty_print=
True))
338 prefDir = self.
pbs_config.
find(
'userPreferenceFile').text.replace(
'/pbs.xml',
'')
339 if not os.path.isdir(prefDir):
342 fd.write(
'<userPBS>\n'+
''.join(etree.tostring(element, encoding=
'UTF-8', pretty_print=
True)
for element
in userPBS)+
'</userPBS>')
349 except Exception
as e:
361 """Common name for a video search. Used to interface with MythTV plugin NetVision
364 self.mashups_api.grabber_title = self.grabber_title
378 except Exception
as e:
379 sys.stderr.write(
"! Error: During a PBS Video search (%s)\nError(%s)\n" % (title, e))
386 '''Gather all videos for each PBS show
387 Display the results and exit
390 self.mashups_api.grabber_title = self.grabber_title
404 except Exception
as e:
405 sys.stderr.write(
"! Error: During a PBS Video treeview\nError(%s)\n" % (e))
def __init__(self, outstream, encoding=None)
def __getattr__(self, attr)
def searchForVideos(self, title, pagenumber)
End of Utility functions.
def displayTreeView(self)
def updatePBS(self, create=False)
def getUserPreferences(self)
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
def getPBSConfig(self)
Start - Utility functions.
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)