4.2.3r4
[enigma2.git] / usr / lib / enigma2 / python / Plugins / SystemPlugins / UPnP / UPnPCore.py
1 # -*- coding: UTF-8 -*-
2 from Components.ResourceManager import resourcemanager
3
4 from coherence.base import Coherence
5 from coherence.upnp.core import DIDLLite
6 from coherence.upnp.devices.control_point import ControlPoint
7 from coherence.upnp.devices.media_renderer import MediaRenderer
8 from coherence.upnp.devices.media_server import MediaServer
9 from coherence.upnp.devices.media_server_client import MediaServerClient
10 from HTMLParser import HTMLParser
11
12 class Statics:
13         CONTAINER_ID_ROOT = 0
14         CONTAINER_ID_SERVERLIST = -1
15
16         ITEM_TYPE_AUDIO = "audio"
17         ITEM_TYPE_CONTAINER = "container"
18         ITEM_TYPE_PICTURE = "picture"
19         ITEM_TYPE_SERVER = "server"
20         ITEM_TYPE_FILE = "file"
21         ITEM_TYPE_VIDEO = "video"
22
23         META_ALBUM = 'album'
24         META_ALBUM_ART_URI = 'album_art_uri'
25         META_ARTIST = 'artist'
26         META_BITRATE = 'bitrate'
27         META_CHILD_COUNT = 'child_count'
28         META_DATE = 'date'
29         META_DURATION = 'duration'
30         META_GENRE = 'genre'
31         META_METATYPE = 'metatype'
32         META_RESOLUTION = 'resolution'
33         META_SIZE = 'size'
34         META_TITLE = 'title'
35         META_TYPE = 'type'
36         META_URI = 'uri'
37         META_COVER_URI = 'cover_uri'
38
39         SORT_TITLE_ASC = "+dc:title"
40         SORT_TITLE_DSC = "+dc:title"
41
42
43 def hs(intval):
44         """
45         Convert an int into a 2-character hex string without the 0x prepended
46         e.g.: 1 -> 01
47         """
48         return "%0.2X" %(intval)
49
50 class DLNA(object):
51         """
52         DLNA.ORG_CI: conversion indicator parameter (boolean integer)
53         0 not transcoded
54         1 transcoded
55         """
56         DLNA_ORG_CONVERSION_KEY = "DLNA_ORG.CI"
57         DLNA_ORG_CONVERSION_NONE = 0,
58         DLNA_ORG_CONVERSION_TRANSCODED = 1,
59
60         """
61         DLNA.ORG_OP: operations parameter (string)
62         "00" (or "0") neither time seek range nor range supported
63         "01" range supported
64         "10" time seek range supported
65         "11" both time seek range and range supported
66         """
67         DLNA_ORG_OPERATION_KEY = "DLNA_ORG.OP"
68         DLNA_ORG_OPERATION_NONE         = 0x00,
69         DLNA_ORG_OPERATION_RANGE        = 0x01,
70         DLNA_ORG_OPERATION_TIMESEEK     = 0x10,
71
72         DLNA_ORG_PROFILE_NAME_KEY = "DLNA_ORG.PN"
73
74         LUT_MIME = {
75                         "mp3"  : "audio/mpeg",
76                         "aac"  : "audio/mp4",
77                         "flac" : "audio/ogg",
78                         "mkv"  : "video/x-matroska",
79                         "mp4"  : "video/mp4",
80                         "ts"   : "video/mpeg",
81                         "mpg"  : "video/mpeg",
82                 }
83
84         LUT_PARAMS = {
85                         "video/mpeg" : ";".join(["DLNA.ORG_PN=MPEG_TS_SD_EU"] + DIDLLite.simple_dlna_tags),
86                 }
87
88         @staticmethod
89         def getMimeType(codec, default="audio/mpeg"):
90                 return DLNA.LUT_MIME.get(codec, default)
91
92         @staticmethod
93         def getParams(mimetype, default="*"):
94                 return DLNA.LUT_PARAMS.get(mimetype, default)
95
96 '''
97 This is a "managed" UPnP A/V Controlpoint which eases the use of UPnP, for Browsing media or adding a Renderer
98 please see the helper classes (UPnPBrowser and AbstractUPnPRenderer) for more
99 '''
100 class ManagedControlPoint(object):
101         def __init__(self):
102                 self.coherence = Coherence({'logmode':'warning'})
103                 self._controlPoint = ControlPoint(self.coherence, auto_client=['MediaServer','MediaRenderer'])
104                 self._controlPoint.connect(self._onMediaServerDetected, 'Coherence.UPnP.ControlPoint.MediaServer.detected')
105                 self._controlPoint.connect(self._onMediaServerRemoved, 'Coherence.UPnP.ControlPoint.MediaServer.removed')
106                 self._controlPoint.connect(self._onMediaRendererDetected, 'Coherence.UPnP.ControlPoint.MediaRenderer.detected')
107                 self._controlPoint.connect(self._onMediaRendererRemoved, 'Coherence.UPnP.ControlPoint.MediaRenderer.removed')
108                 self._controlPoint.connect(self._onMediaDeviceDectected, 'Coherence.UPnP.Device.detection_completed')
109
110                 self.__mediaServerClients = {}
111                 self.__mediaRendererClients = {}
112                 self.__mediaDevices = {}
113
114                 self.__browser = []
115                 self.__devices = []
116
117                 self.onMediaServerDetected = []
118                 self.onMediaServerRemoved  = []
119                 self.onMediaRendererDetected = []
120                 self.onMediaRendererRemoved = []
121                 self.onMediaDeviceDectected = []
122
123         def _onMediaServerDetected(self, client, udn):
124                 print "[DLNA] MediaServer Detected: %s (%s)" % (client.device.get_friendly_name(), client.device.get_friendly_device_type())
125                 self.__mediaServerClients[udn] = client
126                 for fnc in self.onMediaServerDetected:
127                         fnc(udn, client)
128
129         def _onMediaServerRemoved(self, udn):
130                 if self.__mediaServerClients.get(udn, None) != None:
131                         del self.__mediaServerClients[udn]
132                         for fnc in self.onMediaServerRemoved:
133                                 fnc(udn)
134
135         def _onMediaRendererDetected(self, client, udn):
136                 print "[DLNA] MediaRenderer detected: %s (%s, %s)" % (client.device.get_friendly_name(), client.device.get_friendly_device_type(), udn)
137                 self.__mediaRendererClients[udn] = client
138                 for fnc in self.onMediaRendererDetected:
139                         fnc(udn, client)
140
141         def _onMediaRendererRemoved(self, udn):
142                 print "[DLNA] MediaRenderer removed: %s" % (udn)
143                 if self.__mediaRendererClients.get(udn, None) != None:
144                         del self.__mediaRendererClients[udn]
145                         for fnc in self.onMediaRendererRemoved:
146                                 fnc(udn)
147
148         def _onMediaDeviceDectected(self, device):
149                 print "[DLNA] Device found: %s (%s)" % (device.get_friendly_name(), device.get_friendly_device_type())
150                 self.__mediaDevices[device.udn] = device
151
152         def registerRenderer(self, classDef, **kwargs):
153                 renderer = MediaRenderer(self.coherence, classDef, no_thread_needed=True, **kwargs)
154                 self.__devices.append(renderer)
155                 return renderer
156
157         def registerServer(self, classDef, **kwargs):
158                 server = MediaServer(self.coherence, classDef, no_thread_needed=True, **kwargs)
159                 self.__devices.append(server)
160                 return server
161
162         def getServerList(self):
163                 return self.__mediaServerClients.values()
164
165         def getRenderingControlClientList(self):
166                 return self.__mediaRendererClients.values()
167
168         def getDeviceName(self, client):
169                 return Item.ue(client.device.get_friendly_name())
170
171         def getDevice(self, uuid):
172                 for device in self.__devices:
173                         if device.uuid == uuid:
174                                 return device
175                 return None
176
177         def removeDevice(self, uuid):
178                 device = self.getDevice(uuid)
179                 if device:
180                         device.unregister()
181                         self.__devices.remove(device)
182                         return True
183                 return False
184
185         def shutdown(self):
186                 for device in self.__devices:
187                         device.unregister()
188                 self._controlPoint.shutdown()
189
190 class Item(object):
191         htmlparser = HTMLParser()
192
193         @staticmethod
194         def ue(val):
195                 return Item.htmlparser.unescape(val).encode("utf-8")
196
197         @staticmethod
198         def getItemType(item):
199                 if item != None:
200                         if item.__class__.__name__ == MediaServerClient.__name__:
201                                 return Statics.ITEM_TYPE_SERVER
202
203                         itemClass = Item.ue(item.upnp_class)
204                         if Item.isContainer(item):
205                                 return Statics.ITEM_TYPE_CONTAINER
206
207                         elif itemClass.startswith("object.item"):
208                                 type = item.upnp_class.split('.')[-1]
209                                 if type == "videoItem" or type == "movie":
210                                         return Statics.ITEM_TYPE_VIDEO
211                                 elif type == "musicTrack" or type == "audioItem":
212                                         return Statics.ITEM_TYPE_AUDIO
213                                 elif type == "photo":
214                                         return Statics.ITEM_TYPE_PICTURE
215
216                 return None
217
218         @staticmethod
219         def isServer(item):
220                 return Item.getItemType(item) == Statics.ITEM_TYPE_SERVER
221
222         @staticmethod
223         def getServerName(client):
224                 return Item.ue(client.device.get_friendly_name())
225
226         '''
227         Returns the title of the current item
228         '''
229         @staticmethod
230         def getItemTitle(item):
231                 if Item.isServer(item):
232                         return Item.getServerName(item)
233
234                 if item.title != None:
235                         return Item.ue(item.title)
236                 else:
237                         return "<missing title>"
238
239         '''
240         returns the number of children for container items
241         returns -1 for non-container items
242         '''
243         @staticmethod
244         def getItemChildCount(item):
245                 if Item.getItemType(item) != Statics.ITEM_TYPE_SERVER and Item.isContainer(item):
246                         return item.childCount
247
248                 return -1
249
250         '''
251         Currently always returns a dict with the first url and meta-type, which is usually the original/non-transcoded source
252         Raises an IllegalInstanceException if you pass in a container-item
253         '''
254         @staticmethod
255         def getItemUriMeta(item):
256                 assert not Item.isContainer(item)
257                 for res in item.res:
258                         uri = Item.ue(res.data)
259                         meta = Item.ue(res.protocolInfo.split(":")[2])
260                         print "URL: %s\nMeta:%s" %(uri, meta)
261                         if uri:
262                                 return {Statics.META_URI : uri, Statics.META_METATYPE : meta}
263
264         @staticmethod
265         def getItemId(item):
266                 if Item.isServer(item):
267                         return item.device.get_id()
268                 else:
269                         return item.id
270
271         @staticmethod
272         def getAttrOrDefault(instance, attr, default=None):
273                 val = getattr(instance, attr, default) or default
274                 try:
275                         return Item.ue(val)
276                 except:
277                         return val
278
279         @staticmethod
280         def getItemMetadata(item):
281                 type = Item.getItemType(item)
282                 meta = {}
283                 metaNA = _('n/a')
284                 cover_uri = None
285
286                 if type == Statics.ITEM_TYPE_SERVER:
287                         meta = {
288                                         Statics.META_TYPE : type,
289                                         Statics.META_TITLE : Item.getServerName(item),
290                                 }
291
292                 elif type == Statics.ITEM_TYPE_CONTAINER:
293                         meta = {
294                                         Statics.META_TYPE : type,
295                                         Statics.META_TITLE : Item.getAttrOrDefault(item, 'title', metaNA),
296                                         Statics.META_CHILD_COUNT : Item.getItemChildCount(item),
297                                 }
298                 elif type == Statics.ITEM_TYPE_PICTURE or type == Statics.ITEM_TYPE_VIDEO:
299                         for res in item.res:
300                                 content_format = Item.ue( res.protocolInfo.split(':')[2] )
301                                 if ( type == Statics.ITEM_TYPE_VIDEO and content_format.startswith("video") ) or type == Statics.ITEM_TYPE_PICTURE:
302                                         meta = {
303                                                         Statics.META_TYPE : type,
304                                                         Statics.META_METATYPE : content_format,
305                                                         Statics.META_TITLE : Item.getAttrOrDefault(item, 'title', metaNA),
306                                                         Statics.META_DATE : Item.getAttrOrDefault(item, 'date', metaNA),
307                                                         Statics.META_RESOLUTION : Item.getAttrOrDefault(item, 'resolution', metaNA),
308                                                         Statics.META_SIZE : Item.getAttrOrDefault(item, 'size', -1),
309                                                         Statics.META_URI : Item.getAttrOrDefault(res, 'data'),
310                                                 }
311                                 elif type == Statics.ITEM_TYPE_VIDEO and content_format.startswith("image"):
312                                         cover_uri = Item.getAttrOrDefault(res, 'data')
313
314                                 if type == Statics.ITEM_TYPE_PICTURE:
315                                         meta[Statics.META_ALBUM] = Item.getAttrOrDefault(item, 'album', metaNA)
316                                 elif type == Statics.ITEM_TYPE_VIDEO:
317                                         meta[Statics.META_ALBUM_ART_URI] = Item.getAttrOrDefault(item, 'albumArtURI')
318
319                 elif type == Statics.ITEM_TYPE_AUDIO:
320                         for res in item.res:
321                                 content_format = Item.ue( res.protocolInfo.split(':')[2] )
322                                 if content_format.startswith("audio"):
323                                         meta = {
324                                                         Statics.META_TYPE : type,
325                                                         Statics.META_METATYPE : content_format,
326                                                         Statics.META_TITLE : Item.getAttrOrDefault(item, 'title', metaNA),
327                                                         Statics.META_ALBUM : Item.getAttrOrDefault(item, 'album', metaNA),
328                                                         Statics.META_ARTIST : Item.getAttrOrDefault(item, 'artist', metaNA),
329                                                         Statics.META_GENRE : Item.getAttrOrDefault(item, 'genre', metaNA),
330                                                         Statics.META_DURATION : Item.getAttrOrDefault(item, 'duration', "0"),
331                                                         Statics.META_BITRATE : Item.getAttrOrDefault(item, 'bitrate', "0"),
332                                                         Statics.META_SIZE : Item.getAttrOrDefault(item, 'size', -1),
333                                                         Statics.META_ALBUM_ART_URI : Item.getAttrOrDefault(item, 'albumArtURI'),
334                                                         Statics.META_URI : Item.getAttrOrDefault(res, 'data'),
335                                                 }
336                                 elif content_format.startswith("image"):
337                                         cover_uri = Item.getAttrOrDefault(res, 'data')
338                 if cover_uri != None:
339                         meta[Statics.META_COVER_URI] = cover_uri
340
341                 return meta
342
343         @staticmethod
344         def isContainer(item):
345                 if item.__class__.__name__ == MediaServerClient.__name__:
346                         return True
347                 return item.upnp_class.startswith("object.container")
348
349 def removeUPnPDevice(uuid, cp=None):
350         if not cp:
351                 cp = resourcemanager.getResource("UPnPControlPoint")
352         if cp:
353                 return cp.removeDevice(uuid)
354         return False