MythTV  master
bliptv_api.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 # ----------------------
4 # Name: bliptv_api - Simple-to-use Python interface to the bliptv API (http://blip.tv/)
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 # meta data, video and image URLs from blip.tv.
9 # These routines are based on the v2.0 api. Specifications
10 # for this api are published at http://blip.tv/about/api/
11 #
12 # License:Creative Commons GNU GPL v2
13 # (http://creativecommons.org/licenses/GPL/2.0/)
14 #-------------------------------------
15 __title__ ="bliptv_api - Simple-to-use Python interface to the bliptv API (http://blip.tv/about/api/)"
16 __author__="R.D. Vaughan"
17 __purpose__='''
18 This python script is intended to perform a variety of utility functions to search and access text
19 meta data, video and image URLs from blip.tv. These routines are based on the v2.0 api. Specifications
20 for this api are published at http://blip.tv/about/api/
21 '''
22 
23 __version__="v0.2.5"
24 # 0.1.0 Initial development
25 # 0.1.1 Changed to use bliptv's rss data rather than JSON as JSON ad a number of error
26 # 0.1.2 Changes Search to parse XML and added Tree view
27 # 0.1.3 Added directory image logic
28 # 0.1.4 Documentation updates
29 # 0.2.0 Public release
30 # 0.2.1 New python bindings conversion
31 # Better exception error reporting
32 # Better handling of invalid unicode data from source
33 # 0.2.2 Completed exception message improvements
34 # Removed the unused import of the feedparser library
35 # 0.2.3 Fixed an exception message output code error in two places
36 # 0.2.4 Removed the need for python MythTV bindings and added "%SHAREDIR%" to icon directory path
37 # 0.2.5 Changed link URL to full screen when a "blip:embedUrl" exists for an item
38 # Removed a subdirectory level as the "Featured" RSS feed has been discontinued
39 
40 import os, struct, sys, re, time
41 import urllib, urllib2
42 import logging
43 from MythTV import MythXML
44 
45 try:
46  import xml.etree.cElementTree as ElementTree
47 except ImportError:
48  import xml.etree.ElementTree as ElementTree
49 
50 from bliptv_exceptions import (BliptvUrlError, BliptvHttpError, BliptvRssError, BliptvVideoNotFound, BliptvXmlError)
51 
52 class OutStreamEncoder(object):
53  """Wraps a stream with an encoder"""
54  def __init__(self, outstream, encoding=None):
55  self.out = outstream
56  if not encoding:
57  self.encoding = sys.getfilesystemencoding()
58  else:
59  self.encoding = encoding
60 
61  def write(self, obj):
62  """Wraps the output stream, encoding Unicode strings with the specified encoding"""
63  if isinstance(obj, unicode):
64  try:
65  self.out.write(obj.encode(self.encoding))
66  except IOError:
67  pass
68  else:
69  try:
70  self.out.write(obj)
71  except IOError:
72  pass
73 
74  def __getattr__(self, attr):
75  """Delegate everything but write to the stream"""
76  return getattr(self.out, attr)
77 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
78 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
79 
80 
81 class XmlHandler:
82  """Deals with retrieval of XML files from API
83  """
84  def __init__(self, url):
85  self.url = url
86 
87  def _grabUrl(self, url):
88  try:
89  urlhandle = urllib.urlopen(url)
90  except IOError, errormsg:
91  raise BliptvHttpError(errormsg)
92  return urlhandle.read()
93 
94  def getEt(self):
95  xml = self._grabUrl(self.url)
96  try:
97  et = ElementTree.fromstring(xml)
98  except SyntaxError, errormsg:
99  raise BliptvXmlError(errormsg)
100  return et
101 
102 
103 class Videos(object):
104  """Main interface to http://blip.tv/
105  This is done to support a common naming framework for all python Netvision plugins no matter their site
106  target.
107 
108  Supports search and tree view methods
109  The apikey is a not required to access http://blip.tv/
110  """
111  def __init__(self,
112  apikey,
113  mythtv = True,
114  interactive = False,
115  select_first = False,
116  debug = False,
117  custom_ui = None,
118  language = None,
119  search_all_languages = False,
120  ):
121  """apikey (str/unicode):
122  Specify the target site API key. Applications need their own key in some cases
123 
124  mythtv (True/False):
125  When True, the returned meta data is being returned has the key and values massaged to match MythTV
126  When False, the returned meta data is being returned matches what target site returned
127 
128  interactive (True/False): (This option is not supported by all target site apis)
129  When True, uses built-in console UI is used to select the correct show.
130  When False, the first search result is used.
131 
132  select_first (True/False): (This option is not supported currently implemented in any grabbers)
133  Automatically selects the first series search result (rather
134  than showing the user a list of more than one series).
135  Is overridden by interactive = False, or specifying a custom_ui
136 
137  debug (True/False):
138  shows verbose debugging information
139 
140  custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
141  A callable subclass of interactive class (overrides interactive option)
142 
143  language (2 character language abbreviation): (This option is not supported by all target site apis)
144  The language of the returned data. Is also the language search
145  uses. Default is "en" (English). For full list, run..
146 
147  search_all_languages (True/False): (This option is not supported by all target site apis)
148  By default, a Netvision grabber will only search in the language specified using
149  the language option. When this is True, it will search for the
150  show in any language
151 
152  """
153  self.config = {}
154  self.mythxml = MythXML()
156  if apikey is not None:
157  self.config['apikey'] = apikey
158  else:
159  pass # blip.tv does not require an apikey
160 
161  self.config['debug_enabled'] = debug # show debugging messages
162 
163  self.log_name = "Bliptv"
164  self.log = self._initLogger() # Setups the logger (self.log.debug() etc)
165 
166  self.config['custom_ui'] = custom_ui
167 
168  self.config['interactive'] = interactive # prompt for correct series?
169 
170  self.config['select_first'] = select_first
172  self.config['search_all_languages'] = search_all_languages
173 
174  # Defaulting to ENGISH but the blip.tv apis do not support specifying a language
175  self.config['language'] = "en"
176 
177  self.error_messages = {'BliptvUrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'BliptvHttpError': u"! Error: An HTTP communicating error with blip.tv was raised (%s)\n", 'BliptvRssError': u"! Error: Invalid RSS meta data\nwas received from blip.tv error (%s). Skipping item.\n", 'BliptvVideoNotFound': u"! Error: Video search with blip.tv did not return any results (%s)\n", }
178 
179  # This is an example that must be customized for each target site
180  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', 'blip_safeusername': 'item_author', 'updated': 'item_pubdate', 'blip_puredescription': 'item_description', 'link': 'item_link', 'blip_picture': 'item_thumbnail', 'video': 'item_url', 'blip_runtime': 'item_duration', 'blip_rating': 'item_rating', 'width': 'item_width', 'height': 'item_height', 'language': 'item_lang'}]
181 
182  # The following url_ configs are based of the
183  # http://blip.tv/about/api/
184  self.config['base_url'] = "http://www.blip.tv%s"
185  self.config['thumb_url'] = u"http://a.images.blip.tv%s"
186 
187  self.config[u'urls'] = {}
188 
189  # v2 api calls - An example that must be customized for each target site
190  self.config[u'urls'][u'video.search'] = "http://www.blip.tv/?search=%s;&page=%s;&pagelen=%s;&language_code=%s;&skin=rss"
191  self.config[u'urls'][u'categories'] = "http://www.blip.tv/?section=categories&cmd=view&skin=api"
192 
193  self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
194 
195  # Functions that parse video data from RSS data
196  self.config['item_parser'] = {}
197  self.config['item_parser']['main'] = self.getVideosForURL
198 
199  # Tree view url and the function that parses that urls meta data
200  self.config[u'urls'][u'tree.view'] = {
201  'P_R_R_F': {
202  '__all__': ['http://www.blip.tv/%s/?skin=rss', 'main'],
203  },
204  'categories': {
205  '__all__': ['http://www.blip.tv/rss/', 'main'],
206  },
207  }
208 
209  # Tree view categories are disabled until their results can be made more meaningful
210  #self.tree_order = ['P_R_R_F', 'categories', ]
211  self.tree_order = ['P_R_R_F']
212 
213  self.tree_org = {
214 # 'P_R_R_F': [['Popular/Recent/Features/Random ...', ['popular', 'recent', 'random', 'featured',]],
215  'P_R_R_F': [['', ['popular', 'recent', 'random', 'featured',]],
216  ],
217  # categories are dynamically filled in from a list retrieved from the blip.tv site
218  'categories': [
219  ['Categories', u''],
220  ],
221  }
222 
223  self.tree_customize = {
224  'P_R_R_F': {
225  '__default__': { },
226  #'cat name': {},
227  },
228  'categories': {
229  '__default__': {'categories_id': u'', 'sort': u'', },
230  #'cat name': {},
231  },
232  }
233 
234  self.feed_names = {
235  'P_R_R_F': {'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection',
236  },
237  'categories': {'featured': 'Featured Videos', 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection',
238  },
239  }
241  self.feed_icons = {
242  'P_R_R_F': {'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random',
243  },
244  'categories': {'featured': 'directories/topics/featured', 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random',
245  },
246  }
247 
248  # Initialize the tree view flag so that the item parsing code can be used for multiple purposes
249  self.categories = False
250  self.treeview = False
251  self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/bliptv.png'
252  # end __init__()
253 
254 
255 
260 
262  '''Get longitude and latitiude to find videos relative to your location. Up to three different
263  servers will be tried before giving up.
264  return a dictionary e.g.
265  {'Latitude': '43.6667', 'Country': 'Canada', 'Longitude': '-79.4167', 'City': 'Toronto'}
266  return an empty dictionary if there were any errors
267  Code found at: http://blog.suinova.com/2009/04/from-ip-to-geolocation-country-city.html
268  '''
269  def getExternalIP():
270  '''Find the external IP address of this computer.
271  '''
272  url = urllib.URLopener()
273  try:
274  resp = url.open('http://www.whatismyip.com/automation/n09230945.asp')
275  return resp.read()
276  except:
277  return None
278  # end getExternalIP()
279 
280  ip = getExternalIP()
281 
282  if ip == None:
283  return {}
284 
285  try:
286  gs = urllib.urlopen('http://blogama.org/ip_query.php?ip=%s&output=xml' % ip)
287  txt = gs.read()
288  except:
289  try:
290  gs = urllib.urlopen('http://www.seomoz.org/ip2location/look.php?ip=%s' % ip)
291  txt = gs.read()
292  except:
293  try:
294  gs = urllib.urlopen('http://api.hostip.info/?ip=%s' % ip)
295  txt = gs.read()
296  except:
297  logging.error('GeoIP servers not available')
298  return {}
299  try:
300  if txt.find('<Response>') > 0:
301  countrys = re.findall(r'<CountryName>([\w ]+)<',txt)[0]
302  citys = re.findall(r'<City>([\w ]+)<',txt)[0]
303  lats,lons = re.findall(r'<Latitude>([\d\-\.]+)</Latitude>\s*<Longitude>([\d\-\.]+)<',txt)[0]
304  elif txt.find('GLatLng') > 0:
305  citys,countrys = re.findall('<br />\s*([^<]+)<br />\s*([^<]+)<',txt)[0]
306  lats,lons = re.findall('LatLng\(([-\d\.]+),([-\d\.]+)',txt)[0]
307  elif txt.find('<gml:coordinates>') > 0:
308  citys = re.findall('<Hostip>\s*<gml:name>(\w+)</gml:name>',txt)[0]
309  countrys = re.findall('<countryName>([\w ,\.]+)</countryName>',txt)[0]
310  lats,lons = re.findall('gml:coordinates>([-\d\.]+),([-\d\.]+)<',txt)[0]
311  else:
312  logging.error('error parsing IP result %s'%txt)
313  return {}
314  return {'Country':countrys,'City':citys,'Latitude':lats,'Longitude':lons}
315  except:
316  logging.error('Error parsing IP result %s'%txt)
317  return {}
318  # end detectUserLocationByIP()
319 
320  def massageDescription(self, text):
321  '''Removes HTML markup from a text string.
322  @param text The HTML source.
323  @return The plain text. If the HTML source contains non-ASCII
324  entities or character references, this is a Unicode string.
325  '''
326  def fixup(m):
327  text = m.group(0)
328  if text[:1] == "<":
329  return "" # ignore tags
330  if text[:2] == "&#":
331  try:
332  if text[:3] == "&#x":
333  return unichr(int(text[3:-1], 16))
334  else:
335  return unichr(int(text[2:-1]))
336  except ValueError:
337  pass
338  elif text[:1] == "&":
339  import htmlentitydefs
340  entity = htmlentitydefs.entitydefs.get(text[1:-1])
341  if entity:
342  if entity[:2] == "&#":
343  try:
344  return unichr(int(entity[2:-1]))
345  except ValueError:
346  pass
347  else:
348  return unicode(entity, "iso-8859-1")
349  return text # leave as is
350  return self.ampReplace(re.sub(u"(?s)<[^>]*>|&#?\w+;", fixup, self.textUtf8(text))).replace(u'\n',u' ')
351  # end massageDescription()
352 
353 
354  def _initLogger(self):
355  """Setups a logger using the logging module, returns a log object
356  """
357  logger = logging.getLogger(self.log_name)
358  formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s')
359 
360  hdlr = logging.StreamHandler(sys.stdout)
361 
362  hdlr.setFormatter(formatter)
363  logger.addHandler(hdlr)
364 
365  if self.config['debug_enabled']:
366  logger.setLevel(logging.DEBUG)
367  else:
368  logger.setLevel(logging.WARNING)
369  return logger
370  #end initLogger
371 
372 
373  def textUtf8(self, text):
374  if text == None:
375  return text
376  try:
377  return unicode(text, 'utf8')
378  except UnicodeDecodeError:
379  return u''
380  except (UnicodeEncodeError, TypeError):
381  return text
382  # end textUtf8()
383 
384 
385  def ampReplace(self, text):
386  '''Replace all "&" characters with "&amp;"
387  '''
388  text = self.textUtf8(text)
389  return text.replace(u'&amp;',u'~~~~~').replace(u'&',u'&amp;').replace(u'~~~~~', u'&amp;')
390  # end ampReplace()
391 
392  def setTreeViewIcon(self, dir_icon=None):
393  '''Check if there is a specific generic tree view icon. If not default to the channel icon.
394  return self.tree_dir_icon
395  '''
397  if not dir_icon:
398  if not self.feed_icons.has_key(self.tree_key):
399  return self.tree_dir_icon
400  if not self.feed_icons[self.tree_key].has_key(self.feed):
401  return self.tree_dir_icon
402  dir_icon = self.feed_icons[self.tree_key][self.feed]
403  if not dir_icon:
404  return self.tree_dir_icon
405  self.tree_dir_icon = u'%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, )
406  return self.tree_dir_icon
407  # end setTreeViewIcon()
408 
409 
414 
415  def processVideoUrl(self, url):
416  playerUrl = self.mythxml.getInternetContentUrl("nv_python_libs/configs/HTML/bliptv.html", \
417  url.replace(u'http://blip.tv/play/', ''))
418  return self.ampReplace(playerUrl)
419 
420  def searchTitle(self, title, pagenumber, pagelen):
421  '''Key word video search of the blip.tv web site
422  return an array of matching item dictionaries
423  return
424  '''
425  url = self.config[u'urls'][u'video.search'] % (urllib.quote_plus(title.encode("utf-8")), pagenumber, pagelen, self.config['language'])
426 
427  if self.config['debug_enabled']:
428  print "Search URL:"
429  print url
430  print
431 
432  try:
433  etree = XmlHandler(url).getEt()
434  except Exception, errormsg:
435  raise BliptvUrlError(self.error_messages['BliptvUrlError'] % (url, errormsg))
436 
437  if etree is None:
438  raise BliptvVideoNotFound(u"1-No blip.tv Video matches found for search value (%s)" % title)
439 
440  # Massage each field and eliminate any item without a URL
441  elements_final = []
442  dictionary_first = False
443  directory_image = u''
444  self.next_page = False
445  language = self.config['language']
446  for elements in etree.find('channel'):
447  if elements.tag == 'language':
448  if elements.text:
449  language = elements.text[:2]
450  continue
451  if not elements.tag == 'item':
452  continue
453  item = {}
454  item['language'] = language
455  embedURL = u''
456  for elem in elements:
457  if elem.tag == 'title':
458  if elem.text:
459  item['title'] = self.massageDescription(elem.text.strip())
460  continue
461  if elem.tag.endswith('safeusername'):
462  if elem.text:
463  item['blip_safeusername'] = self.massageDescription(elem.text.strip())
464  continue
465  if elem.tag.endswith('pubDate'):
466  if elem.text:
467  item['updated'] = self.massageDescription(elem.text.strip())
468  continue
469  if elem.tag.endswith('puredescription'):
470  if elem.text:
471  item['blip_puredescription'] = self.massageDescription(elem.text.strip())
472  continue
473  if elem.tag.endswith('link'):
474  if elem.text:
475  item['link'] = self.ampReplace(elem.text.strip())
476  continue
477  if elem.tag.endswith('embedUrl'):
478  if elem.text:
479  embedURL = self.ampReplace(elem.text.strip())
480  continue
481  if elem.tag.endswith('thumbnail'):
482  if elem.get('url'):
483  item['blip_picture'] = self.ampReplace(elem.get('url').strip())
484  continue
485  if elem.tag.endswith('group'):
486  file_size = 0
487  for e in elem:
488  if e.tag.endswith('content'):
489  if e.get('fileSize'):
490  try:
491  if int(e.get('fileSize')) > file_size:
492  item['video'] = self.ampReplace(e.get('url').strip())
493  file_size = int(e.get('fileSize'))
494  except:
495  pass
496  continue
497  continue
498  if elem.tag.endswith('runtime'):
499  if elem.text:
500  item['blip_runtime'] = self.massageDescription(elem.text.strip())
501  continue
502  if elem.tag.endswith('rating'):
503  if elem.text:
504  item['blip_rating'] = self.massageDescription(elem.text.strip())
505  continue
506  if not item.has_key('video') and not item.has_key('link') and not embedURL:
507  continue
508  if embedURL:
509  item['link'] = self.processVideoUrl(embedURL)
510  if item.has_key('link') and not item.has_key('video'):
511  continue
512  if item.has_key('video') and not item.has_key('link'):
513  item['link'] = item['video']
514  elements_final.append(item)
515 
516  if not len(elements_final):
517  raise BliptvVideoNotFound(u"2-No blip.tv Video matches found for search value (%s)" % title)
518 
519  return elements_final
520  # end searchTitle()
521 
522 
523  def searchForVideos(self, title, pagenumber):
524  """Common name for a video search. Used to interface with MythTV plugin NetVision
525  """
526  try:
527  data = self.searchTitle(title, pagenumber, self.page_limit)
528  except BliptvVideoNotFound, msg:
529  sys.stderr.write(u"%s\n" % msg)
530  return None
531  except BliptvUrlError, msg:
532  sys.stderr.write(u'%s' % msg)
533  sys.exit(1)
534  except BliptvHttpError, msg:
535  sys.stderr.write(self.error_messages['BliptvHttpError'] % msg)
536  sys.exit(1)
537  except BliptvRssError, msg:
538  sys.stderr.write(self.error_messages['BliptvRssError'] % msg)
539  sys.exit(1)
540  except Exception, e:
541  sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
542  sys.exit(1)
543 
544  if data == None:
545  return None
546  if not len(data):
547  return None
548 
549  items = []
550  for match in data:
551  item_data = {}
552  for key in self.key_translation[1].keys():
553  if key in match.keys():
554  item_data[self.key_translation[1][key]] = match[key]
555  else:
556  item_data[self.key_translation[1][key]] = u''
557  items.append(item_data)
558 
559  # Channel details and search results
560  channel = {'channel_title': u'blip.tv', 'channel_link': u'http://blip.tv', 'channel_description': u"We're the next generation television network", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0}
561 
562  if len(items) == self.page_limit:
563  channel['channel_numresults'] = self.page_limit * int(pagenumber) + 1
564  else:
565  channel['channel_numresults'] = self.page_limit * int(pagenumber)
566  channel['channel_startindex'] = self.page_limit * int(pagenumber)
567  channel['channel_returned'] = len(items)
568 
569  if len(items):
570  return [[channel, items]]
571  return None
572  # end searchForVideos()
573 
574 
575  def getCategories(self):
576  '''Get the list of valid category ids and their name and update the proper dictionaries
577  return nothing
578  '''
579  url = self.config[u'urls'][u'categories']
580  if self.config['debug_enabled']:
581  print "Category list URL:"
582  print url
583  print
584 
585  try:
586  etree = XmlHandler(url).getEt()
587  except Exception, errormsg:
588  sys.stderr.write(self.error_messages['BliptvUrlError'] % (url, errormsg))
589  self.tree_order.remove('categories')
590  return
591 
592  if etree is None:
593  sys.stderr.write(u'1-No Categories found at (%s)\n' % url)
594  self.tree_order.remove('categories')
595  return
596 
597  if not etree.find('payload'):
598  sys.stderr.write(u'2-No Categories found at (%s)\n' % url)
599  self.tree_order.remove('categories')
600  return
601 
602  category = False
603  for element in etree.find('payload'):
604  if element.tag == 'category':
605  tmp_name = u''
606  tmp_id = u''
607  for e in element:
608  if e.tag == 'id':
609  if e.text == '-1':
610  break
611  if e.text:
612  tmp_id = self.massageDescription(e.text.strip())
613  if e.tag == 'name':
614  if e.text:
615  tmp_name = self.massageDescription(e.text.strip())
616  if tmp_id and tmp_name:
617  category = True
618  self.tree_org['categories'].append([tmp_name, ['popular', 'recent', 'random', 'featured',]])
619  self.feed_names['categories'][tmp_name] = tmp_id
620 
621  if not category:
622  sys.stderr.write(u'3-No Categories found at (%s)\n' % url)
623  self.tree_order.remove('categories')
624  return
625 
626  self.tree_org['categories'].append([u'', u'']) # Adds a end of the Categories directory indicator
627 
628  return
629  # end getCategories()
630 
631  def displayTreeView(self):
632  '''Gather the categories/feeds/...etc then retrieve a max page of videos meta data in each of them
633  return array of directories and their video meta data
634  '''
635  # Channel details and search results
636  self.channel = {'channel_title': u'blip.tv', 'channel_link': u'http://blip.tv', 'channel_description': u"We're the next generation television network", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0}
637 
638  if self.config['debug_enabled']:
639  print self.config[u'urls']
640  print
641 
642  # Get category ids
643  self.getCategories()
644 
645  # Process the various video feeds/categories/... etc
646  self.treeview = True
647  dictionaries = []
648  for key in self.tree_order:
649  if key == 'categories':
650  self.categories = True
651  else:
652  self.categories = False
653  self.tree_key = key
654  dictionaries = self.getVideos(self.tree_org[key], dictionaries)
655 
656  return [[self.channel, dictionaries]]
657  # end displayTreeView()
658 
659  def makeURL(self, URL):
660  '''Form a URL to search for videos
661  return a URL
662  '''
663  additions = dict(self.tree_customize[self.tree_key]['__default__']) # Set defaults
664 
665  # Add customizations
666  if self.feed in self.tree_customize[self.tree_key].keys():
667  for element in self.tree_customize[self.tree_key][self.feed].keys():
668  additions[element] = self.tree_customize[self.tree_key][self.feed][element]
669 
670  # Make the search extension string that is added to the URL
671  addition = u''
672  for ky in additions.keys():
673  if ky.startswith('add_'):
674  addition+=u'/%s' % additions[ky]
675  else:
676  addition+=u'?%s=%s' % (ky, additions[ky])
677  index = URL.find('%')
678  if index == -1:
679  return (URL+addition)
680  else:
681  return (URL+addition) % self.feed
682  # end makeURL()
683 
684 
685  def getVideos(self, dir_dict, dictionaries):
686  '''Parse a list made of categories and retrieve video meta data
687  return a dictionary of directory names and categories video meta data
688  '''
689  for sets in dir_dict:
690  if not isinstance(sets[1], list):
691  if sets[0] != '': # Add the nested dictionaries display name
692  dictionaries.append([self.massageDescription(sets[0]), self.channel_icon])
693  else:
694  dictionaries.append(['', u'']) # Add the nested dictionary indicator
695  continue
696  temp_dictionary = []
697  for self.feed in sets[1]:
698  if self.categories:
699  self.tree_customize[self.tree_key]['__default__']['categories_id'] = self.feed_names['categories'][sets[0]]
700  self.tree_customize[self.tree_key]['__default__']['sort'] = self.feed
701  if self.config[u'urls'][u'tree.view'][self.tree_key].has_key('__all__'):
702  URL = self.config[u'urls'][u'tree.view'][self.tree_key]['__all__']
703  else:
704  URL = self.config[u'urls'][u'tree.view'][self.tree_key][self.feed]
705  temp_dictionary = self.config['item_parser'][URL[1]](self.makeURL(URL[0]), temp_dictionary)
706  if len(temp_dictionary):
707  if len(sets[0]): # Add the nested dictionaries display name
708  dictionaries.append([self.massageDescription(sets[0]), self.channel_icon])
709  for element in temp_dictionary:
710  dictionaries.append(element)
711  if len(sets[0]):
712  dictionaries.append(['', u'']) # Add the nested dictionary indicator
713  return dictionaries
714  # end getVideos()
715 
716  def getVideosForURL(self, url, dictionaries):
717  '''Get the video meta data for url search
718  return the video dictionary of directories and their video mata data
719  '''
720  initial_length = len(dictionaries)
721 
722  if self.config['debug_enabled']:
723  print "Video URL:"
724  print url
725  print
726 
727  try:
728  etree = XmlHandler(url).getEt()
729  except Exception, errormsg:
730  sys.stderr.write(self.error_messages['BliptvUrlError'] % (url, errormsg))
731  return dictionaries
732 
733  if etree is None:
734  sys.stderr.write(u'1-No Videos for (%s)\n' % self.feed)
735  return dictionaries
736 
737  dictionary_first = False
738  self.next_page = False
739  language = self.config['language']
740  for elements in etree.find('channel'):
741  if elements.tag.endswith(u'language'):
742  if elements.text:
743  language = elements.text[:2]
744  continue
745 
746  if not elements.tag.endswith(u'item'):
747  continue
748 
749  item = {}
750  item['language'] = language
751  embedURL = u''
752  for elem in elements:
753  if elem.tag == 'title':
754  if elem.text:
755  item['title'] = self.massageDescription(elem.text.strip())
756  continue
757  if elem.tag.endswith('safeusername'):
758  if elem.text:
759  item['blip_safeusername'] = self.massageDescription(elem.text.strip())
760  continue
761  if elem.tag.endswith('pubDate'):
762  if elem.text:
763  item['updated'] = self.massageDescription(elem.text.strip())
764  continue
765  if elem.tag.endswith('puredescription'):
766  if elem.text:
767  item['blip_puredescription'] = self.massageDescription(elem.text.strip())
768  continue
769  if elem.tag == 'link':
770  if elem.text:
771  item['link'] = self.ampReplace(elem.text.strip())
772  continue
773  if elem.tag.endswith('embedUrl'):
774  if elem.text:
775  embedURL = self.ampReplace(elem.text.strip())
776  continue
777  if elem.tag.endswith('thumbnail'):
778  if elem.get('url'):
779  item['blip_picture'] = self.ampReplace(elem.get('url').strip())
780  continue
781  if elem.tag.endswith('group'):
782  file_size = 0
783  for e in elem:
784  if e.tag.endswith('content'):
785  for key in e.keys():
786  if key.endswith('vcodec'):
787  break
788  else:
789  continue
790  if e.get('fileSize'):
791  try:
792  if int(e.get('fileSize')) > file_size:
793  item['video'] = self.ampReplace(e.get('url').strip())
794  file_size = int(e.get('fileSize'))
795  except:
796  pass
797  if e.get('height'):
798  item['height'] = e.get('height').strip()
799  if e.get('width'):
800  item['width'] = e.get('width').strip()
801  continue
802  continue
803  if elem.tag.endswith('runtime'):
804  if elem.text:
805  item['blip_runtime'] = self.massageDescription(elem.text.strip())
806  continue
807  if elem.tag.endswith('rating'):
808  if elem.text:
809  item['blip_rating'] = self.massageDescription(elem.text.strip())
810  continue
811  if not item.has_key('video') and not item.has_key('link'):
812  continue
813  if embedURL:
814  item['link'] = embedURL
815  if item.has_key('link') and not item.has_key('video'):
816  continue
817  if item.has_key('video') and not item.has_key('link'):
818  item['link'] = item['video']
819 
820  if self.treeview:
821  if not dictionary_first: # Add the dictionaries display name
822  dictionaries.append([self.massageDescription(self.feed_names[self.tree_key][self.feed]), self.setTreeViewIcon()])
823  dictionary_first = True
824 
825  final_item = {}
826  for key in self.key_translation[1].keys():
827  if not item.has_key(key):
828  final_item[self.key_translation[1][key]] = u''
829  else:
830  final_item[self.key_translation[1][key]] = item[key]
831  dictionaries.append(final_item)
832 
833  if self.treeview:
834  if initial_length < len(dictionaries): # Need to check if there was any items for this Category
835  dictionaries.append(['', u'']) # Add the nested dictionary indicator
836  return dictionaries
837  # end getVideosForURL()
838 # end Videos() class
def setTreeViewIcon(self, dir_icon=None)
Definition: bliptv_api.py:392
def __init__(self, outstream, encoding=None)
Definition: bliptv_api.py:54
def __init__(self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False)
Definition: bliptv_api.py:111
def getVideosForURL(self, url, dictionaries)
Definition: bliptv_api.py:716
def searchTitle(self, title, pagenumber, pagelen)
Definition: bliptv_api.py:420
def searchForVideos(self, title, pagenumber)
Definition: bliptv_api.py:523
def detectUserLocationByIP(self)
Start - Utility functions.
Definition: bliptv_api.py:261
def processVideoUrl(self, url)
End of Utility functions.
Definition: bliptv_api.py:415
def getVideos(self, dir_dict, dictionaries)
Definition: bliptv_api.py:685