Added the services for zukebox render
authorAndre L. V. Loureiro <loureiro.andrew@gmail.com>
Wed, 27 May 2009 23:15:29 +0000 (19:15 -0400)
committerAndre L. V. Loureiro <loureiro.andrew@gmail.com>
Wed, 27 May 2009 23:15:29 +0000 (19:15 -0400)
zukebox_render/src/services/__init__.py [new file with mode: 0644]
zukebox_render/src/services/avtransport/__init__.py [new file with mode: 0644]
zukebox_render/src/services/avtransport/av_transport.py [new file with mode: 0644]
zukebox_render/src/services/gst_renderer/__init__.py [new file with mode: 0644]
zukebox_render/src/services/gst_renderer/gst_renderer.py [new file with mode: 0644]
zukebox_render/src/services/render_control/__init__.py [new file with mode: 0644]
zukebox_render/src/services/render_control/render_ctl.py [new file with mode: 0644]

diff --git a/zukebox_render/src/services/__init__.py b/zukebox_render/src/services/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/zukebox_render/src/services/avtransport/__init__.py b/zukebox_render/src/services/avtransport/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/zukebox_render/src/services/avtransport/av_transport.py b/zukebox_render/src/services/avtransport/av_transport.py
new file mode 100644 (file)
index 0000000..95144bb
--- /dev/null
@@ -0,0 +1,261 @@
+
+__all__ = ('AVTransport', )
+
+import os.path
+import platform
+
+from brisa.core import log
+from brisa.upnp.device import Service, ServiceController
+from zukebox_renderer.services.gst_renderer import GSTRenderer
+
+service_name = 'AVTransport'
+service_type = 'urn:schemas-upnp-org:service:AVTransport:1'
+
+
+class AVTransport(Service):
+
+    def __init__(self, xml_path):
+        Service.__init__(self, service_name, service_type, '',
+                         os.path.join(xml_path, 'render-transport-scpd.xml'),
+                         AVTransportControl())
+
+    def get_player(self):
+        return self.control_controller.get_player()
+
+
+class AVTransportControl(ServiceController):
+
+    def __init__(self):
+        ServiceController.__init__(self, service_type)
+        self.av_transport_uri = ''
+        self.number_of_tracks = 0
+
+        self.gst_player = GSTRenderer()
+
+        self.urilist = {}
+        self.transport_state = 'NO_MEDIA_PRESENT'
+        self.transport_status= 'OK'
+        self.transport_speed = 1
+
+    def soap_SetAVTransportURI(self, *args, **kwargs):
+        """Specifies the URI of resource.
+
+        This action specifies the URI of the resource to be controlled
+        by the specified AVTransport instance. It is RECOMMENDED that the
+        AVTransport service checks the MIME-type of the specified resource
+        when executing this action"""
+
+        instance_id = kwargs['InstanceID']
+        current_uri = kwargs['CurrentURI']
+        current_uri_metadata = kwargs['CurrentURIMetaData']
+
+        self.av_transport_uri = current_uri
+        self.number_of_tracks = 1
+        self.gst_player.av_uri = current_uri
+        self.transport_state = 'STOPPED'
+
+        log.info('SetAVTransportURI()')
+
+        return {'SetAVTransportURI': {}}
+
+    def soap_GetMediaInfo(self, *args, **kwargs):
+        """Return information of current Media.
+
+        This action returns information associated with the current
+        media of the specified instance; it has no effect on state."""
+
+        log.info('GetMediaInfo()')
+
+        return {'GetMediaInfoResponse':
+                {'NrTracks': str(self.number_of_tracks),
+                'MediaDuration': str(self.gst_player.query_duration()[0]),
+                'CurrentURI': self.av_transport_uri,
+                'CurrentURIMetaData': '',
+                'NextURI': '', 'NextURIMetaData': '',
+                'PlayMedium': 'NETWORK',
+                'RecordMedium': '',
+                'WriteStatus': ''}}
+
+    def soap_GetMediaInfo_Ext(self, *args, **kwargs):
+        """Return information of current Media and CurrentType argment.
+
+        This action returns information associated with the current
+        media of the specified instance; it has no effect on state.The
+        information returned is identical to the information returned
+        by the GetMediaInfo() action, except for the additionally
+        returned CurrentType argument """
+
+        log.info('GetMediaInfo_Ext()')
+
+        return {'GetMediaInfo_ExtResponse': {'CurrentType': 'TRACK_UNAWARE',
+                'NrTracks': str(self.number_of_tracks),
+                'MediaDuration': str(self.gst_player.query_duration()[0]),
+                'CurrentURI': self.av_transport_uri,
+                'CurrentURIMetaData': '',
+                'NextURI': '', 'NextURIMetaData': '',
+                'PlayMedium': 'NETWORK',
+                'RecordMedium': '',
+                'WriteStatus': ''}}
+
+    def soap_GetTransportInfo(self, *args, **kwargs):
+        """Return informations os current Transport state.
+
+        This action returns information associated with the current
+        transport state of the specified instance; it has no effect on
+        state."""
+
+        self.transport_state = self.gst_player.get_state()
+
+        return {'GetTransportInfoResponse': {'CurrentTransportState':
+                                             self.transport_state,
+                                             'CurrentTransportStatus':
+                                             self.transport_status,
+                                             'CurrentSpeed':
+                                             str(self.transport_speed)}}
+
+    def soap_GetPositionInfo(self, *args, **kwargs):
+        """Return information of the Transporte of the specified instante.
+
+        This action returns information associated with the current
+        position of the transport of the specified instance; it has no
+        effect on state."""
+
+        dur = self.gst_player.query_duration()
+        pos = self.gst_player.query_position()
+
+        duration = dur[0]
+
+        if dur[1] == -1:
+            duration = pos[0]
+
+
+        abs_pos = pos[1]
+        rel_pos = pos[1]
+
+        if dur[1] > 2147483646:
+            abs_pos = pos[1]*2147483646/dur[1]
+
+        if pos[1] == -1:
+            abs_pos = 0
+            rel_pos = 0
+
+        return {'GetPositionInfoResponse': {'Track': '1',
+                                            'TrackDuration': \
+                                            '"'+str(duration)+'"',
+                                            'TrackMetaData': '',
+                                            'TrackURI': \
+                                            self.av_transport_uri,
+                                            'RelTime': '"'+str(pos[0])+'"',
+                                            'AbsTime': '"'+str(pos[0])+'"',
+                                            'RelCount': rel_pos,
+                                            'AbsCount': abs_pos}}
+
+    def soap_GetDeviceCapabilities(self, *args, **kwargs):
+        """Return information on device capabilities.
+
+        This action returns information on device capabilities of
+        the specified instance, such as the supported playback and
+        recording formats, and the supported quality levels for
+        recording. This action has no effect on state."""
+
+        log.info('GetDeviceCapabilities()')
+
+        return {'GetDeviceCapabilitiesResponse': {'PlayMedia': 'NONE',
+                                        'RecMedia': 'NOT_IMPLEMENTED',
+                                        'ReqQualityMode': 'NOT_IMPLEMENTED'}}
+
+    def soap_GetTransportSettings(self, *args, **kwargs):
+        """Return information on various settings of instance.
+
+        This action returns information on various settings of the
+        specified instance, such as the current play mode and the
+        current recording quality mode. This action has no effect on
+        state."""
+
+        log.info('GetTransportSettings()')
+
+        return {'GetTransportSettingsResponse': {'PlayMode': 'DIRECT_1',
+                                        'ReqQualityMode': 'NOT_IMPLEMENTED'}}
+
+    def soap_Stop(self, *args, **kwargs):
+        """Stop progression of current resource.
+
+        This action stops the progression of the current resource that
+        is associated with the specified instance."""
+
+        log.info('Stop()')
+        self.transport_state = 'TRANSITIONING'
+        self.gst_player.stop()
+        self.transport_state = 'STOPPED'
+
+        return {'StopResponse': {}}
+
+    def soap_Play(self, *args, **kwargs):
+        """Play the resource of instance.
+
+        This action starts playing the resource of the specified
+        instance, at the specified speed, starting at the current
+        position, according to the current play mode."""
+
+        log.info('Play()')
+        log.debug('Playing uri: %r', self.gst_player.av_uri)
+        self.transport_state = 'TRANSITIONING'
+
+        self.gst_player.play()
+
+        self.transport_state = 'PLAYING'
+
+        return {'PlayResponse': {}}
+
+    def soap_Pause(self, *args, **kwargs):
+        """Pause the resouce of instance.
+
+        This action starts playing the resource of the specified
+        instance, at the specified speed, starting at the current
+        position, according to the current play mode."""
+
+        log.info('Pause()')
+
+        self.transport_state = 'TRANSITIONING'
+
+        self.gst_player.pause()
+
+        self.transport_state = 'PAUSED_PLAYBACK'
+
+        return {'PauseResponse': {}}
+
+    def soap_Seek(self, *args, **kwargs):
+        """Seek through the resource controlled.
+
+        This action starts seeking through the resource controlled
+        by the specified instance - as fast as possible - to the position,
+        specified in the Target argument."""
+
+        (instance_id, unit, target) = args
+
+        self.gst_player.seek(unit, target)
+
+        log.info('Seek()')
+
+        return {'SeekResponse': {}}
+
+    def soap_Next(self, *args, **kwargs):
+        """Advance to the next track.
+
+        This is a convenient action to advance to the next track. This
+        action is functionally equivalent to Seek(TRACK_NR, CurrentTrackNr+1).
+        This action does not cycle back to the first track."""
+
+        log.info('Next()')
+
+    def soap_Previous(self, *args, **kwargs):
+        """Advance to the previous track.
+
+        This is a convenient action to advance to the previous track. This
+        action is functionally equivalent to Seek(TRACK_NR, CurrentTrackNr-1).
+        This action does not cycle back to the last track."""
+
+        log.info('Previous()')
+
+    def get_player(self):
+        return self.gst_player
diff --git a/zukebox_render/src/services/gst_renderer/__init__.py b/zukebox_render/src/services/gst_renderer/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/zukebox_render/src/services/gst_renderer/gst_renderer.py b/zukebox_render/src/services/gst_renderer/gst_renderer.py
new file mode 100644 (file)
index 0000000..7b53c3d
--- /dev/null
@@ -0,0 +1,137 @@
+
+import pygst
+import time
+pygst.require('0.10')
+import gst
+
+from brisa.core import log
+from brisa.utils.looping_call import LoopingCall
+
+
+class GSTRenderer(object):
+
+    def __init__(self):
+        self.build_pipeline()
+        self.__av_uri = None
+        self.time_format = gst.Format(gst.FORMAT_TIME)
+        self.player_state = 0
+        loop = LoopingCall(self.poll_bus)
+        loop.start(0.2, True)
+
+    def poll_bus(self):
+        if self.bus:
+            message = self.bus.poll(gst.MESSAGE_ERROR|gst.MESSAGE_EOS,
+                                    timeout=1)
+            if message:
+                self.on_message(self.bus, message)
+
+    def get_state(self):
+        if self.player_state == 0:
+            return 'STOPPED'
+        if self.player_state == 1:
+            return 'PLAYING'
+        if self.player_state == 2:
+            return 'PAUSED_PLAYBACK'
+
+    def __set_uri(self, uri):
+        self.player.set_property('uri', uri)
+        self.__av_uri = uri
+
+    def __get_uri(self):
+        return self.__av_uri
+
+    av_uri = property(__get_uri, __set_uri)
+
+    def build_pipeline(self):
+        self.player = gst.element_factory_make("playbin", "player")
+        self.bus = self.player.get_bus()
+        self.player.set_state(gst.STATE_READY)
+
+    def on_message(self, bus, message):
+        t = message.type
+        if t == gst.MESSAGE_EOS:
+            self.player.set_state(gst.STATE_NULL)
+            self.player_state = 0
+        elif t == gst.MESSAGE_ERROR:
+            self.player.set_state(gst.STATE_NULL)
+            self.player_state = 0
+
+    def play(self):
+        if self.av_uri is not None:
+            if (self.player.set_state(gst.STATE_PLAYING) ==
+                gst.STATE_CHANGE_FAILURE):
+                log.error("error trying to play %s.", self.av_uri)
+            self.player_state = 1
+        else:
+            log.info("av_uri is None, unable to play.")
+
+    def stop(self):
+        if self.player.set_state(gst.STATE_READY) == gst.STATE_CHANGE_FAILURE:
+            log.error("error while stopping the player")
+        self.player_state = 0
+
+    def pause(self):
+        if self.player.set_state(gst.STATE_PAUSED) == gst.STATE_CHANGE_FAILURE:
+            log.error("error while pausing the player")
+        self.player_state = 2
+
+    def seek(self, unit, target):
+        if unit == "ABS_TIME":
+            target_time = self.convert_int(target)
+            self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH,
+                                    target_time)
+
+        if unit == "REL_TIME":
+            target_time = self.convert_int(target)
+            cur_pos = self.query_position()[1]
+            self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH,
+                                    target_time+cur_pos)
+
+        if unit == "ABS_COUNT":
+            self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH,
+                                    target)
+
+        if unit == "REL_COUNT":
+            cur_pos = self.query_position()[1]
+            self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH,
+                                    target + cur_pos)
+
+    def set_volume(self, volume):
+        self.player.set_property("volume", volume/10)
+
+    def get_volume(self):
+        return int(self.player.get_property("volume")*10)
+
+    def query_duration(self):
+        time.sleep(0.3)
+        try:
+            dur_int = self.player.query_duration(self.time_format, None)[0]
+            dur_str = self.convert_ns(dur_int)
+        except gst.QueryError:
+            dur_int = -1
+            dur_str = ''
+
+        return dur_str, dur_int
+
+    def query_position(self):
+        try:
+            pos_int = self.player.query_position(self.time_format, None)[0]
+            pos_str = self.convert_ns(pos_int)
+        except gst.QueryError:
+            pos_int = -1
+            pos_str = ''
+
+        return pos_str, pos_int
+
+    def convert_ns(self, time):
+        hours, left_time = divmod(time/1000000000, 3600)
+        minutes, left_time = divmod(left_time, 60)
+        return '%02d:%02d:%02d' % (hours, minutes, left_time)
+
+    def convert_int(self, time_str):
+        time_str = time_str.strip('")( ')
+        (hours, min, sec) = time_str.split(":")
+        time_int = int(hours) * 3600 + int(min) * 60 + int(sec)
+        time_int = time_int * 1000000000
+        return time_int
+
diff --git a/zukebox_render/src/services/render_control/__init__.py b/zukebox_render/src/services/render_control/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/zukebox_render/src/services/render_control/render_ctl.py b/zukebox_render/src/services/render_control/render_ctl.py
new file mode 100644 (file)
index 0000000..c091fc2
--- /dev/null
@@ -0,0 +1,58 @@
+
+import os.path
+
+from brisa.core import log
+from brisa.upnp.device import Service, ServiceController
+
+class RenderingControl(Service):
+
+    def __init__(self, xml_path, gst_player):
+        Service.__init__(self, service_name, service_type, '',
+                         os.path.join(xml_path, 'render-control-scpd.xml'),
+                         RenderingControlController(gst_player))
+
+
+
+class RenderingControlController(ServiceController):
+
+    service_name = 'RenderingControl'
+    service_type = 'urn:schemas-upnp-org:service:RenderingControl:1'
+
+    def __init__(self, gst_player):
+        ServiceController.__init__(self, self.service_type)
+        self.gst_player = gst_player
+
+    def soap_ListPresets(self, *args, **kwargs):
+        """ Return List of currently defined. This action returns a list of
+        the currently defined presets.
+        """
+        log.debug('Action on RenderingControlController: ListPresets()')
+        return {'ListPresetsResponse': {'CurrentPresetNameList': ''}}
+
+    def soap_SelectPreset(self, *args, **kwargs):
+        """ Select Present state variables. This action restores (a subset) of
+        the state variables to the values associated with the specified
+        preset.
+        """
+        log.debug('Action on RenderingControlController: SelectPreset()')
+        return {'SelectPresetResponse': {}}
+
+    def soap_GetVolume(self, *args, **kwargs):
+        """ Return the current volume state. This action retrieves the current
+        value of the Volume state variable of the specified channel for the
+        specified instance of this service
+        """
+        log.debug('Action on RenderingControlController: GetVolume()')
+        (instance_id, channel) = args
+        volume = int(self.gst_player.get_volume())
+        return {'GetVolumeResponse': {'CurrentVolume': volume}}
+
+    def soap_SetVolume(self, *args, **kwargs):
+        """Set volume of instance and chanel.
+
+        This action sets the Volume state variable of the specified
+        instance and channel to the specified value."""
+        log.debug('Action on RenderingControlController: SetVolume%s', args)
+        (instance_id, channel, desired_volume) = args
+        self.gst_player.set_volume(int(desired_volume))
+        return {'SetVolumeResponse': {}}