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