1
# Licensed under the MIT license
2
# http://opensource.org/licenses/mit-license.php
4
# Copyright 2009, Dominik Ruf <dominikruf at googlemail dot com>
6
from coherence.backend import BackendItem
7
from coherence.backend import BackendStore
8
from coherence.upnp.core import DIDLLite
9
from coherence.upnp.core.utils import ReverseProxyUriResource
11
from xml.etree.ElementTree import ElementTree
14
from urlparse import urlsplit
19
This backend depends on the feedparser modul.
20
You can get it at http://www.feedparser.org/.""")
22
MIME_TYPES_EXTENTION_MAPPING = {'mp3': 'audio/mpeg',}
25
AUDIO_ALL_CONTAINER_ID = 51
26
AUDIO_ARTIST_CONTAINER_ID = 52
27
AUDIO_ALBUM_CONTAINER_ID = 53
28
VIDEO_FOLDER_CONTAINER_ID = 54
30
class RedirectingReverseProxyUriResource(ReverseProxyUriResource):
31
def render(self, request):
32
self.uri = self.follow_redirect(self.uri)
33
self.resetUri(self.uri)
34
return ReverseProxyUriResource.render(self, request)
36
def follow_redirect(self, uri):
37
netloc, path, query, fragment = urlsplit(uri)[1:]
38
conn = httplib.HTTPConnection(netloc)
39
conn.request('HEAD', '%s?%s#%s' % (path, query, fragment))
40
res = conn.getresponse()
41
if(res.status == 301 or res.status == 302):
42
return self.follow_redirect(res.getheader('location'))
47
class FeedStorageConfigurationException(Exception):
50
class FeedContainer(BackendItem):
51
def __init__(self, parent_id, id, title):
53
self.parent_id = parent_id
55
self.mimetype = 'directory'
56
self.item = DIDLLite.Container(self.id, self.parent_id, self.name)
60
def get_children(self, start=0, end=0):
61
"""returns all the chidlren of this container"""
63
return self.children[start:end]
64
return self.children[start:]
66
def get_child_count(self):
67
"""returns the number of children in this container"""
68
return len(self.children)
71
class FeedEnclosure(BackendItem):
72
def __init__(self, store, parent, id, title, enclosure):
77
self.location = RedirectingReverseProxyUriResource(enclosure.url.encode('latin-1'))
79
# doing this because some (Fraunhofer Podcast) feeds say there mime type is audio/x-mpeg
80
# which at least my XBOX doesn't like
81
ext = enclosure.url.rsplit('.', 1)[0]
82
if ext in MIME_TYPES_EXTENTION_MAPPING:
83
mime_type = MIME_TYPES_EXTENTION_MAPPING[ext]
85
mime_type = enclosure.type
86
if(enclosure.type.startswith('audio')):
87
self.item = DIDLLite.AudioItem(id, parent, self.name)
88
elif(enclosure.type.startswith('video')):
89
self.item = DIDLLite.VideoItem(id, parent, self.name)
90
elif(enclosure.type.startswith('image')):
91
self.item = DIDLLite.ImageItem(id, parent, self.name)
93
res = DIDLLite.Resource("%s%d" % (store.urlbase, id), 'http-get:*:%s:*' % mime_type)
95
self.item.res.append(res)
97
class FeedStore(BackendStore):
98
"""a general feed store"""
100
logCategory = 'feed_store'
101
implements = ['MediaServer']
103
def __init__(self,server,**kwargs):
104
BackendStore.__init__(self,server,**kwargs)
105
self.name = kwargs.get('name', 'Feed Store')
106
self.urlbase = kwargs.get('urlbase','')
107
if( len(self.urlbase)>0 and
108
self.urlbase[len(self.urlbase)-1] != '/'):
110
self.feed_urls = kwargs.get('feed_urls')
111
self.opml_url = kwargs.get('opml_url')
112
if(not(self.feed_urls or self.opml_url)):
113
raise FeedStorageConfigurationException("either feed_urls or opml_url has to be set")
114
if(self.feed_urls and self.opml_url):
115
raise FeedStorageConfigurationException("only feed_urls OR opml_url can be set")
118
self.refresh = int(kwargs.get('refresh', 1)) * (60 * 60) # TODO: not used yet
120
self.wmc_mapping = {'4': str(AUDIO_ALL_CONTAINER_ID), # all tracks
121
'7': str(AUDIO_ALBUM_CONTAINER_ID), # all albums
122
'6': str(AUDIO_ARTIST_CONTAINER_ID), # all artists
123
'15': str(VIDEO_FOLDER_CONTAINER_ID), # all videos
126
self.store[ROOT_CONTAINER_ID] = FeedContainer(-1, ROOT_CONTAINER_ID, self.name)
127
self.store[AUDIO_ALL_CONTAINER_ID] = FeedContainer(-1, AUDIO_ALL_CONTAINER_ID, 'AUDIO_ALL_CONTAINER')
128
self.store[AUDIO_ALBUM_CONTAINER_ID] = FeedContainer(-1, AUDIO_ALBUM_CONTAINER_ID, 'AUDIO_ALBUM_CONTAINER')
129
self.store[VIDEO_FOLDER_CONTAINER_ID] = FeedContainer(-1, VIDEO_FOLDER_CONTAINER_ID, 'VIDEO_FOLDER_CONTAINER')
134
self.error('error while updateing the feed contant for %s: %s' % (self.name, str(e)))
135
self.init_completed()
137
def get_by_id(self,id):
138
"""returns the item according to the DIDLite id"""
139
if isinstance(id, basestring):
143
return self.store[int(id)]
144
except (ValueError,KeyError):
145
self.info("can't get item %d from %s feed storage" % (int(id), self.name))
148
def _update_data(self):
149
"""get the feed xml, parse it, etc."""
152
tree = ElementTree(file=urllib.urlopen(self.opml_url))
153
body = tree.find('body')
154
for outline in body.findall('outline'):
155
feed_urls.append(outline.attrib['url'])
158
feed_urls = self.feed_urls.split()
161
for feed_url in feed_urls:
162
netloc, path, query, fragment = urlsplit(feed_url)[1:]
163
conn = httplib.HTTPConnection(netloc)
164
conn.request('HEAD', '%s?%s#%s' % (path, query, fragment))
165
res = conn.getresponse()
166
if res.status >= 400:
167
self.warning('error getting %s status code: %d' % (feed_url, res.status))
169
fp_dict = feedparser.parse(feed_url)
170
name = fp_dict.feed.title
171
self.store[container_id] = FeedContainer(ROOT_CONTAINER_ID, container_id, name)
172
self.store[ROOT_CONTAINER_ID].children.append(self.store[container_id])
173
self.store[VIDEO_FOLDER_CONTAINER_ID].children.append(self.store[container_id])
174
self.store[AUDIO_ALBUM_CONTAINER_ID].children.append(self.store[container_id])
175
for item in fp_dict.entries:
176
for enclosure in item.enclosures:
177
self.store[item_id] = FeedEnclosure(self, container_id, item_id, '%04d - %s' % (item_id, item.title), enclosure)
178
self.store[container_id].children.append(self.store[item_id])
179
if enclosure.type.startswith('audio'):
180
self.store[AUDIO_ALL_CONTAINER_ID].children.append(self.store[item_id])
181
if not isinstance(self.store[container_id].item, DIDLLite.MusicAlbum):
182
self.store[container_id].item = DIDLLite.MusicAlbum(container_id, AUDIO_ALBUM_CONTAINER_ID, name)
185
if container_id <= 1000:
188
raise Exception('to many containers')