MythTV master
tedtalks_api.py
Go to the documentation of this file.
1# -*- coding: UTF-8 -*-
2
3# ----------------------
4# Name: tedtalks_api - Simple-to-use Python interface to the TedTalks RSS feeds
5# (http://www.ted.com)
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 TedTalks Web site.
10#
11# License:Creative Commons GNU GPL v2
12# (http://creativecommons.org/licenses/GPL/2.0/)
13#-------------------------------------
14__title__ ="tedtalks_api - Simple-to-use Python interface to the TedTalks videos (http://www.ted.com)"
15__author__="R.D. Vaughan"
16__purpose__='''
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 TedTalks Web site. These routines process videos
19provided 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"
21'''
22
23__version__="v0.1.0"
24# 0.1.0 Initial development
25
26import os, struct, sys, re, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
27
28import logging
29from threading import Thread
30from copy import deepcopy
31from operator import itemgetter, attrgetter
32
33from .tedtalks_exceptions import (TedTalksUrlError, TedTalksHttpError, TedTalksRssError, TedTalksVideoNotFound, TedTalksConfigFileError, TedTalksUrlDownloadError)
34import io
35
36class OutStreamEncoder(object):
37 """Wraps a stream with an encoder"""
38 def __init__(self, outstream, encoding=None):
39 self.out = outstream
40 if not encoding:
41 self.encoding = sys.getfilesystemencoding()
42 else:
43 self.encoding = encoding
44
45 def write(self, obj):
46 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
47 if isinstance(obj, str):
48 obj = obj.encode(self.encoding)
49 self.out.buffer.write(obj)
50
51 def __getattr__(self, attr):
52 """Delegate everything but write to the stream"""
53 return getattr(self.out, attr)
54
55if isinstance(sys.stdout, io.TextIOWrapper):
56 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
57 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
58
59
60try:
61 from io import StringIO
62 from lxml import etree
63except Exception as e:
64 sys.stderr.write('\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
65 sys.exit(1)
66
67
68# Used for debugging
69#import nv_python_libs.mashups.mashups_api as target
70try:
71 '''Import the python mashups support classes
72 '''
73 import nv_python_libs.mashups.mashups_api as mashups_api
74except Exception as e:
75 sys.stderr.write('''
76The subdirectory "nv_python_libs/mashups" containing the modules mashups_api and
77mashups_exceptions.py (v0.1.0 or greater),
78They should have been included with the distribution of tedtalks.py.
79Error(%s)
80''' % e)
81 sys.exit(1)
82if 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__)
84 sys.exit(1)
85
86
87class Videos(object):
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
90 site target.
91
92 Supports search methods
93 The apikey is a not required to access http://www.ted.com
94 """
95 def __init__(self,
96 apikey,
97 mythtv = True,
98 interactive = False,
99 select_first = False,
100 debug = False,
101 custom_ui = None,
102 language = None,
103 search_all_languages = False,
104 ):
105 """apikey (str/unicode):
106 Specify the target site API key. Applications need their own key in some cases
107
108 mythtv (True/False):
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
111
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.
115
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
120
121 debug (True/False):
122 shows verbose debugging information
123
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)
126
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..
130
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
134 show in any language
135
136 """
137 self.config = {}
138
139 if apikey is not None:
140 self.config['apikey'] = apikey
141 else:
142 pass # TedTalks does not require an apikey
143
144 self.config['debug_enabled'] = debug # show debugging messages
145 self.common = common
146 self.common.debug = debug # Set the common function debug level
147
148 self.log_name = 'TedTalks_Grabber'
149 self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
150 self.logger = self.common.logger # Setups the logger (self.log.debug() etc)
151
152 self.config['custom_ui'] = custom_ui
153
154 self.config['interactive'] = interactive
155
156 self.config['select_first'] = select_first
157
158 self.config['search_all_languages'] = search_all_languages
159
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", }
161
162 # Channel details and search results
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}
164
165 self.channel_icon = '%SHAREDIR%/mythnetvision/icons/tedtalks.png'
166
167 self.config['image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
168
169 # Initialize Mashups api variables
170 mashups_api.common = self.common
171 self.mashups_api = mashups_api.Videos('')
172 self.mashups_api.channel = self.channel
173 if language:
174 self.mashups_api.config['language'] = self.config['language']
175 self.mashups_api.config['debug_enabled'] = self.config['debug_enabled']
176 self.mashups_api.getUserPreferences = self.getUserPreferences
177 # end __init__()
178
179
184
186 ''' Read the MNV TedTalks grabber "tedtalks_config.xml" configuration file
187 return nothing
188 '''
189 # Read the 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:]):
192 raise TedTalksConfigFileError(self.error_messages['TedTalksConfigFileError'] % (url[7:], ))
193
194 if self.config['debug_enabled']:
195 print(url)
196 print()
197 try:
198 self.tedtalks_config = etree.parse(url)
199 except Exception as errormsg:
200 raise TedTalksUrlError(self.error_messages['TedTalksUrlError'] % (url, errormsg))
201 return
202 # end getTedTalksConfig()
203
204
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.
209 return nothing
210 '''
211 # Get tedtalks_config.xml
212 self.getTedTalksConfig()
213
214 # Check if the tedtalks.xml file exists
215 userPreferenceFile = self.tedtalks_config.find('userPreferenceFile').text
216 if userPreferenceFile[0] == '~':
217 self.tedtalks_config.find('userPreferenceFile').text = "%s%s" % (os.path.expanduser("~"), userPreferenceFile[1:])
218 if os.path.isfile(self.tedtalks_config.find('userPreferenceFile').text):
219 # Read the grabber tedtalks_config.xml configuration file
220 url = 'file://%s' % (self.tedtalks_config.find('userPreferenceFile').text, )
221 if self.config['debug_enabled']:
222 print(url)
223 print()
224 try:
225 self.userPrefs = etree.parse(url)
226 except Exception as errormsg:
227 raise TedTalksUrlError(self.error_messages['TedTalksUrlError'] % (url, errormsg))
228 create = False
229 else:
230 create = True
231
232 # If required create/update the tedtalks.xml file
233 self.updateTedTalks(create)
234 return
235 # end getUserPreferences()
236
237 def updateTedTalks(self, create=False):
238 ''' Create or update the tedtalks.xml user preferences file
239 return nothing
240 '''
241 userDefaultFile = '%s/nv_python_libs/configs/XML/defaultUserPrefs/tedtalks.xml' % (baseProcessingDir, )
242 if os.path.isfile(userDefaultFile):
243 # Read the default tedtalks.xml user preferences file
244 url = 'file://%s' % (userDefaultFile, )
245 if self.config['debug_enabled']:
246 print(url)
247 print()
248 try:
249 userTedTalks = etree.parse(url)
250 except Exception as e:
251 raise TedTalksUrlError(self.error_messages['TedTalksUrlError'] % (url, e))
252 else:
253 raise Exception('!Error: The default TedTalk file is missing (%s)', userDefaultFile)
254
255 # If there was an existing tedtalks.xml file then add any relevant user settings
256 # to this new tedtalks.xml
257 if not create:
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,)
262 if len(elements):
263 elements[0].attrib['enabled'] = showElement.attrib['enabled']
264 elements[0].attrib['parameter'] = showElement.attrib['parameter']
265
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))
269 print()
270
271 # Save the tedtalks.xml file
272 prefDir = self.tedtalks_config.find('userPreferenceFile').text.replace('/tedtalks.xml', '')
273 if not os.path.isdir(prefDir):
274 os.makedirs(prefDir)
275 fd = open(self.tedtalks_config.find('userPreferenceFile').text, 'w')
276 fd.write(etree.tostring(userTedTalks, encoding='UTF-8', pretty_print=True))
277 fd.close()
278
279 # Read the refreshed user config file
280 try:
281 self.userPrefs = etree.parse(self.tedtalks_config.find('userPreferenceFile').text)
282 self.mashups_api.userPrefs = self.userPrefs
283 except Exception as errormsg:
284 raise TedTalksUrlError(self.error_messages['TedTalksUrlError'] % (url, errormsg))
285 return
286 # end updateTedTalks()
287
288
293
294 def searchTitle(self, title, pagenumber, pagelen):
295 '''Key word video search of the TedTalks web site
296 return an array of matching item elements
297 return
298 '''
299 searchVar = self.tedtalks_config.find('searchURLS').xpath(".//href")[0].text
300 try:
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))
307 url = searchVar
308
309 if self.config['debug_enabled']:
310 print(url)
311 print()
312
313 self.tedtalks_config.find('searchURLS').xpath(".//href")[0].text = url
314
315 # Globally add all the xpath extentions to the "mythtv" namespace allowing access within the
316 # XSLT stylesheets
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]
322
323 # Add the parameter element from the User preferences file
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]
328
329 # Perform a search
330 try:
331 resultTree = self.common.getUrlData(self.tedtalks_config.find('searchURLS'))
332 except Exception as errormsg:
333 raise TedTalksUrlDownloadError(self.error_messages['TedTalksUrlDownloadError'] % (errormsg))
334
335 if resultTree is None:
336 raise TedTalksVideoNotFound("No TedTalks Video matches found for search value (%s)" % title)
337
338 searchResults = resultTree.xpath('//result//item')
339 if not len(searchResults):
340 raise TedTalksVideoNotFound("No TedTalks Video matches found for search value (%s)" % title)
341
342 return searchResults
343 # end searchTitle()
344
345
346 def searchForVideos(self, title, pagenumber):
347 """Common name for a video search. Used to interface with MythTV plugin NetVision
348 """
349 # Get tedtalks_config.xml
350 self.getUserPreferences()
351
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))
355 print()
356
357 # Easier for debugging
358# print self.searchTitle(title, pagenumber, self.page_limit)
359# print
360# sys.exit()
361
362 try:
363 data = self.searchTitle(title, pagenumber, self.page_limit)
364 except TedTalksVideoNotFound as msg:
365 sys.stderr.write("%s\n" % msg)
366 sys.exit(0)
367 except TedTalksUrlError as msg:
368 sys.stderr.write('%s\n' % msg)
369 sys.exit(1)
370 except TedTalksHttpError as msg:
371 sys.stderr.write(self.error_messages['TedTalksHttpError'] % msg)
372 sys.exit(1)
373 except TedTalksRssError as msg:
374 sys.stderr.write(self.error_messages['TedTalksRssError'] % msg)
375 sys.exit(1)
376 except Exception as e:
377 sys.stderr.write("! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
378 sys.exit(1)
379
380 # Create RSS element tree
381 rssTree = etree.XML(self.common.mnvRSS+'</rss>')
382
383 # Set the paging values
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)
388 else:
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)
392
393 # Add the Channel element tree
394 channelTree = self.common.mnvChannelElement(self.channel)
395 rssTree.append(channelTree)
396
397 for item in data:
398 channelTree.append(item)
399
400 # Output the MNV search results
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))
403 sys.exit(0)
404 # end searchForVideos()
405
407 '''Gather all videos for each TedTalks show
408 Display the results and exit
409 '''
410 self.mashups_api.page_limit = self.page_limit
411 self.mashups_api.grabber_title = self.grabber_title
412 self.mashups_api.mashup_title = self.mashup_title
413 self.mashups_api.channel_icon = self.channel_icon
414 self.mashups_api.mashup_title = 'tedtalks'
415
416 # Easier for debugging
417# self.mashups_api.displayTreeView()
418# print
419# sys.exit(1)
420
421 try:
422 self.mashups_api.Search = False
424 except Exception as e:
425 sys.stderr.write("! Error: During a TedTalks Video treeview\nError(%s)\n" % (e))
426 sys.exit(1)
427
428 sys.exit(0)
429 # end displayTreeView()
430# end Videos() class
def __init__(self, outstream, encoding=None)
Definition: tedtalks_api.py:38
def searchForVideos(self, title, pagenumber)
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
def getTedTalksConfig(self)
Start - Utility functions.
def searchTitle(self, title, pagenumber, pagelen)
End of 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)
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)