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