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__='''
17 This python script is intended to perform a variety of utility functions to search and access text
18 metadata, video and image URLs from MTV. These routines are based on the api. Specifications
19 for 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 
38 import os, struct, sys, re, time
39 from datetime import datetime, timedelta
40 import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse
41 import logging
42 
43 try:
44  import xml.etree.cElementTree as ElementTree
45 except ImportError:
46  import xml.etree.ElementTree as ElementTree
47 
48 from .mtv_exceptions import (MtvUrlError, MtvHttpError, MtvRssError, MtvVideoNotFound, MtvInvalidSearchType, MtvXmlError, MtvVideoDetailError)
49 import io
50 
51 class 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 
70 if isinstance(sys.stdout, io.TextIOWrapper):
71  sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
72  sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
73 
74 
75 class XmlHandler:
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 
97 class 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
210  self.tree_customize = {
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 
568  def displayTreeView(self):
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
nv_python_libs.mtv.mtv_api.Videos.getVideos
def getVideos(self, dir_dict, dictionaries)
Definition: mtv_api.py:622
nv_python_libs.mtv.mtv_api.OutStreamEncoder.__getattr__
def __getattr__(self, attr)
Definition: mtv_api.py:66
nv_python_libs.mtv.mtv_api.Videos.tree_org
tree_org
Definition: mtv_api.py:186
nv_python_libs.mtv.mtv_api.Videos.displayTreeView
def displayTreeView(self)
Definition: mtv_api.py:568
nv_python_libs.mtv.mtv_api.OutStreamEncoder.encoding
encoding
Definition: mtv_api.py:56
nv_python_libs.mtv.mtv_api.Videos.grabber_title
grabber_title
Definition: mtv_api.py:501
nv_python_libs.mtv.mtv_api.Videos.makeURL
def makeURL(self, URL)
Definition: mtv_api.py:596
nv_python_libs.mtv.mtv_api.Videos.__init__
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:105
nv_python_libs.mtv.mtv_api.Videos.tree_order
tree_order
Definition: mtv_api.py:185
nv_python_libs.mtv.mtv_exceptions.MtvVideoNotFound
Definition: mtv_exceptions.py:38
nv_python_libs.mtv.mtv_exceptions.MtvHttpError
Definition: mtv_exceptions.py:28
nv_python_libs.mtv.mtv_api.Videos.setTreeViewIcon
def setTreeViewIcon(self, dir_icon=None)
Definition: mtv_api.py:325
hardwareprofile.software.dict
dictionary dict
Definition: software.py:55
nv_python_libs.mtv.mtv_api.Videos.channel
channel
Definition: mtv_api.py:573
nv_python_libs.mtv.mtv_api.OutStreamEncoder.write
def write(self, obj)
Definition: mtv_api.py:60
nv_python_libs.mtv.mtv_exceptions.MtvUrlError
Definition: mtv_exceptions.py:23
nv_python_libs.mtv.mtv_exceptions.MtvVideoDetailError
Definition: mtv_exceptions.py:53
nv_python_libs.mtv.mtv_api.Videos.tree_dir_icon
tree_dir_icon
Definition: mtv_api.py:329
nv_python_libs.mtv.mtv_api.Videos.massageDescription
def massageDescription(self, text)
Start - Utility functions.
Definition: mtv_api.py:252
nv_python_libs.mtv.mtv_api.Videos.feed_icons
feed_icons
Definition: mtv_api.py:223
nv_python_libs.mtv.mtv_api.OutStreamEncoder.out
out
Definition: mtv_api.py:54
nv_python_libs.mtv.mtv_api.XmlHandler._grabUrl
def _grabUrl(self, url)
Definition: mtv_api.py:81
nv_python_libs.mtv.mtv_api.XmlHandler
Definition: mtv_api.py:75
nv_python_libs.mtv.mtv_api.XmlHandler.url
url
Definition: mtv_api.py:79
nv_python_libs.mtv.mtv_api.Videos.error_messages
error_messages
Definition: mtv_api.py:161
print
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)
Definition: vbi608extractor.cpp:29
nv_python_libs.mtv.mtv_api.OutStreamEncoder.__init__
def __init__(self, outstream, encoding=None)
Definition: mtv_api.py:53
nv_python_libs.mtv.mtv_api.Videos.feed_names
feed_names
Definition: mtv_api.py:216
nv_python_libs.mtv.mtv_api.Videos.searchTitle
def searchTitle(self, title, pagenumber, pagelen)
End of Utility functions.
Definition: mtv_api.py:349
nv_python_libs.mtv.mtv_api.Videos.getVideosForURL
def getVideosForURL(self, url, dictionaries)
Definition: mtv_api.py:657
nv_python_libs.mtv.mtv_api.Videos.searchForVideos
def searchForVideos(self, title, pagenumber)
Definition: mtv_api.py:497
nv_python_libs.mtv.mtv_api.XmlHandler.getEt
def getEt(self)
Definition: mtv_api.py:88
nv_python_libs.mtv.mtv_api.OutStreamEncoder
Definition: mtv_api.py:51
nv_python_libs.mtv.mtv_api.Videos.tree_key
tree_key
Definition: mtv_api.py:590
nv_python_libs.mtv.mtv_api.Videos.textUtf8
def textUtf8(self, text)
Definition: mtv_api.py:305
nv_python_libs.mtv.mtv_api.Videos.tree_customize
tree_customize
Definition: mtv_api.py:201
nv_python_libs.mtv.mtv_api.Videos.log_name
log_name
Definition: mtv_api.py:147
nv_python_libs.mtv.mtv_api.Videos.config
config
Definition: mtv_api.py:138
nv_python_libs.mtv.mtv_api.Videos.videoDetails
def videoDetails(self, url, title='')
Definition: mtv_api.py:444
nv_python_libs.mtv.mtv_api.XmlHandler.__init__
def __init__(self, url)
Definition: mtv_api.py:78
nv_python_libs.mtv.mtv_api.Videos.log
log
Definition: mtv_api.py:148
nv_python_libs.mtv.mtv_api.Videos.ampReplace
def ampReplace(self, text)
Definition: mtv_api.py:317
nv_python_libs.mtv.mtv_api.Videos
Definition: mtv_api.py:97
nv_python_libs.mtv.mtv_exceptions.MtvXmlError
Definition: mtv_exceptions.py:48
nv_python_libs.mtv.mtv_api.Videos.mtvHtmlPath
mtvHtmlPath
Definition: mtv_api.py:230
nv_python_libs.mtv.mtv_api.Videos.treeview
treeview
Definition: mtv_api.py:233
nv_python_libs.mtv.mtv_api.Videos.channel_icon
channel_icon
Definition: mtv_api.py:234
nv_python_libs.mtv.mtv_api.Videos._initLogger
def _initLogger(self)
Definition: mtv_api.py:286
nv_python_libs.mtv.mtv_api.Videos.key_translation
key_translation
Definition: mtv_api.py:164