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