14 __title__ =
"tedtalks_api - Simple-to-use Python interface to the TedTalks videos (http://www.ted.com)"
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 TedTalks Web site. These routines process videos
19 provided by TedTalks (http://www.ted.com). The specific TedTalks RSS feeds that are processed are controled through a user XML preference file usually found at
20 "~/.mythtv/MythNetvision/userGrabberPrefs/tedtalks.xml"
26 import os, struct, sys, re, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
29 from threading
import Thread
30 from copy
import deepcopy
31 from operator
import itemgetter, attrgetter
33 from .tedtalks_exceptions
import (TedTalksUrlError, TedTalksHttpError, TedTalksRssError, TedTalksVideoNotFound, TedTalksConfigFileError, TedTalksUrlDownloadError)
37 """Wraps a stream with an encoder"""
46 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
47 if isinstance(obj, str):
49 self.
out.buffer.write(obj)
52 """Delegate everything but write to the stream"""
53 return getattr(self.
out, attr)
55 if isinstance(sys.stdout, io.TextIOWrapper):
61 from io
import StringIO
62 from lxml
import etree
63 except Exception
as e:
64 sys.stderr.write(
'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
71 '''Import the python mashups support classes
74 except Exception
as e:
76 The subdirectory "nv_python_libs/mashups" containing the modules mashups_api and
77 mashups_exceptions.py (v0.1.0 or greater),
78 They should have been included with the distribution of tedtalks.py.
82 if mashups_api.__version__ <
'0.1.0':
83 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__)
88 """Main interface to http://www.ted.com
89 This is done to support a common naming framework for all python Netvision plugins no matter their
92 Supports search methods
93 The apikey is a not required to access http://www.ted.com
103 search_all_languages = False,
105 """apikey (str/unicode):
106 Specify the target site API key. Applications need their own key in some cases
109 When True, the returned meta data is being returned has the key and values massaged to match MythTV
110 When False, the returned meta data is being returned matches what target site returned
112 interactive (True/False): (This option is not supported by all target site apis)
113 When True, uses built-in console UI is used to select the correct show.
114 When False, the first search result is used.
116 select_first (True/False): (This option is not supported currently implemented in any grabbers)
117 Automatically selects the first series search result (rather
118 than showing the user a list of more than one series).
119 Is overridden by interactive = False, or specifying a custom_ui
122 shows verbose debugging information
124 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
125 A callable subclass of interactive class (overrides interactive option)
127 language (2 character language abbreviation): (This option is not supported by all target site apis)
128 The language of the returned data. Is also the language search
129 uses. Default is "en" (English). For full list, run..
131 search_all_languages (True/False): (This option is not supported by all target site apis)
132 By default, a Netvision grabber will only search in the language specified using
133 the language option. When this is True, it will search for the
139 if apikey
is not None:
140 self.
config[
'apikey'] = apikey
144 self.
config[
'debug_enabled'] = debug
152 self.
config[
'custom_ui'] = custom_ui
156 self.
config[
'select_first'] = select_first
158 self.
config[
'search_all_languages'] = search_all_languages
160 self.
error_messages = {
'TedTalksUrlError':
"! Error: The URL (%s) cause the exception error (%s)\n",
'TedTalksHttpError':
"! Error: An HTTP communications error with the TedTalks was raised (%s)\n",
'TedTalksRssError':
"! Error: Invalid RSS meta data\nwas received from the TedTalks error (%s). Skipping item.\n",
'TedTalksVideoNotFound':
"! Error: Video search with the TedTalks did not return any results (%s)\n",
'TedTalksConfigFileError':
"! Error: tedtalks_config.xml file missing\nit should be located in and named as (%s).\n",
'TedTalksUrlDownloadError':
"! Error: Downloading a RSS feed or Web page (%s).\n", }
163 self.
channel = {
'channel_title':
'TedTalks',
'channel_link':
'http://www.ted.com',
'channel_description':
"TED is a small nonprofit devoted to Ideas Worth Spreading.",
'channel_numresults': 0,
'channel_returned': 1,
'channel_startindex': 0}
165 self.
channel_icon =
'%SHAREDIR%/mythnetvision/icons/tedtalks.png'
167 self.
config[
'image_extentions'] = [
"png",
"jpg",
"bmp"]
170 mashups_api.common = self.
common
186 ''' Read the MNV TedTalks grabber "tedtalks_config.xml" configuration file
190 url =
'file://%s/nv_python_libs/configs/XML/tedtalks_config.xml' % (baseProcessingDir, )
191 if not os.path.isfile(url[7:]):
194 if self.
config[
'debug_enabled']:
199 except Exception
as errormsg:
206 '''Read the tedtalks_config.xml and user preference tedtalks.xml file.
207 If the tedtalks.xml file does not exist then create it.
208 If the tedtalks.xml file is too old then update it.
216 if userPreferenceFile[0] ==
'~':
217 self.
tedtalks_config.
find(
'userPreferenceFile').text =
"%s%s" % (os.path.expanduser(
"~"), userPreferenceFile[1:])
221 if self.
config[
'debug_enabled']:
226 except Exception
as errormsg:
238 ''' Create or update the tedtalks.xml user preferences file
241 userDefaultFile =
'%s/nv_python_libs/configs/XML/defaultUserPrefs/tedtalks.xml' % (baseProcessingDir, )
242 if os.path.isfile(userDefaultFile):
244 url =
'file://%s' % (userDefaultFile, )
245 if self.
config[
'debug_enabled']:
249 userTedTalks = etree.parse(url)
250 except Exception
as e:
253 raise Exception(
'!Error: The default TedTalk file is missing (%s)', userDefaultFile)
258 for showElement
in self.
userPrefs.xpath(
"//sourceURL"):
259 showName = showElement.getparent().attrib[
'name']
260 sourceName = showElement.attrib[
'name']
261 elements = userTedTalks.xpath(
"//sourceURL[@name=$showName]", showName=showName,)
263 elements[0].attrib[
'enabled'] = showElement.attrib[
'enabled']
264 elements[0].attrib[
'parameter'] = showElement.attrib[
'parameter']
266 if self.
config[
'debug_enabled']:
267 print(
"After any merging userTedTalks:")
268 sys.stdout.write(etree.tostring(userTedTalks, encoding=
'UTF-8', pretty_print=
True))
272 prefDir = self.
tedtalks_config.
find(
'userPreferenceFile').text.replace(
'/tedtalks.xml',
'')
273 if not os.path.isdir(prefDir):
276 fd.write(etree.tostring(userTedTalks, encoding=
'UTF-8', pretty_print=
True))
283 except Exception
as errormsg:
295 '''Key word video search of the TedTalks web site
296 return an array of matching item elements
301 searchVar = searchVar.replace(
'SEARCHTERM', urllib.parse.quote_plus(title.encode(
"utf-8")))
302 searchVar = searchVar.replace(
'PAGENUM', str(pagenumber))
303 except UnicodeDecodeError:
304 searchVar =
'?q=%s' % ()
305 searchVar = searchVar.replace(
'SEARCHTERM', urllib.parse.quote_plus(title))
306 searchVar = searchVar.replace(
'PAGENUM', str(pagenumber))
309 if self.
config[
'debug_enabled']:
317 self.
common.buildFunctionDict()
318 mnvXpath = etree.FunctionNamespace(
'http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format')
319 mnvXpath.prefix =
'mnvXpath'
320 for key
in list(self.
common.functionDict.keys()):
321 mnvXpath[key] = common.functionDict[key]
324 paraMeter = self.
userPrefs.
find(
'search').xpath(
"//search//sourceURL[@enabled='true']/@parameter")
325 if not len(paraMeter):
326 raise Exception(
'TedTalks User preferences file "tedtalks.xml" does not have an enabled search with a "parameter" attribute.')
327 etree.SubElement(self.
tedtalks_config.
find(
'searchURLS').xpath(
".//url")[0],
"parameter").text = paraMeter[0]
332 except Exception
as errormsg:
335 if resultTree
is None:
338 searchResults = resultTree.xpath(
'//result//item')
339 if not len(searchResults):
347 """Common name for a video search. Used to interface with MythTV plugin NetVision
352 if self.
config[
'debug_enabled']:
353 print(
"self.tedtalks_config:")
354 sys.stdout.write(etree.tostring(self.
tedtalks_config, encoding=
'UTF-8', pretty_print=
True))
363 data = self.
searchTitle(title, pagenumber, self.page_limit)
364 except TedTalksVideoNotFound
as msg:
365 sys.stderr.write(
"%s\n" % msg)
367 except TedTalksUrlError
as msg:
368 sys.stderr.write(
'%s\n' % msg)
370 except TedTalksHttpError
as msg:
373 except TedTalksRssError
as msg:
376 except Exception
as e:
377 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
381 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
384 if len(data) == self.page_limit:
385 self.
channel[
'channel_returned'] = len(data)
386 self.
channel[
'channel_startindex'] = len(data)+(self.page_limit*(int(pagenumber)-1))
387 self.
channel[
'channel_numresults'] = len(data)+(self.page_limit*(int(pagenumber)-1)+1)
389 self.
channel[
'channel_returned'] = len(data)+(self.page_limit*(int(pagenumber)-1))
390 self.
channel[
'channel_startindex'] = len(data)
391 self.
channel[
'channel_numresults'] = len(data)
395 rssTree.append(channelTree)
398 channelTree.append(item)
401 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
402 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
407 '''Gather all videos for each TedTalks show
408 Display the results and exit
411 self.
mashups_api.grabber_title = self.grabber_title
424 except Exception
as e:
425 sys.stderr.write(
"! Error: During a TedTalks Video treeview\nError(%s)\n" % (e))