MythTV master
mashups_api.py
Go to the documentation of this file.
1# -*- coding: UTF-8 -*-
2
3# ----------------------
4# Name: mashups_api - Simple-to-use Python interface to Mashups of RSS feeds and HTML video data
5#
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 various Internet sources.
10#
11# License:Creative Commons GNU GPL v2
12# (http://creativecommons.org/licenses/GPL/2.0/)
13#-------------------------------------
14__title__ ="mashups_api - Simple-to-use Python interface to Mashups of RSS feeds and HTML video data"
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 various Internet sources. These routines process the RSS feeds and information into MNV standard channel, directory and item RSS XML files. The specific Mashups are specified through a user XML preference file usually found at
19"~/.mythtv/MythNetvision/userGrabberPrefs/xxxxMashup.xml" where "xxxx" is the specific mashup name matching the associated grabber name that calls these functions.
20'''
21
22__version__="v0.1.6"
23# 0.1.0 Initial development
24# 0.1.1 Added Search Mashup capabilities
25# 0.1.2 Fixed a couple of error messages with improper variable names
26# 0.1.3 Add the ability for a Mashup to search the "internetcontentarticles" table
27# 0.1.4 Add the ability for a Mashup to pass variables to a XSLT style sheet
28# 0.1.5 Removed a redundant build of the common XSLT function dictionary
29# 0.1.6 Corrected a bug were a users custom setting were not being updated properly
30
31import os, struct, sys, time, datetime, shutil, urllib.request, urllib.parse, urllib.error
32from socket import gethostname, gethostbyname
33from copy import deepcopy
34import logging
35
36from .mashups_exceptions import (MashupsUrlError, MashupsHttpError, MashupsRssError, MashupsVideoNotFound, MashupsConfigFileError, MashupsUrlDownloadError)
37import io
38
39class OutStreamEncoder(object):
40 """Wraps a stream with an encoder"""
41 def __init__(self, outstream, encoding=None):
42 self.out = outstream
43 if not encoding:
44 self.encoding = sys.getfilesystemencoding()
45 else:
46 self.encoding = encoding
47
48 def write(self, obj):
49 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
50 if isinstance(obj, str):
51 obj = obj.encode(self.encoding)
52 self.out.buffer.write(obj)
53
54 def __getattr__(self, attr):
55 """Delegate everything but write to the stream"""
56 return getattr(self.out, attr)
57
58if isinstance(sys.stdout, io.TextIOWrapper):
59 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
60 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
61
62
63try:
64 from io import StringIO
65 from lxml import etree
66except Exception as e:
67 sys.stderr.write('\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
68 sys.exit(1)
69
70
71class Videos(object):
72 """Main interface to any Mashup
73 This is done to support a common naming framework for all python Netvision plugins
74 no matter their site target.
75
76 Supports MNV Mashup Search and Treeview methods
77 The apikey is a not required for Mashups
78 """
79 def __init__(self,
80 apikey,
81 mythtv = True,
82 interactive = False,
83 select_first = False,
84 debug = False,
85 custom_ui = None,
86 language = None,
87 search_all_languages = False,
88 ):
89 """apikey (str/unicode):
90 Specify the target site API key. Applications need their own key in some cases
91
92 mythtv (True/False):
93 When True, the returned meta data is being returned has the key and values massaged to match MythTV
94 When False, the returned meta data is being returned matches what target site returned
95
96 interactive (True/False): (This option is not supported by all target site apis)
97 When True, uses built-in console UI is used to select the correct show.
98 When False, the first search result is used.
99
100 select_first (True/False): (This option is not supported currently implemented in any grabbers)
101 Automatically selects the first series search result (rather
102 than showing the user a list of more than one series).
103 Is overridden by interactive = False, or specifying a custom_ui
104
105 debug (True/False):
106 shows verbose debugging information
107
108 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
109 A callable subclass of interactive class (overrides interactive option)
110
111 language (2 character language abbreviation): (This option is not supported by all target site apis)
112 The language of the returned data. Is also the language search
113 uses. Default is "en" (English). For full list, run..
114
115 search_all_languages (True/False): (This option is not supported by all target site apis)
116 By default, a Netvision grabber will only search in the language specified using
117 the language option. When this is True, it will search for the
118 show in any language
119
120 """
121 self.config = {}
122
123 if apikey is not None:
124 self.config['apikey'] = apikey
125 else:
126 pass # Mashups does not require an apikey
127
128 self.config['debug_enabled'] = debug # show debugging messages
129 self.common = common
130 self.common.debug = debug # Set the common function debug level
131
132 self.log_name = 'Mashups_Grabber'
133 self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
134 self.logger = self.common.logger # Setups the logger (self.log.debug() etc)
135
136 self.config['custom_ui'] = custom_ui
137
138 self.config['interactive'] = interactive
139
140 self.config['select_first'] = select_first
141
142 if language:
143 self.config['language'] = language
144 else:
145 self.config['language'] = 'en'
146
147 self.config['search_all_languages'] = search_all_languages
148
149 self.error_messages = {'MashupsUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'MashupsHttpError': "! Error: An HTTP communications error with the Mashups was raised (%s)\n", 'MashupsRssError': "! Error: Invalid RSS meta data\nwas received from the Mashups error (%s). Skipping item.\n", 'MashupsVideoNotFound': "! Error: Video search with the Mashups did not return any results (%s)\n", 'MashupsConfigFileError': "! Error: mashups_config.xml file missing\nit should be located in and named as (%s).\n", 'MashupsUrlDownloadError': "! Error: Downloading a RSS feed or Web page (%s).\n", }
150
151 # Channel details and search results
152 self.channel = {'channel_title': '', 'channel_link': 'http://www.mythtv.org/wiki/MythNetvision', 'channel_description': "Mashups combines media from multiple sources to create a new work", 'channel_numresults': 0, 'channel_returned': 0, 'channel_startindex': 0}
153
154 self.channel_icon = '%SHAREDIR%/mythnetvision/icons/mashups.png'
155
156 self.config['image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
157 # end __init__()
158
159
164
165 def setTreeViewIcon(self, dir_icon=None):
166 '''Check if there is a specific generic tree view icon. If not default to the channel icon.
167 return self.tree_dir_icon
168 '''
170 if not dir_icon:
171 if self.tree_key not in self.feed_icons:
172 return self.tree_dir_icon
173 if self.feed not in self.feed_icons[self.tree_key]:
174 return self.tree_dir_icon
175 if not dir_icon:
176 return self.tree_dir_icon
177 dir_icon = self.feed_icons[self.tree_key][self.feed]
178 if not dir_icon:
179 return self.tree_dir_icon
180 self.tree_dir_icon = '%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, )
181 return self.tree_dir_icon
182 # end setTreeViewIcon()
183
184
186 ''' Read the MNV Mashups grabber "mashups_config.xml" configuration file
187 return nothing
188 '''
189 # Read the grabber mashups_config.xml configuration file
190 url = '%s/nv_python_libs/configs/XML/mashups_config.xml' % (baseProcessingDir, )
191 if not os.path.isfile(url):
192 raise MashupsConfigFileError(self.error_messages['MashupsConfigFileError'] % (url, ))
193
194 if self.config['debug_enabled']:
195 print(url)
196 print()
197 try:
198 self.mashups_config = etree.parse(url)
199 except Exception as errormsg:
200 raise MashupsUrlError(self.error_messages['MashupsUrlError'] % (url, errormsg))
201 return
202 # end getMashupsConfig()
203
204
206 '''Read the mashups_config.xml and user preference xxxxxMashup.xml file.
207 If the xxxxxMashup.xml file does not exist then copy the default.
208 return nothing
209 '''
210 # Get mashups_config.xml
211 self.getMashupsConfig()
212
213 # Check if the mashups.xml file exists
214 fileName = '%s.xml' % self.mashup_title.replace('treeview', '').replace('search', '')
215 userPreferenceFile = '%s/%s' % (self.mashups_config.find('userPreferenceFile').text, fileName)
216 if userPreferenceFile[0] == '~':
217 self.mashups_config.find('userPreferenceFile').text = "%s%s" % (os.path.expanduser("~"), userPreferenceFile[1:])
218
219 # If the user config file does not exists then copy one from the default
220 create = False
221 defaultConfig = '%s/nv_python_libs/configs/XML/defaultUserPrefs/%s' % (baseProcessingDir, fileName, )
222 prefDir = self.mashups_config.find('userPreferenceFile').text.replace('/'+fileName, '')
223 if not os.path.isfile(self.mashups_config.find('userPreferenceFile').text):
224 # Make the necessary directories if they do not already exist
225 if not os.path.isdir(prefDir):
226 os.makedirs(prefDir)
227 shutil.copy2(defaultConfig, self.mashups_config.find('userPreferenceFile').text)
228 create = True
229
230 # Read the grabber mashups_config.xml configuration file
231 if self.config['debug_enabled']:
232 print(self.mashups_config.find('userPreferenceFile').text)
233 print()
234 try:
235 self.userPrefs = etree.parse(self.mashups_config.find('userPreferenceFile').text)
236 except Exception as errormsg:
237 raise MashupsUrlError(self.error_messages['MashupsUrlError'] % (self.mashups_config.find('userPreferenceFile').text, errormsg))
238
239 if create:
240 return
241
242 # Merge the existing entries with the user's current settings to get any distributed
243 # additions or changes
244 try:
245 defaultPrefs = etree.parse(defaultConfig)
246 except Exception as errormsg:
247 raise MashupsUrlError(self.error_messages['MashupsUrlError'] % (defaultConfig, errormsg))
248 urlFilter = etree.XPath('//sourceURL[@url=$url and @name=$name]', namespaces=self.common.namespaces)
249 globalmaxFilter = etree.XPath('./../..', namespaces=self.common.namespaces)
250 for sourceURL in self.userPrefs.xpath('//sourceURL'):
251 url = sourceURL.attrib['url']
252 name = sourceURL.attrib['name']
253 defaultSourceURL = urlFilter(defaultPrefs, url=url, name=name)
254 if len(defaultSourceURL):
255 defaultSourceURL[0].attrib['enabled'] = sourceURL.attrib['enabled']
256 if sourceURL.attrib.get('max'):
257 defaultSourceURL[0].attrib['max'] = sourceURL.attrib['max']
258 directory = globalmaxFilter(sourceURL)[0]
259 if directory.attrib.get('globalmax'):
260 defaultDir = directory.attrib.get('globalmax')
261 globalmaxFilter(defaultSourceURL[0])[0].attrib['globalmax'] = directory.attrib['globalmax']
262
263 # Save the updated xxxxMashup.xml file
264 tagName = defaultPrefs.getroot().tag
265
266 # Get the internal documentaion
267 docComment = ''
268 for element in defaultPrefs.iter(tag=etree.Comment):
269 docComment+=etree.tostring(element, encoding='UTF-8', pretty_print=True)[:-1]
270
271 fd = open(self.mashups_config.find('userPreferenceFile').text, 'w')
272 fd.write(('<%s>\n' % tagName)+docComment)
273 fd.write(''.join(etree.tostring(element, encoding='UTF-8', pretty_print=True) for element in defaultPrefs.xpath('/%s/*' % tagName))+('</%s>\n'% tagName))
274 fd.close()
275
276 # Make sure that the user preferences are using the latest version
277 self.userPrefs = defaultPrefs
278
279 return
280 # end getUserPreferences()
281
282
287
288 def searchForVideos(self, title, pagenumber):
289 """Common name for a video search. Used to interface with MythTV plugin NetVision
290 Display the results and exit
291 """
292 # Get the user preferences
293 try:
294 self.getUserPreferences()
295 except Exception as e:
296 sys.stderr.write('%s' % e)
297 sys.exit(1)
298
299 if self.config['debug_enabled']:
300 print("self.userPrefs:")
301 sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
302 print()
303
304 # Import the mnvsearch_api.py functions
305 fullPath = '%s/nv_python_libs/%s' % (self.common.baseProcessingDir, 'mnvsearch')
306 sys.path.append(fullPath)
307 try:
308 exec('''import mnvsearch_api''')
309 except Exception as errmsg:
310 sys.stderr.write('! Error: Dynamic import of mnvsearch_api functions\nmessage(%s)\n' % (errmsg))
311 sys.exit(1)
312 mnvsearch_api.common = self.common
313 mnvsearch = mnvsearch_api.Videos(None, debug=self.config['debug_enabled'], language=self.config['language'])
314 mnvsearch.page_limit = self.page_limit
315
316 # Get the dictionary of mashups functions pointers
317 self.common.buildFunctionDict()
318
319 # Massage channel icon
320 self.channel_icon = self.common.ampReplace(self.channel_icon)
321
322 # Create RSS element tree
323 rssTree = etree.XML(self.common.mnvRSS+'</rss>')
324
325 # Add the Channel element tree
326 self.channel['channel_title'] = self.grabber_title
327 self.channel_icon = self.setTreeViewIcon(dir_icon=self.mashup_title.replace('Mashuptreeview', ''))
328 channelTree = self.common.mnvChannelElement(self.channel)
329 rssTree.append(channelTree)
330
331 # Globally add all the xpath extentions to the "mythtv" namespace allowing access within the
332 # XSLT stylesheets
333 self.common.buildFunctionDict()
334 mnvXpath = etree.FunctionNamespace('http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format')
335 mnvXpath.prefix = 'mnvXpath'
336 for key in list(self.common.functionDict.keys()):
337 mnvXpath[key] = self.common.functionDict[key]
338
339 # Build Search parameter dictionary
340 self.common.pagenumber = pagenumber
341 self.common.page_limit = self.page_limit
342 self.common.language = self.config['language']
343 self.common.searchterm = title.encode("utf-8")
344 searchParms = {
345 'searchterm': urllib.parse.quote_plus(title.encode("utf-8")),
346 'pagemax': self.page_limit,
347 'language': self.config['language'],
348 }
349 # Create a structure of feeds that can be concurrently downloaded
350 xsltFilename = etree.XPath('./@xsltFile', namespaces=self.common.namespaces)
351 sourceData = etree.XML('<xml></xml>')
352 for source in self.userPrefs.xpath('//search//sourceURL[@enabled="true"]'):
353 if source.attrib.get('mnvsearch'):
354 continue
355 urlName = source.attrib.get('name')
356 if urlName:
357 uniqueName = '%s;%s' % (urlName, source.attrib.get('url'))
358 else:
359 uniqueName = 'RSS;%s' % (source.attrib.get('url'))
360 url = etree.XML('<url></url>')
361 etree.SubElement(url, "name").text = uniqueName
362 if source.attrib.get('pageFunction'):
363 searchParms['pagenum'] = self.common.functionDict[source.attrib['pageFunction']]('dummy', 'dummy')
364 else:
365 searchParms['pagenum'] = pagenumber
366 etree.SubElement(url, "href").text = source.attrib.get('url') % searchParms
367 if len(xsltFilename(source)):
368 for xsltName in xsltFilename(source):
369 etree.SubElement(url, "xslt").text = xsltName.strip()
370 etree.SubElement(url, "parserType").text = source.attrib.get('type')
371 sourceData.append(url)
372
373 if self.config['debug_enabled']:
374 print("rssData:")
375 sys.stdout.write(etree.tostring(sourceData, encoding='UTF-8', pretty_print=True))
376 print()
377
378 # Get the source data
379 if sourceData.find('url') is not None:
380 # Process each directory of the user preferences that have an enabled rss feed
381 try:
382 resultTree = self.common.getUrlData(sourceData)
383 except Exception as errormsg:
384 raise MashupsUrlDownloadError(self.error_messages['MashupsUrlDownloadError'] % (errormsg))
385 if self.config['debug_enabled']:
386 print("resultTree:")
387 sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
388 print()
389
390 # Process the results
391 itemFilter = etree.XPath('.//item', namespaces=self.common.namespaces)
392 # Create special directory elements
393 for result in resultTree.findall('results'):
394 channelTree.xpath('numresults')[0].text = self.common.numresults
395 channelTree.xpath('returned')[0].text = self.common.returned
396 channelTree.xpath('startindex')[0].text = self.common.startindex
397 for item in itemFilter(result):
398 channelTree.append(item)
399
400 # Process any mnvsearches
401 for source in self.userPrefs.xpath('//search//sourceURL[@enabled="true" and @mnvsearch]'):
402 results = mnvsearch.searchForVideos(title, pagenumber, feedtitle=source.xpath('./@mnvsearch')[0])
403 if len(list(results[0].keys())):
404 channelTree.xpath('returned')[0].text = '%s' % (int(channelTree.xpath('returned')[0].text)+results[1])
405 channelTree.xpath('startindex')[0].text = '%s' % (int(channelTree.xpath('startindex')[0].text)+results[2])
406 channelTree.xpath('numresults')[0].text = '%s' % (int(channelTree.xpath('numresults')[0].text)+results[3])
407 lastKey = None
408 for key in sorted(results[0].keys()):
409 if lastKey != key:
410 channelTree.append(results[0][key])
411 lastKey = key
412
413 # Check that there was at least some items
414 if len(rssTree.xpath('//item')):
415 # Output the MNV Mashup results
416 sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
417 sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
418
419 sys.exit(0)
420 # end searchForVideos()
421
422
424 '''Gather the Mashups Internet sources then get the videos meta data in each of them
425 Display the results and exit
426 '''
427 # Get the user preferences
428 try:
429 self.getUserPreferences()
430 except Exception as e:
431 sys.stderr.write('%s' % e)
432 sys.exit(1)
433
434 if self.config['debug_enabled']:
435 print("self.userPrefs:")
436 sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
437 print()
438
439 # Massage channel icon
440 self.channel_icon = self.common.ampReplace(self.channel_icon)
441
442 # Create RSS element tree
443 rssTree = etree.XML(self.common.mnvRSS+'</rss>')
444
445 # Add the Channel element tree
446 self.channel['channel_title'] = self.grabber_title
447 self.channel_icon = self.setTreeViewIcon(dir_icon=self.mashup_title.replace('Mashuptreeview', ''))
448 channelTree = self.common.mnvChannelElement(self.channel)
449 rssTree.append(channelTree)
450 self.common.language = self.config['language']
451
452 # Globally add all the xpath extentions to the "mythtv" namespace allowing access within the
453 # XSLT stylesheets
454 self.common.buildFunctionDict()
455 mnvXpath = etree.FunctionNamespace('http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format')
456 mnvXpath.prefix = 'mnvXpath'
457 for key in list(self.common.functionDict.keys()):
458 mnvXpath[key] = common.functionDict[key]
459
460 # Create a structure of feeds that can be concurrently downloaded
461 xsltFilename = etree.XPath('./@xsltFile', namespaces=self.common.namespaces)
462 sourceData = etree.XML('<xml></xml>')
463 for source in self.userPrefs.xpath('//directory//sourceURL[@enabled="true"]'):
464 urlName = source.attrib.get('name')
465 if urlName:
466 uniqueName = '%s;%s' % (urlName, source.attrib.get('url'))
467 else:
468 uniqueName = 'RSS;%s' % (source.attrib.get('url'))
469 url = etree.XML('<url></url>')
470 etree.SubElement(url, "name").text = uniqueName
471 etree.SubElement(url, "href").text = source.attrib.get('url')
472 if source.attrib.get('parameter') is not None:
473 etree.SubElement(url, "parameter").text = source.attrib.get('parameter')
474 if len(xsltFilename(source)):
475 for xsltName in xsltFilename(source):
476 etree.SubElement(url, "xslt").text = xsltName.strip()
477 etree.SubElement(url, "parserType").text = source.attrib.get('type')
478 sourceData.append(url)
479
480 if self.config['debug_enabled']:
481 print("rssData:")
482 sys.stdout.write(etree.tostring(sourceData, encoding='UTF-8', pretty_print=True))
483 print()
484
485 # Get the source data
486 if sourceData.find('url') is not None:
487 # Process each directory of the user preferences that have an enabled rss feed
488 try:
489 resultTree = self.common.getUrlData(sourceData)
490 except Exception as errormsg:
491 raise MashupsUrlDownloadError(self.error_messages['MashupsUrlDownloadError'] % (errormsg))
492 if self.config['debug_enabled']:
493 print("resultTree:")
494 sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
495 print()
496 # Process the results
497 categoryDir = None
498 categoryElement = None
499 xsltShowName = etree.XPath('//directory//sourceURL[@url=$url]/../@name', namespaces=self.common.namespaces)
500 channelThumbnail = etree.XPath('.//directoryThumbnail', namespaces=self.common.namespaces)
501 directoryFilter = etree.XPath('.//directory', namespaces=self.common.namespaces)
502 itemFilter = etree.XPath('.//item', namespaces=self.common.namespaces)
503 feedFilter = etree.XPath('//directory//sourceURL[@url=$url]')
504 channelThumbnail = etree.XPath('.//directoryThumbnail', namespaces=self.common.namespaces)
505 specialDirectoriesFilter = etree.XPath('.//specialDirectories')
506 specialDirectoriesKeyFilter = etree.XPath('.//specialDirectories/*[name()=$name]/@key')
507 specialDirectoriesDict = {}
508 # Create special directory elements
509 for result in resultTree.findall('results'):
510 if len(specialDirectoriesFilter(result)):
511 for element in specialDirectoriesFilter(result)[0]:
512 if not element.tag in list(specialDirectoriesDict.keys()):
513 specialDirectoriesElement = etree.XML('<directory></directory>')
514 specialDirectoriesElement.attrib['name'] = element.attrib['dirname']
515 if element.attrib.get('count'):
516 count = int(element.attrib['count'])
517 else:
518 count = 1
519 if element.attrib.get('thumbnail'):
520 specialDirectoriesElement.attrib['thumbnail'] = element.attrib['thumbnail']
521 else:
522 specialDirectoriesElement.attrib['thumbnail'] = self.channel_icon
523 specialDirectoriesDict[element.tag] = [specialDirectoriesElement, element.attrib['reverse'], count]
524 for result in resultTree.findall('results'):
525 names = result.find('name').text.split(';')
526 names[0] = self.common.massageText(names[0])
527 if len(xsltShowName(self.userPrefs, url=names[1])):
528 names[0] = self.common.massageText(xsltShowName(self.userPrefs, url=names[1])[0].strip())
529 if names[0] == 'RSS':
530 names[0] = self.common.massageText(rssName(result.find('result'))[0].text)
531 count = 0
532 urlMax = None
533 url = feedFilter(self.userPrefs, url=names[1])
534 if len(url):
535 if url[0].attrib.get('max'):
536 try:
537 urlMax = int(url[0].attrib.get('max'))
538 except:
539 pass
540 elif url[0].getparent().getparent().attrib.get('globalmax'):
541 try:
542 urlMax = int(url[0].getparent().getparent().attrib.get('globalmax'))
543 except:
544 pass
545 if urlMax == 0:
546 urlMax = None
547
548 # Create a new directory and/or subdirectory if required
549 if names[0] != categoryDir:
550 if categoryDir is not None:
551 channelTree.append(categoryElement)
552 categoryElement = etree.XML('<directory></directory>')
553 categoryElement.attrib['name'] = names[0]
554 if len(channelThumbnail(result)):
555 categoryElement.attrib['thumbnail'] = channelThumbnail(result)[0].text
556 else:
557 categoryElement.attrib['thumbnail'] = self.channel_icon
558 categoryDir = names[0]
559
560 # Add the special directories videos
561 for key in list(specialDirectoriesDict.keys()):
562 sortDict = {}
563 count = 0
564 for sortData in specialDirectoriesKeyFilter(result, name=key):
565 sortDict[sortData] = count
566 count+=1
567 if len(sortDict):
568 if specialDirectoriesDict[key][1] == 'true':
569 sortedKeys = sorted(list(sortDict.keys()), reverse=True)
570 else:
571 sortedKeys = sorted(list(sortDict.keys()), reverse=False)
572 if specialDirectoriesDict[key][2] == 0:
573 number = len(sortDict)
574 else:
575 number = specialDirectoriesDict[key][2]
576 for count in range(number):
577 if count == len(sortDict):
578 break
579 specialDirectoriesDict[key][0].append(deepcopy(itemFilter(result)[sortDict[sortedKeys[count]]]))
580
581 if len(directoryFilter(result)):
582 for directory in directoryFilter(result):
583 if not len(itemFilter(directory)):
584 continue
585 tmpDirElement = etree.XML('<directory></directory>')
586 tmpDirElement.attrib['name'] = directory.attrib['name']
587 if directory.attrib.get('thumbnail'):
588 tmpDirElement.attrib['thumbnail'] = directory.attrib['thumbnail']
589 else:
590 tmpDirElement.attrib['thumbnail'] = self.channel_icon
591 count = 0
592 for item in itemFilter(directory):
593 tmpDirElement.append(item)
594 if urlMax:
595 count+=1
596 if count == urlMax:
597 break
598 categoryElement.append(tmpDirElement)
599 else:
600 # Process the results through its XSLT stylesheet and append the results to the
601 # directory date and time order
602 count = 0
603 for item in itemFilter(result):
604 categoryElement.append(item)
605 if urlMax:
606 count+=1
607 if count == urlMax:
608 break
609
610 # Add the last directory processed and the "Special" directories
611 if categoryElement is not None:
612 if len(itemFilter(categoryElement)):
613 channelTree.append(categoryElement)
614 # Add the special directories videos
615 for key in list(specialDirectoriesDict.keys()):
616 if len(itemFilter(specialDirectoriesDict[key][0])):
617 channelTree.append(specialDirectoriesDict[key][0])
618
619 # Check that there was at least some items
620 if len(rssTree.xpath('//item')):
621 # Output the MNV Mashup results
622 sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
623 sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
624
625 sys.exit(0)
626 # end displayTreeView()
627# end Videos() class
def __init__(self, outstream, encoding=None)
Definition: mashups_api.py:41
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
Definition: mashups_api.py:88
def setTreeViewIcon(self, dir_icon=None)
Start - Utility functions.
Definition: mashups_api.py:165
def searchForVideos(self, title, pagenumber)
End of Utility functions.
Definition: mashups_api.py:288
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)