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)
72 for digit
in etree.LIBXML_VERSION:
73 version+=str(digit)+
'.'
74 version = version[:-1]
77 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
78 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
85 '''Import the python mashups support classes
88 except Exception
as e:
90 The subdirectory "nv_python_libs/mashups" containing the modules mashups_api and
91 mashups_exceptions.py (v0.1.0 or greater),
92 They should have been included with the distribution of tedtalks.py.
96 if mashups_api.__version__ <
'0.1.0':
97 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__)
102 """Main interface to http://www.ted.com
103 This is done to support a common naming framework for all python Netvision plugins no matter their
106 Supports search methods
107 The apikey is a not required to access http://www.ted.com
113 select_first = False,
117 search_all_languages = False,
119 """apikey (str/unicode):
120 Specify the target site API key. Applications need their own key in some cases
123 When True, the returned meta data is being returned has the key and values massaged to match MythTV
124 When False, the returned meta data is being returned matches what target site returned
126 interactive (True/False): (This option is not supported by all target site apis)
127 When True, uses built-in console UI is used to select the correct show.
128 When False, the first search result is used.
130 select_first (True/False): (This option is not supported currently implemented in any grabbers)
131 Automatically selects the first series search result (rather
132 than showing the user a list of more than one series).
133 Is overridden by interactive = False, or specifying a custom_ui
136 shows verbose debugging information
138 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
139 A callable subclass of interactive class (overrides interactive option)
141 language (2 character language abbreviation): (This option is not supported by all target site apis)
142 The language of the returned data. Is also the language search
143 uses. Default is "en" (English). For full list, run..
145 search_all_languages (True/False): (This option is not supported by all target site apis)
146 By default, a Netvision grabber will only search in the language specified using
147 the language option. When this is True, it will search for the
153 if apikey
is not None:
154 self.
config[
'apikey'] = apikey
158 self.
config[
'debug_enabled'] = debug
166 self.
config[
'custom_ui'] = custom_ui
170 self.
config[
'select_first'] = select_first
172 self.
config[
'search_all_languages'] = search_all_languages
174 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", }
177 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}
179 self.
channel_icon =
'%SHAREDIR%/mythnetvision/icons/tedtalks.png'
181 self.
config[
'image_extentions'] = [
"png",
"jpg",
"bmp"]
184 mashups_api.common = self.
common
200 ''' Read the MNV TedTalks grabber "tedtalks_config.xml" configuration file
204 url =
'file://%s/nv_python_libs/configs/XML/tedtalks_config.xml' % (baseProcessingDir, )
205 if not os.path.isfile(url[7:]):
208 if self.
config[
'debug_enabled']:
213 except Exception
as errormsg:
220 '''Read the tedtalks_config.xml and user preference tedtalks.xml file.
221 If the tedtalks.xml file does not exist then create it.
222 If the tedtalks.xml file is too old then update it.
230 if userPreferenceFile[0] ==
'~':
231 self.
tedtalks_config.
find(
'userPreferenceFile').text =
"%s%s" % (os.path.expanduser(
"~"), userPreferenceFile[1:])
235 if self.
config[
'debug_enabled']:
240 except Exception
as errormsg:
252 ''' Create or update the tedtalks.xml user preferences file
255 userDefaultFile =
'%s/nv_python_libs/configs/XML/defaultUserPrefs/tedtalks.xml' % (baseProcessingDir, )
256 if os.path.isfile(userDefaultFile):
258 url =
'file://%s' % (userDefaultFile, )
259 if self.
config[
'debug_enabled']:
263 userTedTalks = etree.parse(url)
264 except Exception
as e:
267 raise Exception(
'!Error: The default TedTalk file is missing (%s)', userDefaultFile)
272 for showElement
in self.
userPrefs.xpath(
"//sourceURL"):
273 showName = showElement.getparent().attrib[
'name']
274 sourceName = showElement.attrib[
'name']
275 elements = userTedTalks.xpath(
"//sourceURL[@name=$showName]", showName=showName,)
277 elements[0].attrib[
'enabled'] = showElement.attrib[
'enabled']
278 elements[0].attrib[
'parameter'] = showElement.attrib[
'parameter']
280 if self.
config[
'debug_enabled']:
281 print(
"After any merging userTedTalks:")
282 sys.stdout.write(etree.tostring(userTedTalks, encoding=
'UTF-8', pretty_print=
True))
286 prefDir = self.
tedtalks_config.
find(
'userPreferenceFile').text.replace(
'/tedtalks.xml',
'')
287 if not os.path.isdir(prefDir):
290 fd.write(etree.tostring(userTedTalks, encoding=
'UTF-8', pretty_print=
True))
297 except Exception
as errormsg:
309 '''Key word video search of the TedTalks web site
310 return an array of matching item elements
315 searchVar = searchVar.replace(
'SEARCHTERM', urllib.parse.quote_plus(title.encode(
"utf-8")))
316 searchVar = searchVar.replace(
'PAGENUM', str(pagenumber))
317 except UnicodeDecodeError:
318 searchVar =
'?q=%s' % ()
319 searchVar = searchVar.replace(
'SEARCHTERM', urllib.parse.quote_plus(title))
320 searchVar = searchVar.replace(
'PAGENUM', str(pagenumber))
323 if self.
config[
'debug_enabled']:
331 self.
common.buildFunctionDict()
332 mnvXpath = etree.FunctionNamespace(
'http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format')
333 mnvXpath.prefix =
'mnvXpath'
334 for key
in list(self.
common.functionDict.keys()):
335 mnvXpath[key] = common.functionDict[key]
338 paraMeter = self.
userPrefs.
find(
'search').xpath(
"//search//sourceURL[@enabled='true']/@parameter")
339 if not len(paraMeter):
340 raise Exception(
'TedTalks User preferences file "tedtalks.xml" does not have an enabled search with a "parameter" attribute.')
341 etree.SubElement(self.
tedtalks_config.
find(
'searchURLS').xpath(
".//url")[0],
"parameter").text = paraMeter[0]
346 except Exception
as errormsg:
349 if resultTree
is None:
352 searchResults = resultTree.xpath(
'//result//item')
353 if not len(searchResults):
361 """Common name for a video search. Used to interface with MythTV plugin NetVision
366 if self.
config[
'debug_enabled']:
367 print(
"self.tedtalks_config:")
368 sys.stdout.write(etree.tostring(self.
tedtalks_config, encoding=
'UTF-8', pretty_print=
True))
377 data = self.
searchTitle(title, pagenumber, self.page_limit)
378 except TedTalksVideoNotFound
as msg:
379 sys.stderr.write(
"%s\n" % msg)
381 except TedTalksUrlError
as msg:
382 sys.stderr.write(
'%s\n' % msg)
384 except TedTalksHttpError
as msg:
387 except TedTalksRssError
as msg:
390 except Exception
as e:
391 sys.stderr.write(
"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
395 rssTree = etree.XML(self.
common.mnvRSS+
'</rss>')
398 if len(data) == self.page_limit:
399 self.
channel[
'channel_returned'] = len(data)
400 self.
channel[
'channel_startindex'] = len(data)+(self.page_limit*(int(pagenumber)-1))
401 self.
channel[
'channel_numresults'] = len(data)+(self.page_limit*(int(pagenumber)-1)+1)
403 self.
channel[
'channel_returned'] = len(data)+(self.page_limit*(int(pagenumber)-1))
404 self.
channel[
'channel_startindex'] = len(data)
405 self.
channel[
'channel_numresults'] = len(data)
409 rssTree.append(channelTree)
412 channelTree.append(item)
415 sys.stdout.write(
'<?xml version="1.0" encoding="UTF-8"?>\n')
416 sys.stdout.write(etree.tostring(rssTree, encoding=
'UTF-8', pretty_print=
True))
421 '''Gather all videos for each TedTalks show
422 Display the results and exit
425 self.
mashups_api.grabber_title = self.grabber_title
438 except Exception
as e:
439 sys.stderr.write(
"! Error: During a TedTalks Video treeview\nError(%s)\n" % (e))