MythTV master
mtv_api.py
Go to the documentation of this file.
1# -*- coding: UTF-8 -*-
2
3# ----------------------
4# Name: mtv_api - Simple-to-use Python interface to the MTV API (http://www.mtv.com/)
5# Python Script
6# Author: R.D. Vaughan
7# Purpose: This python script is intended to perform a variety of utility functions to search and access text
8# metadata, video and image URLs from MTV. These routines are based on the api. Specifications
9# for this api are published at http://developer.mtvnservices.com/docs
10#
11# License:Creative Commons GNU GPL v2
12# (http://creativecommons.org/licenses/GPL/2.0/)
13#-------------------------------------
14__title__ ="mtv_api - Simple-to-use Python interface to the MTV API (http://developer.mtvnservices.com/docs)"
15__author__="R.D. Vaughan"
16__purpose__='''
17This python script is intended to perform a variety of utility functions to search and access text
18metadata, video and image URLs from MTV. These routines are based on the api. Specifications
19for this api are published at http://developer.mtvnservices.com/docs
20'''
21
22__version__="v0.2.5"
23# 0.1.0 Initial development
24# 0.1.1 Added Tree View Processing
25# 0.1.2 Modified Reee view code and structure to be standandized across all grabbers
26# 0.1.3 Added directory image access and display
27# 0.1.4 Documentation review
28# 0.2.0 Public release
29# 0.2.1 New python bindings conversion
30# Better exception error reporting
31# Better handling of invalid unicode data from source
32# 0.2.2 Complete abort error message display improvements
33# Removed the import and use of the feedparser library
34# 0.2.3 Fixed an exception message output code error in two places
35# 0.2.4 Removed the need for python MythTV bindings and added "%SHAREDIR%" to icon directory path
36# 0.2.5 Use MTV web page as API not returning valid URLs
37
38import os, struct, sys, re, time
39from datetime import datetime, timedelta
40import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse
41import logging
42
43try:
44 import xml.etree.cElementTree as ElementTree
45except ImportError:
46 import xml.etree.ElementTree as ElementTree
47
48from .mtv_exceptions import (MtvUrlError, MtvHttpError, MtvRssError, MtvVideoNotFound, MtvInvalidSearchType, MtvXmlError, MtvVideoDetailError)
49import io
50
51class OutStreamEncoder(object):
52 """Wraps a stream with an encoder"""
53 def __init__(self, outstream, encoding=None):
54 self.out = outstream
55 if not encoding:
56 self.encoding = sys.getfilesystemencoding()
57 else:
58 self.encoding = encoding
59
60 def write(self, obj):
61 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
62 if isinstance(obj, str):
63 obj = obj.encode(self.encoding)
64 self.out.buffer.write(obj)
65
66 def __getattr__(self, attr):
67 """Delegate everything but write to the stream"""
68 return getattr(self.out, attr)
69
70if isinstance(sys.stdout, io.TextIOWrapper):
71 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
72 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
73
74
76 """Deals with retrieval of XML files from API
77 """
78 def __init__(self, url):
79 self.url = url
80
81 def _grabUrl(self, url):
82 try:
83 urlhandle = urllib.request.urlopen(url)
84 except IOError as errormsg:
85 raise MtvHttpError(errormsg)
86 return urlhandle.read()
87
88 def getEt(self):
89 xml = self._grabUrl(self.url)
90 try:
91 et = ElementTree.fromstring(xml)
92 except SyntaxError as errormsg:
93 raise MtvXmlError(errormsg)
94 return et
95
96
97class Videos(object):
98 """Main interface to http://www.mtv.com/
99 This is done to support a common naming framework for all python Netvision plugins no matter their site
100 target.
101
102 Supports search methods
103 The apikey is a not required to access http://www.mtv.com/
104 """
105 def __init__(self,
106 apikey,
107 mythtv = True,
108 interactive = False,
109 select_first = False,
110 debug = False,
111 custom_ui = None,
112 language = None,
113 search_all_languages = False,
114 ):
115 """apikey (str/unicode):
116 Specify the target site API key. Applications need their own key in some cases
117
118 mythtv (True/False):
119 When True, the returned meta data is being returned has the key and values massaged to match MythTV
120 When False, the returned meta data is being returned matches what target site returned
121
122 interactive (True/False): (This option is not supported by all target site apis)
123 When True, uses built-in console UI is used to select the correct show.
124 When False, the first search result is used.
125
126 select_first (True/False): (This option is not supported currently implemented in any grabbers)
127 Automatically selects the first series search result (rather
128 than showing the user a list of more than one series).
129 Is overridden by interactive = False, or specifying a custom_ui
130
131 debug (True/False):
132 shows verbose debugging information
133
134 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
135 A callable subclass of interactive class (overrides interactive option)
136
137 language (2 character language abbreviation): (This option is not supported by all target site apis)
138 The language of the returned data. Is also the language search
139 uses. Default is "en" (English). For full list, run..
140
141 search_all_languages (True/False): (This option is not supported by all target site apis)
142 By default, a Netvision grabber will only search in the language specified using
143 the language option. When this is True, it will search for the
144 show in any language
145
146 """
147 self.config = {}
148
149 if apikey is not None:
150 self.config['apikey'] = apikey
151 else:
152 pass # MTV does not require an apikey
153
154 self.config['debug_enabled'] = debug # show debugging messages
155
156 self.log_name = "MTV"
157 self.log = self._initLogger() # Setups the logger (self.log.debug() etc)
158
159 self.config['custom_ui'] = custom_ui
160
161 self.config['interactive'] = interactive # prompt for correct series?
162
163 self.config['select_first'] = select_first
164
165 self.config['search_all_languages'] = search_all_languages
166
167 # Defaulting to ENGISH but the MTV apis do not support specifying a language
168 self.config['language'] = "en"
169
170 self.error_messages = {'MtvUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'MtvHttpError': "! Error: An HTTP communications error with MTV was raised (%s)\n", 'MtvRssError': "! Error: Invalid RSS metadata\nwas received from MTV error (%s). Skipping item.\n", 'MtvVideoNotFound': "! Error: Video search with MTV did not return any results (%s)\n", 'MtvVideoDetailError': "! Error: Invalid Video metadata detail\nwas received from MTV error (%s). Skipping item.\n", }
171
172 # This is an example that must be customized for each target site
173 self.key_translation = [{'channel_title': 'channel_title', 'channel_link': 'channel_link', 'channel_description': 'channel_description', 'channel_numresults': 'channel_numresults', 'channel_returned': 'channel_returned', 'channel_startindex': 'channel_startindex'}, {'title': 'item_title', 'media_credit': 'item_author', 'published_parsed': 'item_pubdate', 'media_description': 'item_description', 'video': 'item_link', 'thumbnail': 'item_thumbnail', 'link': 'item_url', 'duration': 'item_duration', 'item_rating': 'item_rating', 'item_width': 'item_width', 'item_height': 'item_height', 'language': 'item_lang'}]
174
175 self.config['urls'] = {}
176
177 self.config['image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
178 self.config['urls'] = {}
179
180 # Functions that parse video data from RSS data
181 self.config['item_parser'] = {}
182 self.config['item_parser']['main'] = self.getVideosForURL
183
184 # v2 api calls - An example that must be customized for each target site
185 self.config['urls']['tree.view'] = {
186 'new_genres': {
187 '__all__': ['http://api.mtvnservices.com/1/genre/%s/videos/?', 'main'],
188 },
189 'genres': {
190 '__all__': ['http://api.mtvnservices.com/1/genre/%s/videos/?', 'main'],
191 },
192 }
193
194 self.tree_order = ['new_genres', 'genres', ]
195 self.tree_org = {
196 'new_genres': [['New over the last 3 months', ['pop', 'rock', 'metal', 'randb', 'jazz', 'blues_folk', 'country', 'latin', 'hip_hop', 'world_reggae', 'electronic_dance', 'easy_listening', 'classical', 'soundtracks_musicals', 'alternative', 'environmental', ]],
197 ],
198 'genres': [['All Genres', ['pop', 'rock', 'metal', 'randb', 'jazz', 'blues_folk', 'country', 'latin', 'hip_hop', 'world_reggae', 'electronic_dance', 'easy_listening', 'classical', 'soundtracks_musicals', 'alternative', 'environmental', ]],
199 ],
200 }
201
202 # Time periods of videos e.g, &date=01011980-12311989 or MMDDYYYY-MMDDYYYY
203 d1 = datetime.now()
204 yr = d1 - timedelta(weeks=52)
205 mts = d1 - timedelta(days=93)
206 last_3_months = '%s-%s' % (mts.strftime('%m%d%Y'), d1.strftime('%m%d%Y'))
207 last_year = '%s-%s' % (yr.strftime('%m%d%Y'), d1.strftime('%m%d%Y'))
208
209 # http://api.mtvnservices.com/1/genre/rock/videos/?&max-results=20&start-index=1
211 'new_genres': { # ?term=%s&start-index=%s&max-results=%s
212 '__default__': {'max-results': '20', 'start-index': '1', 'date': last_3_months, 'sort': 'date_descending'},
213 #'cat name': {'max-results': '', 'start-index': '', 'date': '', 'sort': ''},
214 },
215 'genres': { # ?term=%s&start-index=%s&max-results=%s
216 '__default__': {'max-results': '20', 'start-index': '1', 'sort': 'date_descending'},
217 #'cat name': {'max-results': '', 'start-index': '', 'date': '', 'sort': ''},
218 'rock': {'date': last_year, },
219 'R&B': {'date': last_year, },
220 'country': {'date': last_year, },
221 'hip_hop': {'date': last_year, },
222 },
223 }
224
225 self.feed_names = {
226 'new_genres': {'world_reggae': 'World/Reggae', 'pop': 'Pop', 'metal': 'Metal', 'environmental': 'Environmental', 'latin': 'Latin', 'randb': 'R&B', 'rock': 'Rock', 'easy_listening': 'Easy Listening', 'jazz': 'Jazz', 'country': 'Country', 'hip_hop': 'Hip-Hop', 'classical': 'Classical', 'electronic_dance': 'Electro / Dance', 'blues_folk': 'Blues / Folk', 'alternative': 'Alternative', 'soundtracks_musicals': 'Soundtracks / Musicals', 'New over the last 3 months': 'directories/topics/month'
227 },
228 'genres': {'world_reggae': 'World/Reggae', 'pop': 'Pop', 'metal': 'Metal', 'environmental': 'Environmental', 'latin': 'Latin', 'randb': 'R&B', 'rock': 'Rock', 'easy_listening': 'Easy Listening', 'jazz': 'Jazz', 'country': 'Country', 'hip_hop': 'Hip-Hop', 'classical': 'Classical', 'electronic_dance': 'Electro / Dance', 'blues_folk': 'Blues / Folk', 'alternative': 'Alternative', 'soundtracks_musicals': 'Soundtracks / Musicals',
229 },
230 }
231
232 self.feed_icons = {
233 'new_genres': {'New over the last 3 months': 'directories/topics/recent', 'world_reggae': 'directories/music_genres/world_reggae', 'pop': 'directories/music_genres/pop', 'metal': 'directories/music_genres/metal', 'environmental': 'directories/music_genres/environmental', 'latin': 'directories/music_genres/latino', 'randb': 'directories/music_genres/rnb', 'rock': 'directories/music_genres/rock', 'easy_listening': 'directories/music_genres/easy_listening', 'jazz': 'directories/music_genres/jazz', 'country': 'directories/music_genres/country', 'hip_hop': 'directories/music_genres/hiphop', 'classical': 'directories/music_genres/classical', 'electronic_dance': 'directories/music_genres/electronic_dance', 'blues_folk': 'directories/music_genres/blues_folk', 'alternative': 'directories/music_genres/alternative', 'soundtracks_musicals': 'directories/music_genres/soundtracks_musicals',
234 },
235 'genres': {'Genres': 'directories/topics/music','world_reggae': 'directories/music_genres/world_reggae', 'pop': 'directories/music_genres/pop', 'metal': 'directories/music_genres/metal', 'environmental': 'directories/music_genres/environmental', 'latin': 'directories/music_genres/latino', 'randb': 'directories/music_genres/rnb', 'rock': 'directories/music_genres/rock', 'easy_listening': 'directories/music_genres/easy_listening', 'jazz': 'directories/music_genres/jazz', 'country': 'directories/music_genres/country', 'hip_hop': 'directories/music_genres/hiphop', 'classical': 'directories/music_genres/classical', 'electronic_dance': 'directories/music_genres/electronic_dance', 'blues_folk': 'directories/music_genres/blues_folk', 'alternative': 'directories/music_genres/alternative', 'soundtracks_musicals': 'directories/music_genres/soundtracks_musicals',
236 },
237 }
238 # Get the absolute path to the mtv.html file
239 self.mtvHtmlPath = 'file://'+os.path.dirname( os.path.realpath( __file__ )).replace('/nv_python_libs/mtv', '/nv_python_libs/configs/HTML/mtv.html?title=%s&videocode=%s')
240
241 # Initialize the tree view flag so that the item parsing code can be used for multiple purposes
242 self.treeview = False
243 self.channel_icon = '%SHAREDIR%/mythnetvision/icons/mtv.png'
244 # end __init__()
245
246
251
252 def massageDescription(self, text):
253 '''Removes HTML markup from a text string.
254 @param text The HTML source.
255 @return The plain text. If the HTML source contains non-ASCII
256 entities or character references, this is a Unicode string.
257 '''
258 def fixup(m):
259 text = m.group(0)
260 if text[:1] == "<":
261 return "" # ignore tags
262 if text[:2] == "&#":
263 try:
264 if text[:3] == "&#x":
265 return chr(int(text[3:-1], 16))
266 else:
267 return chr(int(text[2:-1]))
268 except ValueError:
269 pass
270 elif text[:1] == "&":
271 import html.entities
272 entity = html.entities.entitydefs.get(text[1:-1])
273 if entity:
274 if entity[:2] == "&#":
275 try:
276 return chr(int(entity[2:-1]))
277 except ValueError:
278 pass
279 else:
280 return str(entity, "iso-8859-1")
281 return text # leave as is
282 return self.ampReplace(re.sub(r"(?s)<[^>]*>|&#?\w+;", fixup, self.textUtf8(text))).replace('\n',' ')
283 # end massageDescription()
284
285
286 def _initLogger(self):
287 """Setups a logger using the logging module, returns a log object
288 """
289 logger = logging.getLogger(self.log_name)
290 formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s')
291
292 hdlr = logging.StreamHandler(sys.stdout)
293
294 hdlr.setFormatter(formatter)
295 logger.addHandler(hdlr)
296
297 if self.config['debug_enabled']:
298 logger.setLevel(logging.DEBUG)
299 else:
300 logger.setLevel(logging.WARNING)
301 return logger
302 #end initLogger
303
304
305 def textUtf8(self, text):
306 if text is None:
307 return text
308 try:
309 return str(text, 'utf8')
310 except UnicodeDecodeError:
311 return ''
312 except (UnicodeEncodeError, TypeError):
313 return text
314 # end textUtf8()
315
316
317 def ampReplace(self, text):
318 '''Replace all "&" characters with "&amp;"
319 '''
320 text = self.textUtf8(text)
321 return text.replace('&amp;','~~~~~').replace('&','&amp;').replace('~~~~~', '&amp;')
322 # end ampReplace()
323
324
325 def setTreeViewIcon(self, dir_icon=None):
326 '''Check if there is a specific generic tree view icon. If not default to the channel icon.
327 return self.tree_dir_icon
328 '''
330 if not dir_icon:
331 if self.tree_key not in self.feed_icons:
332 return self.tree_dir_icon
333 if self.feed not in self.feed_icons[self.tree_key]:
334 return self.tree_dir_icon
335 dir_icon = self.feed_icons[self.tree_key][self.feed]
336 if not dir_icon:
337 return self.tree_dir_icon
338 self.tree_dir_icon = '%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, )
339 return self.tree_dir_icon
340 # end setTreeViewIcon()
341
342
347
348
349 def searchTitle(self, title, pagenumber, pagelen):
350 '''Key word video search of the MTV web site
351 return an array of matching item dictionaries
352 return
353 '''
354 url = self.config['urls']['video.search'] % (urllib.parse.quote_plus(title.encode("utf-8")), pagenumber , pagelen,)
355 if self.config['debug_enabled']:
356 print(url)
357 print()
358
359 try:
360 etree = XmlHandler(url).getEt()
361 except Exception as errormsg:
362 raise MtvUrlError(self.error_messages['MtvUrlError'] % (url, errormsg))
363
364 if etree is None:
365 raise MtvVideoNotFound("No MTV Video matches found for search value (%s)" % title)
366
367 data = []
368 for entry in etree:
369 if not entry.tag.endswith('entry'):
370 continue
371 item = {}
372 for parts in entry:
373 if parts.tag.endswith('id'):
374 item['id'] = parts.text
375 if parts.tag.endswith('title'):
376 item['title'] = parts.text
377 if parts.tag.endswith('author'):
378 for e in parts:
379 if e.tag.endswith('name'):
380 item['media_credit'] = e.text
381 break
382 if parts.tag.endswith('published'):
383 item['published_parsed'] = parts.text
384 if parts.tag.endswith('description'):
385 item['media_description'] = parts.text
386 data.append(item)
387
388 # Make sure there are no item elements that are None
389 for item in data:
390 for key in list(item.keys()):
391 if item[key] is None:
392 item[key] = ''
393
394 # Massage each field and eliminate any item without a URL
395 elements_final = []
396 for item in data:
397 if not 'id' in list(item.keys()):
398 continue
399
400 video_details = None
401 try:
402 video_details = self.videoDetails(item['id'], urllib.parse.quote(item['title'].encode("utf-8")))
403 except MtvUrlError as msg:
404 sys.stderr.write(self.error_messages['MtvUrlError'] % msg)
405 except MtvVideoDetailError as msg:
406 sys.stderr.write(self.error_messages['MtvVideoDetailError'] % msg)
407 except Exception as e:
408 sys.stderr.write("! Error: Unknown error while retrieving a Video's meta data. Skipping video.' (%s)\nError(%s)\n" % (title, e))
409
410 if video_details:
411 for key in list(video_details.keys()):
412 item[key] = video_details[key]
413
414 item['language'] = ''
415 for key in list(item.keys()):
416 if key == 'content':
417 if len(item[key]):
418 if 'language' in item[key][0]:
419 if item[key][0]['language'] is not None:
420 item['language'] = item[key][0]['language']
421 if key == 'published_parsed': # '2009-12-21T00:00:00Z'
422 if item[key]:
423 pub_time = time.strptime(item[key].strip(), "%Y-%m-%dT%H:%M:%SZ")
424 item[key] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', pub_time)
425 continue
426 if key == 'media_description' or key == 'title':
427 # Strip the HTML tags
428 if item[key]:
429 item[key] = self.massageDescription(item[key].strip())
430 item[key] = item[key].replace('|', '-')
431 continue
432 if type(item[key]) == type(''):
433 if item[key]:
434 item[key] = item[key].replace('"\n',' ').strip()
435 elements_final.append(item)
436
437 if not len(elements_final):
438 raise MtvVideoNotFound("No MTV Video matches found for search value (%s)" % title)
439
440 return elements_final
441 # end searchTitle()
442
443
444 def videoDetails(self, url, title=''):
445 '''Using the passed URL retrieve the video meta data details
446 return a dictionary of video metadata details
447 return
448 '''
449 if self.config['debug_enabled']:
450 print(url)
451 print()
452
453 try:
454 etree = XmlHandler(url).getEt()
455 except Exception as errormsg:
456 raise MtvUrlError(self.error_messages['MtvUrlError'] % (url, errormsg))
457
458 if etree is None:
459 raise MtvVideoDetailError('1-No Video meta data for (%s)' % url)
460
461 metadata = {}
462 cur_size = True
463 for e in etree:
464 if e.tag.endswith('content') and e.text is None:
465 index = e.get('url').rindex(':')
466 metadata['video'] = self.mtvHtmlPath % (title, e.get('url')[index+1:])
467 # !! This tag will need to be added at a later date
468# metadata['customhtml'] = u'true'
469 metadata['duration'] = e.get('duration')
470 if e.tag.endswith('player'):
471 metadata['link'] = e.get('url')
472 if e.tag.endswith('thumbnail'):
473 if cur_size == False:
474 continue
475 height = e.get('height')
476 width = e.get('width')
477 if int(width) > cur_size:
478 metadata['thumbnail'] = e.get('url')
479 cur_size = int(width)
480 if int(width) >= 200:
481 cur_size = False
482 break
483
484 if not len(metadata):
485 raise MtvVideoDetailError('2-No Video meta data for (%s)' % url)
486
487 if 'video' not in metadata:
488 metadata['video'] = metadata['link']
489 metadata['duration'] = ''
490 else:
491 metadata['link'] = metadata['video']
492
493 return metadata
494 # end videoDetails()
495
496
497 def searchForVideos(self, title, pagenumber):
498 """Common name for a video search. Used to interface with MythTV plugin NetVision
499 """
500 # v2 api calls - An example that must be customized for each target site
501 if self.grabber_title == 'MTV':
502 self.config['urls']['video.search'] = "http://api.mtvnservices.com/1/video/search/?term=%s&start-index=%s&max-results=%s"
503 elif self.grabber_title == 'MTV Artists': # This search type is not currently implemented
504 self.config['urls']['video.search'] = "http://api.mtvnservices.com/1/artist/search/?term=%s&start-index=%s&max-results=%s"
505 else:
506 sys.stderr.write("! Error: MtvInvalidSearchType - The grabber name (%s) is invalid \n" % self.grabber_title)
507 sys.exit(1)
508
509
510 # Easier for debugging
511# print self.searchTitle(title, pagenumber, self.page_limit)
512# print
513# sys.exit()
514
515
516 startindex = (int(pagenumber) -1) * self.page_limit + 1
517 try:
518 data = self.searchTitle(title, startindex, self.page_limit)
519 except MtvVideoNotFound as msg:
520 sys.stderr.write("%s\n" % msg)
521 return None
522 except MtvUrlError as msg:
523 sys.stderr.write('%s\n' % msg)
524 sys.exit(1)
525 except MtvHttpError as msg:
526 sys.stderr.write(self.error_messages['MtvHttpError'] % msg)
527 sys.exit(1)
528 except MtvRssError as msg:
529 sys.stderr.write(self.error_messages['MtvRssError'] % msg)
530 sys.exit(1)
531 except Exception as e:
532 sys.stderr.write("! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
533 sys.exit(1)
534
535 if data is None:
536 return None
537 if not len(data):
538 return None
539
540 items = []
541 for match in data:
542 item_data = {}
543 for key in list(self.key_translation[1].keys()):
544 if key in list(match.keys()):
545 item_data[self.key_translation[1][key]] = match[key]
546 else:
547 item_data[self.key_translation[1][key]] = ''
548 items.append(item_data)
549
550 # Channel details and search results
551 channel = {'channel_title': 'MTV', 'channel_link': 'http://www.mtv.com', 'channel_description': "Visit MTV (Music Television) for TV shows, music videos, celebrity photos, news.", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0}
552
553 if len(items) == self.page_limit:
554 channel['channel_numresults'] = self.page_limit * int(pagenumber) + 1
555 elif len(items) < self.page_limit:
556 channel['channel_numresults'] = self.page_limit * (int(pagenumber)-1) + len(items)
557 else:
558 channel['channel_numresults'] = self.page_limit * int(pagenumber)
559 channel['channel_startindex'] = self.page_limit * int(pagenumber)
560 channel['channel_returned'] = len(items)
561
562 if len(items):
563 return [[channel, items]]
564 return None
565 # end searchForVideos()
566
567
569 '''Gather the MTV Genres/Artists/...etc then get a max page of videos meta data in each of them
570 return array of directories and their video metadata
571 '''
572 # Channel details and search results
573 self.channel = {'channel_title': 'MTV', 'channel_link': 'http://www.mtv.com', 'channel_description': "Visit MTV (Music Television) for TV shows, music videos, celebrity photos, news.", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0}
574
575 if self.config['debug_enabled']:
576 print(self.config['urls'])
577 print()
578
579 # Set the default videos per page limit for all feeds/categories/... etc
580 for key in list(self.tree_customize.keys()):
581 if '__default__' in list(self.tree_customize[key].keys()):
582 if 'max-results' in list(self.tree_customize[key]['__default__'].keys()):
583 self.tree_customize[key]['__default__']['max-results'] = str(self.page_limit)
584
585 # Get videos within each category
586 dictionaries = []
587
588 # Process the various video feeds/categories/... etc
589 for key in self.tree_order:
590 self.tree_key = key
591 dictionaries = self.getVideos(self.tree_org[key], dictionaries)
592
593 return [[self.channel, dictionaries]]
594 # end displayTreeView()
595
596 def makeURL(self, URL):
597 '''Form a URL to search for videos
598 return a URL
599 '''
600 additions = dict(self.tree_customize[self.tree_key]['__default__']) # Set defaults
601
602 # Add customizations
603 if self.feed in list(self.tree_customize[self.tree_key].keys()):
604 for element in list(self.tree_customize[self.tree_key][self.feed].keys()):
605 additions[element] = self.tree_customize[self.tree_key][self.feed][element]
606
607 # Make the search extension string that is added to the URL
608 addition = ''
609 for ky in list(additions.keys()):
610 if ky.startswith('add_'):
611 addition+='/%s' % additions[ky]
612 else:
613 addition+='&%s=%s' % (ky, additions[ky])
614 index = URL.find('%')
615 if index == -1:
616 return (URL+addition)
617 else:
618 return (URL+addition) % self.feed
619 # end makeURL()
620
621
622 def getVideos(self, dir_dict, dictionaries):
623 '''Parse a list made of genres/artists ... etc lists and retrieve video meta data
624 return a dictionary of directory names and categories video metadata
625 '''
626 for sets in dir_dict:
627 if not isinstance(sets[1], list):
628 if sets[0] != '': # Add the nested dictionaries display name
629 try:
630 dictionaries.append([self.massageDescription(sets[0]), self.setTreeViewIcon(self.feed_icons[self.tree_key][sets[0]])])
631 except KeyError:
632 dictionaries.append([self.massageDescription(sets[0]), self.channel_icon])
633 else:
634 dictionaries.append(['', '']) # Add the nested dictionary indicator
635 continue
636 temp_dictionary = []
637 for self.feed in sets[1]:
638 if '__all__' in self.config['urls']['tree.view'][self.tree_key]:
639 URL = self.config['urls']['tree.view'][self.tree_key]['__all__']
640 else:
641 URL = self.config['urls']['tree.view'][self.tree_key][self.feed]
642 temp_dictionary = self.config['item_parser'][URL[1]](self.makeURL(URL[0]), temp_dictionary)
643 if len(temp_dictionary):
644 if len(sets[0]): # Add the nested dictionaries display name
645 try:
646 dictionaries.append([self.massageDescription(sets[0]), self.setTreeViewIcon(self.feed_icons[self.tree_key][sets[0]])])
647 except KeyError:
648 dictionaries.append([self.massageDescription(sets[0]), self.channel_icon])
649 for element in temp_dictionary:
650 dictionaries.append(element)
651 if len(sets[0]):
652 dictionaries.append(['', '']) # Add the nested dictionary indicator
653 return dictionaries
654 # end getVideos()
655
656
657 def getVideosForURL(self, url, dictionaries):
658 '''Get the video metadata for url search
659 return the video dictionary of directories and their video mata data
660 '''
661 initial_length = len(dictionaries)
662
663 if self.config['debug_enabled']:
664 print("Category URL:")
665 print(url)
666 print()
667
668 try:
669 etree = XmlHandler(url).getEt()
670 except Exception as errormsg:
671 sys.stderr.write(self.error_messages['MtvUrlError'] % (url, errormsg))
672 return dictionaries
673
674 if etree is None:
675 sys.stderr.write('1-No Videos for (%s)\n' % self.feed)
676 return dictionaries
677
678 dictionary_first = False
679 for elements in etree:
680 if elements.tag.endswith('totalResults'):
681 self.channel['channel_numresults'] += int(elements.text)
682 self.channel['channel_startindex'] = self.page_limit
683 self.channel['channel_returned'] = self.page_limit # False value CHANGE later
684 continue
685
686 if not elements.tag.endswith('entry'):
687 continue
688
689 metadata = {}
690 cur_size = True
691 flash = False
692 metadata['language'] = self.config['language']
693 for e in elements:
694 if e.tag.endswith('title'):
695 if e.text is not None:
696 metadata['title'] = self.massageDescription(e.text.strip())
697 else:
698 metadata['title'] = ''
699 continue
700 if e.tag == 'content':
701 if e.text is not None:
702 metadata['media_description'] = self.massageDescription(e.text.strip())
703 else:
704 metadata['media_description'] = ''
705 continue
706 if e.tag.endswith('published'): # '2007-03-06T00:00:00Z'
707 if e.text is not None:
708 pub_time = time.strptime(e.text.strip(), "%Y-%m-%dT%H:%M:%SZ")
709 metadata['published_parsed'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', pub_time)
710 else:
711 metadata['published_parsed'] = ''
712 continue
713 if e.tag.endswith('content') and e.text is None:
714 metadata['video'] = self.ampReplace(e.get('url'))
715 metadata['duration'] = e.get('duration')
716 continue
717 if e.tag.endswith('player'):
718 metadata['link'] = self.ampReplace(e.get('url'))
719 continue
720 if e.tag.endswith('thumbnail'):
721 if cur_size == False:
722 continue
723 height = e.get('height')
724 width = e.get('width')
725 if int(width) > cur_size:
726 metadata['thumbnail'] = self.ampReplace(e.get('url'))
727 cur_size = int(width)
728 if int(width) >= 200:
729 cur_size = False
730 continue
731 if e.tag.endswith('author'):
732 for a in e:
733 if a.tag.endswith('name'):
734 if a.text:
735 metadata['media_credit'] = self.massageDescription(a.text.strip())
736 else:
737 metadata['media_credit'] = ''
738 break
739 continue
740
741 if not len(metadata):
742 raise MtvVideoDetailError('2-No Video meta data for (%s)' % url)
743
744 if 'video' not in metadata and 'link' not in metadata:
745 continue
746
747 if 'video' not in metadata:
748 metadata['video'] = metadata['link']
749 else:
750 index = metadata['video'].rindex(':')
751 metadata['video'] = self.mtvHtmlPath % (urllib.parse.quote(metadata['title'].encode("utf-8")), metadata['video'][index+1:])
752 metadata['link'] = metadata['video']
753 # !! This tag will need to be added at a later date
754# metadata['customhtml'] = u'true'
755
756 if not dictionary_first: # Add the dictionaries display name
757 dictionaries.append([self.massageDescription(self.feed_names[self.tree_key][self.feed]), self.setTreeViewIcon()])
758 dictionary_first = True
759
760 final_item = {}
761 for key in list(self.key_translation[1].keys()):
762 if key not in metadata:
763 final_item[self.key_translation[1][key]] = ''
764 else:
765 final_item[self.key_translation[1][key]] = metadata[key]
766 dictionaries.append(final_item)
767
768 if initial_length < len(dictionaries): # Need to check if there was any items for this Category
769 dictionaries.append(['', '']) # Add the nested dictionary indicator
770 return dictionaries
771 # end getVideosForURL()
772# end Videos() class
def __init__(self, outstream, encoding=None)
Definition: mtv_api.py:53
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
Definition: mtv_api.py:114
def videoDetails(self, url, title='')
Definition: mtv_api.py:444
def getVideos(self, dir_dict, dictionaries)
Definition: mtv_api.py:622
def setTreeViewIcon(self, dir_icon=None)
Definition: mtv_api.py:325
def getVideosForURL(self, url, dictionaries)
Definition: mtv_api.py:657
def searchTitle(self, title, pagenumber, pagelen)
End of Utility functions.
Definition: mtv_api.py:349
def massageDescription(self, text)
Start - Utility functions.
Definition: mtv_api.py:252
def searchForVideos(self, title, pagenumber)
Definition: mtv_api.py:497
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)