enigma2 20130907 (master) -> 20130927 (master)
[enigma2.git] / usr / lib / enigma2 / python / Plugins / SystemPlugins / UPnP / UPnPCore.py
1 # -*- coding: UTF-8 -*-
2
3 from coherence.base import Coherence
4
5 from coherence.upnp.devices.control_point import ControlPoint
6 from coherence.upnp.devices.media_renderer import MediaRenderer
7 from coherence.upnp.devices.media_server import MediaServer
8 from coherence.upnp.devices.media_server_client import MediaServerClient
9 from HTMLParser import HTMLParser
10
11 class Statics:
12         CONTAINER_ID_ROOT = 0
13         CONTAINER_ID_SERVERLIST = -1
14
15         ITEM_TYPE_AUDIO = "audio"
16         ITEM_TYPE_CONTAINER = "container"
17         ITEM_TYPE_PICTURE = "picture"
18         ITEM_TYPE_SERVER = "server"
19         ITEM_TYPE_FILE = "file"
20         ITEM_TYPE_VIDEO = "video"
21
22         META_ALBUM = 'album'
23         META_ALBUM_ART_URI = 'album_art_uri'
24         META_ARTIST = 'artist'
25         META_BITRATE = 'bitrate'
26         META_CHILD_COUNT = 'child_count'
27         META_DATE = 'date'
28         META_DURATION = 'duration'
29         META_GENRE = 'genre'
30         META_METATYPE = 'metatype'
31         META_RESOLUTION = 'resolution'
32         META_SIZE = 'size'
33         META_TITLE = 'title'
34         META_TYPE = 'type'
35         META_URI = 'uri'
36         META_COVER_URI = 'cover_uri'
37
38         SORT_TITLE_ASC = "+dc:title"
39         SORT_TITLE_DSC = "+dc:title"
40
41 '''
42 This is a "managed" UPnP A/V Controlpoint which eases the use of UPnP, for Browsing media or adding a Renderer
43 please see the helper classes (UPnPBrowser and AbstractUPnPRenderer) for more
44 '''
45 class ManagedControlPoint(object):
46         def __init__(self):
47                 self.coherence = Coherence({'logmode':'warning'})
48                 self._controlPoint = ControlPoint(self.coherence, auto_client=['MediaServer','MediaRenderer'])
49                 self._controlPoint.connect(self._onMediaServerDetected, 'Coherence.UPnP.ControlPoint.MediaServer.detected')
50                 self._controlPoint.connect(self._onMediaServerRemoved, 'Coherence.UPnP.ControlPoint.MediaServer.removed')
51                 self._controlPoint.connect(self._onMediaRendererDetected, 'Coherence.UPnP.ControlPoint.MediaRenderer.detected')
52                 self._controlPoint.connect(self._onMediaRendererRemoved, 'Coherence.UPnP.ControlPoint.MediaRenderer.removed')
53                 self._controlPoint.connect(self._onMediaDeviceDectected, 'Coherence.UPnP.Device.detection_completed')
54
55                 self.__mediaServerClients = {}
56                 self.__mediaRendererClients = {}
57                 self.__mediaDevices = {}
58
59                 self.__browser = []
60                 self.__devices = []
61
62                 self.onMediaServerDetected = []
63                 self.onMediaServerRemoved  = []
64                 self.onMediaRendererDetected = []
65                 self.onMediaRendererRemoved = []
66                 self.onMediaDeviceDectected = []
67
68         def _onMediaServerDetected(self, client, udn):
69                 print "[DLNA] MediaServer Detected: %s (%s)" % (client.device.get_friendly_name(), client.device.get_friendly_device_type())
70                 self.__mediaServerClients[udn] = client
71                 for fnc in self.onMediaServerDetected:
72                         fnc(udn, client)
73
74         def _onMediaServerRemoved(self, udn):
75                 if self.__mediaServerClients.get(udn, None) != None:
76                         del self.__mediaServerClients[udn]
77                         for fnc in self.onMediaServerRemoved:
78                                 fnc(udn)
79
80         def _onMediaRendererDetected(self, client, udn):
81                 print "[DLNA] MediaRenderer detected: %s (%s, %s)" % (client.device.get_friendly_name(), client.device.get_friendly_device_type(), udn)
82                 self.__mediaRendererClients[udn] = client
83                 for fnc in self.onMediaRendererDetected:
84                         fnc(udn, client)
85
86         def _onMediaRendererRemoved(self, udn):
87                 print "[DLNA] MediaRenderer removed: %s" % (udn)
88                 if self.__mediaRendererClients.get(udn, None) != None:
89                         del self.__mediaRendererClients[udn]
90                         for fnc in self.onMediaRendererRemoved:
91                                 fnc(udn)
92
93         def _onMediaDeviceDectected(self, device):
94                 print "[DLNA] Device found: %s (%s)" % (device.get_friendly_name(), device.get_friendly_device_type())
95                 self.__mediaDevices[device.udn] = device
96
97         def registerRenderer(self, classDef, **kwargs):
98                 renderer = MediaRenderer(self.coherence, classDef, no_thread_needed=True, **kwargs)
99                 self.__devices.append(renderer)
100                 return renderer
101
102         def registerServer(self, classDef, **kwargs):
103                 server = MediaServer(self.coherence, classDef, no_thread_needed=True, **kwargs)
104                 self.__devices.append(server)
105                 return server
106
107         def getServerList(self):
108                 return self.__mediaServerClients.values()
109
110         def getRenderingControlClientList(self):
111                 return self.__mediaRendererClients.values()
112
113         def getDeviceName(self, client):
114                 return Item.ue(client.device.get_friendly_name())
115
116         def shutdown(self):
117                 for device in self.__devices:
118                         device.unregister()
119                 self._controlPoint.shutdown()
120
121 class Item(object):
122         htmlparser = HTMLParser()
123
124         @staticmethod
125         def ue(val):
126                 return Item.htmlparser.unescape(val).encode("utf-8")
127
128         @staticmethod
129         def getItemType(item):
130                 if item != None:
131                         if item.__class__.__name__ == MediaServerClient.__name__:
132                                 return Statics.ITEM_TYPE_SERVER
133
134                         itemClass = Item.ue(item.upnp_class)
135                         if Item.isContainer(item):
136                                 return Statics.ITEM_TYPE_CONTAINER
137
138                         elif itemClass.startswith("object.item"):
139                                 type = item.upnp_class.split('.')[-1]
140                                 if type == "videoItem" or type == "movie":
141                                         return Statics.ITEM_TYPE_VIDEO
142                                 elif type == "musicTrack" or type == "audioItem":
143                                         return Statics.ITEM_TYPE_AUDIO
144                                 elif type == "photo":
145                                         return Statics.ITEM_TYPE_PICTURE
146
147                 return None
148
149         @staticmethod
150         def isServer(item):
151                 return Item.getItemType(item) == Statics.ITEM_TYPE_SERVER
152
153         @staticmethod
154         def getServerName(client):
155                 return Item.ue(client.device.get_friendly_name())
156
157         '''
158         Returns the title of the current item
159         '''
160         @staticmethod
161         def getItemTitle(item):
162                 if Item.isServer(item):
163                         return Item.getServerName(item)
164
165                 if item.title != None:
166                         return Item.ue(item.title)
167                 else:
168                         return "<missing title>"
169
170         '''
171         returns the number of children for container items
172         returns -1 for non-container items
173         '''
174         @staticmethod
175         def getItemChildCount(item):
176                 if Item.getItemType(item) != Statics.ITEM_TYPE_SERVER and Item.isContainer(item):
177                         return item.childCount
178
179                 return -1
180
181         '''
182         Currently always returns a dict with the first url and meta-type, which is usually the original/non-transcoded source
183         Raises an IllegalInstanceException if you pass in a container-item
184         '''
185         @staticmethod
186         def getItemUriMeta(item):
187                 assert not Item.isContainer(item)
188                 for res in item.res:
189                         uri = Item.ue(res.data)
190                         meta = Item.ue(res.protocolInfo.split(":")[2])
191                         print "URL: %s\nMeta:%s" %(uri, meta)
192                         if uri:
193                                 return {Statics.META_URI : uri, Statics.META_METATYPE : meta}
194
195         @staticmethod
196         def getItemId(item):
197                 if Item.isServer(item):
198                         return item.device.get_id()
199                 else:
200                         return item.id
201
202         @staticmethod
203         def getAttrOrDefault(instance, attr, default=None):
204                 val = getattr(instance, attr, default) or default
205                 try:
206                         return Item.ue(val)
207                 except:
208                         return val
209
210         @staticmethod
211         def getItemMetadata(item):
212                 type = Item.getItemType(item)
213                 meta = {}
214                 metaNA = _('n/a')
215                 cover_uri = None
216
217                 if type == Statics.ITEM_TYPE_SERVER:
218                         meta = {
219                                         Statics.META_TYPE : type,
220                                         Statics.META_TITLE : Item.getServerName(item),
221                                 }
222
223                 elif type == Statics.ITEM_TYPE_CONTAINER:
224                         meta = {
225                                         Statics.META_TYPE : type,
226                                         Statics.META_TITLE : Item.getAttrOrDefault(item, 'title', metaNA),
227                                         Statics.META_CHILD_COUNT : Item.getItemChildCount(item),
228                                 }
229                 elif type == Statics.ITEM_TYPE_PICTURE or type == Statics.ITEM_TYPE_VIDEO:
230                         for res in item.res:
231                                 content_format = Item.ue( res.protocolInfo.split(':')[2] )
232                                 if ( type == Statics.ITEM_TYPE_VIDEO and content_format.startswith("video") ) or type == Statics.ITEM_TYPE_PICTURE:
233                                         meta = {
234                                                         Statics.META_TYPE : type,
235                                                         Statics.META_METATYPE : content_format,
236                                                         Statics.META_TITLE : Item.getAttrOrDefault(item, 'title', metaNA),
237                                                         Statics.META_DATE : Item.getAttrOrDefault(item, 'date', metaNA),
238                                                         Statics.META_RESOLUTION : Item.getAttrOrDefault(item, 'resolution', metaNA),
239                                                         Statics.META_SIZE : Item.getAttrOrDefault(item, 'size', -1),
240                                                         Statics.META_URI : Item.getAttrOrDefault(res, 'data'),
241                                                 }
242                                 elif type == Statics.ITEM_TYPE_VIDEO and content_format.startswith("image"):
243                                         cover_uri = Item.getAttrOrDefault(res, 'data')
244
245                                 if type == Statics.ITEM_TYPE_PICTURE:
246                                         meta[Statics.META_ALBUM] = Item.getAttrOrDefault(item, 'album', metaNA)
247                                 elif type == Statics.ITEM_TYPE_VIDEO:
248                                         meta[Statics.META_ALBUM_ART_URI] = Item.getAttrOrDefault(item, 'albumArtURI')
249
250                 elif type == Statics.ITEM_TYPE_AUDIO:
251                         for res in item.res:
252                                 content_format = Item.ue( res.protocolInfo.split(':')[2] )
253                                 if content_format.startswith("audio"):
254                                         meta = {
255                                                         Statics.META_TYPE : type,
256                                                         Statics.META_METATYPE : content_format,
257                                                         Statics.META_TITLE : Item.getAttrOrDefault(item, 'title', metaNA),
258                                                         Statics.META_ALBUM : Item.getAttrOrDefault(item, 'album', metaNA),
259                                                         Statics.META_ARTIST : Item.getAttrOrDefault(item, 'artist', metaNA),
260                                                         Statics.META_GENRE : Item.getAttrOrDefault(item, 'genre', metaNA),
261                                                         Statics.META_DURATION : Item.getAttrOrDefault(item, 'duration', "0"),
262                                                         Statics.META_BITRATE : Item.getAttrOrDefault(item, 'bitrate', "0"),
263                                                         Statics.META_SIZE : Item.getAttrOrDefault(item, 'size', -1),
264                                                         Statics.META_ALBUM_ART_URI : Item.getAttrOrDefault(item, 'albumArtURI'),
265                                                         Statics.META_URI : Item.getAttrOrDefault(res, 'data'),
266                                                 }
267                                 elif content_format.startswith("image"):
268                                         cover_uri = Item.getAttrOrDefault(res, 'data')
269                 if cover_uri != None:
270                         meta[Statics.META_COVER_URI] = cover_uri
271
272                 return meta
273
274         @staticmethod
275         def isContainer(item):
276                 print item.__class__.__name__
277                 if item.__class__.__name__ == MediaServerClient.__name__:
278                         return True
279                 return item.upnp_class.startswith("object.container")