# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php
# Coherence backend presenting the content of
# the MIRO Guide catalog for on-line videos
#
# The APi is described on page:
# https://develop.participatoryculture.org/trac/democracy/wiki/MiroGuideApi
# Copyright 2009, Jean-Michel Sizun
# Copyright 2009 Frank Scholz <coherence@beebits.net>
import os
import urllib.error
import urllib.parse
import urllib.request
from coherence.backend import BackendItem, Container, \
LazyContainer, \
AbstractBackendStore
from coherence.backends.youtube_storage import TestVideoProxy
from coherence.upnp.core import DIDLLite
from coherence.upnp.core import utils
[docs]class VideoItem(BackendItem):
def __init__(self, name, description, url, thumbnail_url, store):
BackendItem.__init__(self)
self.name = name
self.duration = None
self.size = None
self.mimetype = 'video'
self.url = None
self.video_url = url
self.thumbnail_url = thumbnail_url
self.description = description
self.date = None
self.item = None
self.location = TestVideoProxy(self.video_url, hash(self.video_url),
store.proxy_mode,
store.cache_directory,
store.cache_maxsize, store.buffer_size
)
[docs] def get_item(self):
if self.item is None:
upnp_id = self.get_id()
upnp_parent_id = self.parent.get_id()
self.item = DIDLLite.VideoItem(upnp_id, upnp_parent_id, self.name)
self.item.description = self.description
self.item.date = self.date
if self.thumbnail_url is not None:
self.item.icon = self.thumbnail_url
self.item.albumArtURI = self.thumbnail_url
res = DIDLLite.Resource(
self.url, 'http-get:*:%s:*' % self.mimetype)
res.duration = self.duration
res.size = self.size
self.item.res.append(res)
return self.item
[docs] def get_path(self):
return self.url
[docs] def get_id(self):
return self.storage_id
[docs]class MiroGuideStore(AbstractBackendStore):
logCategory = 'miroguide_store'
implements = ['MediaServer']
description = (
'Miro Guide',
'connects to the MIRO Guide service and '
'exposes the podcasts catalogued by the service. ',
None)
options = [
{'option': 'name', 'text': 'Server Name:', 'type': 'string',
'default': 'my media',
'help': 'the name under this MediaServer '
'shall show up with on other UPnP clients'},
{'option': 'version', 'text': 'UPnP Version:', 'type': 'int',
'default': 2, 'enum': (2, 1),
'help': 'the highest UPnP version this MediaServer shall support',
'level': 'advance'},
{'option': 'uuid', 'text': 'UUID Identifier:', 'type': 'string',
'help': 'the unique (UPnP) identifier for this MediaServer, '
'usually automatically set',
'level': 'advance'},
{'option': 'language', 'text': 'Language:', 'type': 'string',
'default': 'English'},
{'option': 'refresh', 'text': 'Refresh period',
'type': 'string'},
{'option': 'proxy_mode', 'text': 'Proxy mode:', 'type': 'string',
'enum': ('redirect', 'proxy', 'cache', 'buffered')},
{'option': 'buffer_size', 'text': 'Buffering size:',
'type': 'int'},
{'option': 'cache_directory', 'text': 'Cache directory:',
'type': 'dir', 'group': 'Cache'},
{'option': 'cache_maxsize', 'text': 'Cache max size:',
'type': 'int', 'group': 'Cache'},
]
def __init__(self, server, **kwargs):
AbstractBackendStore.__init__(self, server, **kwargs)
self.name = kwargs.get('name', 'MiroGuide')
self.language = kwargs.get('language', 'English')
self.refresh = int(kwargs.get('refresh', 60)) * 60
self.proxy_mode = kwargs.get('proxy_mode', 'redirect')
self.cache_directory = kwargs.get('cache_directory',
'/tmp/coherence-cache')
try:
if self.proxy_mode != 'redirect':
os.mkdir(self.cache_directory)
except Exception as e:
self.error(f'MiroGuideStore.__init__: {e!r}')
self.cache_maxsize = kwargs.get('cache_maxsize', 100000000)
self.buffer_size = kwargs.get('buffer_size', 750000)
rootItem = Container(None, self.name)
self.set_root_item(rootItem)
categoriesItem = Container(rootItem, 'All by Categories')
rootItem.add_child(categoriesItem)
languagesItem = Container(rootItem, 'All by Languages')
rootItem.add_child(languagesItem)
self.appendLanguage('Recent Videos', self.language, rootItem,
sort='-age', count=15)
self.appendLanguage('Top Rated', self.language, rootItem,
sort='rating', count=15)
self.appendLanguage('Most Popular', self.language, rootItem,
sort='-popular', count=15)
def gotError(error):
print(f'ERROR: {error}')
def gotCategories(result):
if result is None:
print('Unable to retrieve list of categories')
return
data, header = result
categories = eval(
data) # FIXME add some checks to avoid code injection
for category in categories:
name = category['name'].encode('ascii', 'strict')
category_url = category['url'].encode('ascii', 'strict')
self.appendCategory(name, name, categoriesItem)
categories_url = 'https://www.miroguide.com/api/list_categories'
d1 = utils.getPage(categories_url)
d1.addCallbacks(gotCategories, gotError)
def gotLanguages(result):
if result is None:
print('Unable to retrieve list of languages')
return
data, header = result
languages = eval(
data) # FIXME add some checks to avoid code injection
for language in languages:
name = language['name'].encode('ascii', 'strict')
language_url = language['url'].encode('ascii', 'strict')
self.appendLanguage(name, name, languagesItem)
languages_url = 'https://www.miroguide.com/api/list_languages'
d2 = utils.getPage(languages_url)
d2.addCallbacks(gotLanguages, gotError)
self.init_completed()
def __repr__(self):
return self.__class__.__name__
[docs] def appendCategory(self, name, category_id, parent):
item = LazyContainer(parent, name, category_id, self.refresh,
self.retrieveChannels, filter='category',
filter_value=category_id, per_page=100)
parent.add_child(item, external_id=category_id)
[docs] def appendLanguage(self, name, language_id, parent, sort='name', count=0):
item = LazyContainer(parent, name, language_id, self.refresh,
self.retrieveChannels, filter='language',
filter_value=language_id, per_page=100, sort=sort,
count=count)
parent.add_child(item, external_id=language_id)
[docs] def appendChannel(self, name, channel_id, parent):
item = LazyContainer(parent, name, channel_id, self.refresh,
self.retrieveChannelItems, channel_id=channel_id)
parent.add_child(item, external_id=channel_id)
[docs] def upnp_init(self):
self.current_connection_id = None
if self.server:
self.server.connection_manager_server.set_variable(
0, 'SourceProtocolInfo',
[f'http-get:*:{"video/"}:*'],
# FIXME put list of all possible video mimetypes
default=True)
self.wmc_mapping = {'15': self.get_root_id()}
[docs] def retrieveChannels(self, parent, filter, filter_value, per_page=100,
page=0, offset=0, count=0, sort='name'):
filter_value = urllib.parse.quote(filter_value.encode('utf-8'))
limit = count
if count == 0:
limit = per_page
uri = \
f'https://www.miroguide.com/api/get_channels?' \
f'limit={limit:d}&offset={offset:d}&filter={filter}&' \
f'filter_value={filter_value}&sort={sort}'
# print uri
d = utils.getPage(uri)
def gotChannels(result):
if result is None:
print(
f'Unable to retrieve channel for category {filter}')
return
data, header = result
channels = eval(data)
for channel in channels:
publisher = channel['publisher']
description = channel['description']
url = channel['url']
hi_def = channel['hi_def']
thumbnail_url = channel['thumbnail_url']
postal_code = channel['postal_code']
id = channel['id']
website_url = channel['website_url']
name = channel['name']
self.appendChannel(name, id, parent)
if (count == 0) and (len(channels) >= per_page):
# print('reached page limit (%d)' % len(channels))
parent.childrenRetrievingNeeded = True
def gotError(error):
print(f'ERROR: {error}')
d.addCallbacks(gotChannels, gotError)
return d
[docs] def retrieveChannelItems(self, parent, channel_id):
uri = f'https://www.miroguide.com/api/get_channel?id={channel_id}'
d = utils.getPage(uri)
def gotItems(result):
if result is None:
print(f'Unable to retrieve items for channel {channel_id}')
return
data, header = result
channel = eval(data)
items = []
if 'item' in channel:
items = channel['item']
for item in items:
# print('item:',item)
url = item['url']
description = item['description']
# print('description:', description)
name = item['name']
thumbnail_url = None
if 'thumbnail_url' in channel:
# print('Thumbnail:', channel['thumbnail_url'])
thumbnail_url = channel['thumbnail_url']
# size = size['size']
item = VideoItem(name, description, url, thumbnail_url, self)
item.parent = parent
parent.add_child(item, external_id=url)
def gotError(error):
print(f'ERROR: {error}')
d.addCallbacks(gotItems, gotError)
return d