Beispiel #1
0
class StopAll(Cue):
    Name = QT_TRANSLATE_NOOP('CueName', 'Stop-All')

    action = Property(default=CueAction.Stop.value)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.name = translate('CueName', self.Name)

    def __start__(self, fade=False):
        for cue in Application().cue_model:
            action = self.__adjust_action(cue, CueAction(self.action))
            if action:
                cue.execute(action=action)

        return False

    def __adjust_action(self, cue, action, fade=False):
        if action in cue.CueActions:
            return action
        elif action is CueAction.FadeOutPause:
            return self.__adjust_action(cue, CueAction.Pause, True)
        elif action is CueAction.Pause and fade:
            return self.__adjust_action(cue, CueAction.FadeOutStop)
        elif action is CueAction.FadeOutInterrupt:
            return self.__adjust_action(cue, CueAction.Interrupt)
        elif action is CueAction.FadeOutStop:
            return self.__adjust_action(cue, CueAction.Stop)

        return None
Beispiel #2
0
class SeekCue(Cue):
    Name = QT_TRANSLATE_NOOP('CueName', 'Seek Cue')

    target_id = Property()
    time = Property(default=-1)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.name = translate('CueName', self.Name)

    def __start__(self, fade=False):
        cue = Application().cue_model.get(self.target_id)
        if isinstance(cue, MediaCue) and self.time >= 0:
            cue.media.seek(self.time)

        return False
Beispiel #3
0
class Speed(GstMediaElement):
    ElementType = ElementType.Plugin
    MediaType = MediaType.Audio
    Name = QT_TRANSLATE_NOOP('MediaElementName', 'Speed')

    speed = Property(default=1.0)

    def __init__(self, pipeline):
        super().__init__()

        self.pipeline = pipeline
        self.scale_tempo = Gst.ElementFactory.make("scaletempo", None)
        self.audio_convert = Gst.ElementFactory.make("audioconvert", None)

        self.pipeline.add(self.scale_tempo)
        self.pipeline.add(self.audio_convert)

        self.scale_tempo.link(self.audio_convert)

        bus = self.pipeline.get_bus()
        bus.add_signal_watch()
        self._handler = bus.connect("message", self.__on_message)

        self._old_speed = self.speed
        self.changed('speed').connect(self.__prepare_speed)

    def __prepare_speed(self, value):
        if self._old_speed != value:
            self._old_speed = value

            if self.pipeline.current_state == Gst.State.PLAYING:
                self.__change_speed()

    def sink(self):
        return self.scale_tempo

    def src(self):
        return self.audio_convert

    def dispose(self):
        bus = self.pipeline.get_bus()
        bus.remove_signal_watch()
        bus.disconnect(self._handler)

    def __on_message(self, bus, message):
        if (message.type == Gst.MessageType.STATE_CHANGED and
                    message.src == self.scale_tempo and
                    message.parse_state_changed()[1] == Gst.State.PLAYING):
            self.__change_speed()

    def __change_speed(self):
        current_position = self.scale_tempo.query_position(Gst.Format.TIME)[1]

        self.scale_tempo.seek(self.speed,
                              Gst.Format.TIME,
                              Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE,
                              Gst.SeekType.SET,
                              current_position,
                              Gst.SeekType.NONE,
                              0)
class GstSrcElement(GstMediaElement):
    ElementType = ElementType.Input

    duration = Property(default=0)

    def input_uri(self):
        """Return the input uri or None"""
        return None
Beispiel #5
0
class UriInput(GstSrcElement):
    MediaType = MediaType.Audio
    Name = QT_TRANSLATE_NOOP('MediaElementName', 'URI Input')

    uri = GstProperty('decoder', default='')
    download = GstProperty('decoder', default=False)
    buffer_size = GstProperty('decoder', default=-1, gst_name='buffer-size')
    use_buffering = GstProperty('decoder', default=False,
                                gst_name='use-buffering')
    _mtime = Property(default=-1)

    def __init__(self, pipe):
        super().__init__()

        self.decoder = Gst.ElementFactory.make("uridecodebin", None)
        self.audio_convert = Gst.ElementFactory.make("audioconvert", None)
        self._handler = self.decoder.connect("pad-added", self.__on_pad_added)

        pipe.add(self.decoder)
        pipe.add(self.audio_convert)

        self.changed('uri').connect(self.__uri_changed)

    def input_uri(self):
        return self.uri

    def dispose(self):
        self.decoder.disconnect(self._handler)

    def src(self):
        return self.audio_convert

    def __on_pad_added(self, *args):
        self.decoder.link(self.audio_convert)

    def __uri_changed(self, value):
        # Save the current mtime (file flag for last-change time)
        mtime = self._mtime
        # If the uri is a file, then update the current mtime
        if value.split('://')[0] == 'file':
            if path.exists(value.split('//')[1]):
                self._mtime = path.getmtime(value.split('//')[1])
        else:
            mtime = None

        # If something is changed or the duration is invalid
        if mtime != self._mtime or self.duration < 0:
            self.__duration()

    @async_in_pool(pool=ThreadPoolExecutor(1))
    def __duration(self):
        self.duration = gst_uri_duration(self.uri)
Beispiel #6
0
    def __init__(self):
        super().__init__()
        self.__handlers = {}

        # Register a Cue property to store settings
        Cue.register_property('triggers', Property({}))
        # Cue.triggers -> {trigger: [(target_id, action), ...]}

        # Register SettingsPage
        CueSettingsRegistry().add_item(TriggersSettings)

        Application().cue_model.item_added.connect(self.__cue_added)
        Application().cue_model.item_removed.connect(self.__cue_removed)
class IndexActionCue(Cue):
    Name = QT_TRANSLATE_NOOP('CueName', 'Index Action')

    target_index = Property(default=-1)
    relative = Property(default=True)
    action = Property(default=CueAction.Stop.value)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.name = translate('CueName', self.Name)

    def __start__(self, fade=False):
        if self.relative:
            index = self.index + self.target_index
        else:
            index = self.target_index

        try:
            cue = Application().layout.model_adapter.item(index)
            if cue is not self:
                cue.execute(CueAction(self.action))
        except IndexError:
            pass
class CollectionCue(Cue):
    Name = QT_TRANSLATE_NOOP('CueName', 'Collection Cue')

    targets = Property(default=[])

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.name = translate('CueName', self.Name)

    def __start__(self, fade=False):
        for target_id, action in self.targets:
            cue = Application().cue_model.get(target_id)
            if cue is not self:
                cue.execute(action=CueAction[action])

        return False
Beispiel #9
0
    def __init__(self):
        super().__init__()
        self.__client = OlaTimecode()
        self.__cues = set()

        # Register a new Cue property to store settings
        Cue.register_property('timecode', Property(default={}))

        # Register cue-settings-page
        CueSettingsRegistry().add_item(TimecodeCueSettings, MediaCue)
        # Register pref-settings-page
        AppSettings.register_settings_widget(TimecodeSettings)

        # Watch cue-model changes
        Application().cue_model.item_added.connect(self.__cue_added)
        Application().cue_model.item_removed.connect(self.__cue_removed)
Beispiel #10
0
    def __init__(self):
        super().__init__()
        self.__map = {}
        self.__actions_map = {}
        self.__protocols = {}

        # Register a new Cue property to store settings
        Cue.register_property('controller', Property(default={}))

        # Listen cue_model changes
        Application().cue_model.item_added.connect(self.__cue_added)
        Application().cue_model.item_removed.connect(self.__cue_removed)

        # Register settings-page
        CueSettingsRegistry().add_item(ControllerSettings)
        # Load available protocols
        self.__load_protocols()
Beispiel #11
0
class MidiCue(Cue):
    Name = QT_TRANSLATE_NOOP('CueName', 'MIDI Cue')

    message = Property(default='')

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.name = translate('CueName', self.Name)

        midi_out = MIDIOutput()
        if not midi_out.is_open():
            midi_out.open()

    def __start__(self, fade=False):
        if self.message:
            MIDIOutput().send_from_str(self.message)

        return False
Beispiel #12
0
class Cue(HasProperties):
    """Cue(s) are the base component for implement any kind of live-controllable
    element (live = during a show).

    A cue implement his behavior(s) reimplementing __start__, __stop__,
    __pause__ and __interrupt__ methods.

    Cue provide **(and any subclass should do the same)** properties via
    HasProperties/Property specifications.

    :ivar _type_: Cue type (class name). Should NEVER change after init.
    :ivar id: Identify the cue uniquely. Should NEVER change after init.
    :ivar index: Cue position in the view.
    :ivar name: Cue visualized name.
    :ivar description: Cue text description.
    :ivar stylesheet: Cue style, used by the view.
    :ivar duration: The cue duration in milliseconds. (0 means no duration)
    :ivar pre_wait: Cue pre-wait in seconds.
    :ivar post_wait: Cue post-wait in seconds.
    :ivar next_action: What do after post_wait.
    :ivar fadein_type: Fade-In type
    :ivar fadeout_type: Fade-Out type
    :ivar fadein_duration: Fade-In duration in seconds
    :ivar fadeout_duration: Fade-Out duration in seconds
    :ivar default_start_action: action to execute to start
    :ivar default_stop_action: action to execute to stop
    :cvar CueActions: actions supported by the cue (default: CueAction.Start)

    A cue should declare CueAction.Default as supported only if CueAction.Start
    and CueAction.Stop are both supported.
    If CueAction.Stop is supported, CueAction.Interrupt should be supported.

    .. Note::
        If 'next_action' is AutoFollow or DoNothing, the postwait is not
        performed.
    """
    Name = 'Cue'

    _type_ = WriteOnceProperty()
    id = WriteOnceProperty()
    name = Property(default='Untitled')
    index = Property(default=-1)
    description = Property(default='')
    stylesheet = Property(default='')
    duration = Property(default=0)
    pre_wait = Property(default=0)
    post_wait = Property(default=0)
    next_action = Property(default=CueNextAction.DoNothing.value)
    fadein_type = Property(default=FadeInType.Linear.name)
    fadeout_type = Property(default=FadeOutType.Linear.name)
    fadein_duration = Property(default=0)
    fadeout_duration = Property(default=0)
    default_start_action = Property(default=CueAction.Start.value)
    default_stop_action = Property(default=CueAction.Stop.value)

    CueActions = (CueAction.Start,)

    def __init__(self, id=None):
        super().__init__()
        self.id = str(uuid4()) if id is None else id
        self._type_ = self.__class__.__name__

        self._st_lock = Lock()
        self._state = CueState.Stop
        self._prewait = RWait()
        self._postwait = RWait()

        # Pre-Wait signals
        self.prewait_start = self._prewait.start
        self.prewait_ended = self._prewait.ended
        self.prewait_paused = self._prewait.paused
        self.prewait_stopped = self._prewait.stopped

        # Post-Wait signals
        self.postwait_start = self._postwait.start
        self.postwait_ended = self._postwait.ended
        self.postwait_paused = self._postwait.paused
        self.postwait_stopped = self._postwait.stopped

        # Fade signals
        self.fadein_start = Signal()
        self.fadein_end = Signal()
        self.fadeout_start = Signal()
        self.fadeout_end = Signal()

        # Status signals
        self.interrupted = Signal()     # self
        self.started = Signal()         # self
        self.stopped = Signal()         # self
        self.paused = Signal()          # self
        self.error = Signal()           # self, error, details
        self.next = Signal()            # self
        self.end = Signal()             # self

        self.changed('next_action').connect(self.__next_action_changed)

    def execute(self, action=CueAction.Default):
        """Execute the specified action, if supported.

        .. Note::
            Even if not specified in Cue.CueActions, when CueAction.Default
            is given, a "default" action is selected depending on the current
            cue state, if this action is not supported nothing will be done.

        :param action: the action to be performed
        :type action: CueAction
        """
        if action == CueAction.Default:
            if self._state & CueState.IsRunning:
                action = CueAction(self.default_stop_action)
            else:
                action = CueAction(self.default_start_action)

        if action is CueAction.Interrupt:
            self.interrupt()
        elif action is CueAction.FadeOutInterrupt:
            self.interrupt(fade=True)
        elif action in self.CueActions:
            if action == CueAction.Start:
                self.start()
            elif action == CueAction.FadeInStart:
                self.start(fade=self.fadein_duration > 0)
            elif action == CueAction.Stop:
                self.stop()
            elif action == CueAction.FadeOutStop:
                self.stop(fade=self.fadeout_duration > 0)
            elif action == CueAction.Pause:
                self.pause()
            elif action == CueAction.FadeOutPause:
                self.pause(fade=self.fadeout_duration > 0)
            elif action == CueAction.FadeOut:
                duration = config['Cue'].getfloat('FadeActionDuration')
                fade_type = FadeOutType[config['Cue'].get('FadeActionType')]
                self.fadeout(duration, fade_type)
            elif action == CueAction.FadeIn:
                duration = config['Cue'].getfloat('FadeActionDuration')
                fade_type = FadeInType[config['Cue'].get('FadeActionType')]
                self.fadein(duration, fade_type)

    @async_function
    def start(self, fade=False):
        """Start the cue."""

        # If possible acquire the state-lock, otherwise return
        if not self._st_lock.acquire(blocking=False):
            return

        try:
            # If we are already running release and return
            if self._state & CueState.IsRunning:
                return

            state = self._state

            # PreWait
            if self.pre_wait and state & (CueState.IsStopped |
                                          CueState.PreWait_Pause):
                self._state = CueState.PreWait
                # Start the wait, the lock is released during the wait and
                # re-acquired after
                if not self._prewait.wait(self.pre_wait, lock=self._st_lock):
                    # PreWait interrupted, check the state to be correct
                    if self._state & CueState.PreWait:
                        self._state ^= CueState.PreWait
                    return

            # Cue-Start (still locked), the __start__ function should not block
            if state & (CueState.IsStopped |
                        CueState.Pause |
                        CueState.PreWait_Pause):

                running = self.__start__(fade)
                self._state = CueState.Running
                self.started.emit(self)

                if not running:
                    self._ended()

            # PostWait (still locked)
            if state & (CueState.IsStopped |
                        CueState.PreWait_Pause |
                        CueState.PostWait_Pause):
                if self.next_action == CueNextAction.AutoNext:
                    self._state |= CueState.PostWait

                    if self._postwait.wait(self.post_wait, lock=self._st_lock):
                        # PostWait ended
                        self._state ^= CueState.PostWait
                        self.next.emit(self)
                    elif self._state & CueState.PostWait:
                        # PostWait interrupted, check the state to be correct
                        self._state ^= CueState.PostWait

                    # If the cue was only post-waiting we remain with
                    # an invalid state
                    if not self._state:
                        self._state = CueState.Stop
        finally:
            self._st_lock.release()

    def restart(self, fade=False):
        """Restart the cue if paused."""
        # TODO: change to resume
        if self._state & CueState.IsPaused:
            self.start(fade)

    def __start__(self, fade=False):
        """Implement the cue `start` behavior.

        Long running task should not block this function (i.e. the fade should
        be performed in another thread).

        When called from `Cue.start()`, `_st_lock` is acquired.

        If the execution is instantaneous should return False, otherwise
        return True and call the `_ended` function later.

        :param fade: True if a fade should be performed (when supported)
        :type fade: bool
        :return: False if the cue is already terminated, True otherwise
            (e.g. asynchronous execution)
        :rtype: bool
        """
        return False

    @async_function
    def stop(self, fade=False):
        """Stop the cue."""

        # If possible acquire the state-lock, otherwise return
        if not self._st_lock.acquire(blocking=False):
            return

        try:
            # Stop PreWait (if in PreWait(_Pause) nothing else is "running")
            if self._state & (CueState.PreWait | CueState.PreWait_Pause):
                self._state = CueState.Stop
                self._prewait.stop()
            else:
                # Stop PostWait
                if self._state & (CueState.PostWait | CueState.PostWait_Pause):
                    # Remove PostWait or PostWait_Pause state
                    self._state = (
                        (self._state ^ CueState.PostWait) &
                        (self._state ^ CueState.PostWait_Pause)
                    )
                    self._postwait.stop()

                # Stop the cue
                if self._state & (CueState.Running | CueState.Pause):
                    # Here the __stop__ function should release and re-acquire
                    # the state-lock during a fade operation
                    if not self.__stop__(fade):
                        # Stop operation interrupted
                        return

                    # Remove Running or Pause state
                    self._state = (
                        (self._state ^ CueState.Running) &
                        (self._state ^ CueState.Pause)
                    )
                    self._state |= CueState.Stop
                    self.stopped.emit(self)
        finally:
            self._st_lock.release()

    def __stop__(self, fade=False):
        """Implement the cue `stop` behavior.

        Long running task should block this function (i.e. the fade should
        "block" this function), when this happen `_st_lock` must be released and
        then re-acquired.

        If called during a `fadeout` operation this should be interrupted,
        the cue stopped and return `True`.

        :param fade: True if a fade should be performed (when supported)
        :type fade: bool
        :return: False if interrupted, True otherwise
        :rtype: bool
        """
        return False

    @async_function
    def pause(self, fade=False):
        """Pause the cue."""

        # If possible acquire the state-lock, otherwise return
        if not self._st_lock.acquire(blocking=False):
            return

        try:
            # Pause PreWait (if in PreWait nothing else is "running")
            if self._state & CueState.PreWait:
                self._state ^= CueState.PreWait
                self._state |= CueState.PreWait_Pause
                self._prewait.pause()
            else:
                # Pause PostWait
                if self._state & CueState.PostWait:
                    self._state ^= CueState.PostWait
                    self._state |= CueState.PostWait_Pause
                    self._postwait.pause()

                # Pause the cue
                if self._state & CueState.Running:
                    # Here the __pause__ function should release and re-acquire
                    # the state-lock during a fade operation
                    if not self.__pause__(fade):
                        return

                    self._state ^= CueState.Running
                    self._state |= CueState.Pause
                    self.paused.emit(self)
        finally:
            self._st_lock.release()

    def __pause__(self, fade=False):
        """Implement the cue `pause` behavior.

        Long running task should block this function (i.e. the fade should
        "block" this function), when this happen `_st_lock` must be released and
        then re-acquired.

        If called during a `fadeout` operation this should be interrupted,
        the cue paused and return `True`.

        If during the execution the fade operation is interrupted this function
        must return `False`.

        :param fade: True if a fade should be performed (when supported)
        :type fade: bool
        :return: False if interrupted, True otherwise
        :rtype: bool
        """
        return False

    @async_function
    def interrupt(self, fade=False):
        """Interrupt the cue.

        :param fade: True if a fade should be performed (when supported)
        :type fade: bool
        """
        with self._st_lock:
            # Stop PreWait (if in PreWait(_Pause) nothing else is "running")
            if self._state & (CueState.PreWait | CueState.PreWait_Pause):
                self._state = CueState.Stop
                self._prewait.stop()
            else:
                # Stop PostWait
                if self._state & (CueState.PostWait | CueState.PostWait_Pause):
                    # Remove PostWait or PostWait_Pause state
                    self._state = (
                        (self._state ^ CueState.PostWait) &
                        (self._state ^ CueState.PostWait_Pause)
                    )
                    self._postwait.stop()

                # Interrupt the cue
                if self._state & (CueState.Running | CueState.Pause):
                    self.__interrupt__(fade)

                    # Remove Running or Pause state
                    self._state = (
                        (self._state ^ CueState.Running) &
                        (self._state ^ CueState.Pause)
                    )
                    self._state |= CueState.Stop
                    self.interrupted.emit(self)

    def __interrupt__(self, fade=False):
        """Implement the cue `interrupt` behavior.

        Long running task should block this function without releasing
        `_st_lock`.

        :param fade: True if a fade should be performed (when supported)
        :type fade: bool
        """

    def fadein(self, duration, fade_type):
        """Fade-in the cue.

        :param duration: How much the fade should be long (in seconds)
        :type duration: float
        :param fade_type: The fade type
        :type fade_type: FadeInType
        """

    def fadeout(self, duration, fade_type):
        """Fade-out the cue.

        :param duration: How much the fade should be long (in seconds)
        :type duration: float
        :param fade_type: The fade type
        :type fade_type: FadeOutType
        """

    def _ended(self):
        """Remove the Running state, if needed set it to Stop."""
        locked = self._st_lock.acquire(blocking=False)

        if self._state == CueState.Running:
            self._state = CueState.Stop
        else:
            self._state ^= CueState.Running

        self.end.emit(self)

        if locked:
            self._st_lock.release()

    def _error(self, message, details):
        """Remove Running/Pause/Stop state and add Error state."""
        locked = self._st_lock.acquire(blocking=False)

        self._state = (
            (self._state ^ CueState.Running) &
            (self._state ^ CueState.Pause) &
            (self._state ^ CueState.Stop)
        ) | CueState.Error

        self.error.emit(self, message, details)

        if locked:
            self._st_lock.release()

    def current_time(self):
        """Return the current execution time if available, otherwise 0.

        :rtype: int
        """
        return 0

    def prewait_time(self):
        return self._prewait.current_time()

    def postwait_time(self):
        return self._postwait.current_time()

    @property
    def state(self):
        """Return the current state.

        :rtype: int
        """
        return self._state

    def __next_action_changed(self, next_action):
        self.end.disconnect(self.next.emit)
        if next_action == CueNextAction.AutoFollow:
            self.end.connect(self.next.emit)
Beispiel #13
0
class Media(HasProperties):
    """Interface for Media objects.

    Media(s) provides control over multimedia contents.
    To control various parameter of the media, MediaElement(s) should be used.

    .. note::
        The play/stop/pause functions must be non-blocking functions.
    """

    loop = Property(default=0)
    duration = Property(default=0)
    start_time = Property(default=0)
    stop_time = Property(default=0)

    def __init__(self):
        super().__init__()

        self.paused = Signal()
        """Emitted when paused (self)"""
        self.played = Signal()
        """Emitted when played (self)"""
        self.stopped = Signal()
        """Emitted when stopped (self)"""
        self.interrupted = Signal()
        """Emitted after interruption (self)"""
        self.eos = Signal()
        """End-of-Stream (self)"""

        self.on_play = Signal()
        """Emitted before play (self)"""
        self.on_stop = Signal()
        """Emitted before stop (self)"""
        self.on_pause = Signal()
        """Emitted before pause (self)"""

        self.sought = Signal()
        """Emitted after a seek (self, position)"""
        self.error = Signal()
        """Emitted when an error occurs (self, error, details)"""

        self.elements_changed = Signal()
        """Emitted when one or more elements are added/removed (self)"""

    @property
    @abstractmethod
    def state(self):
        """
        :return: the media current state
        :rtype: MediaState
        """

    @abstractmethod
    def current_time(self):
        """
        :return: the current playback time in milliseconds or 0
        :rtype: int
        """

    @abstractmethod
    def element(self, class_name):
        """
        :param name: The element class-name
        :type name: str

        :return: The element with the specified class-name or None
        :rtype: lisp.core.base.media_element.MediaElement
        """

    @abstractmethod
    def elements(self):
        """
        :return: All the MediaElement(s) of the media
        :rtype: list
        """

    @abstractmethod
    def elements_properties(self):
        """
        :return: Media elements configurations
        :rtype: dict
        """

    @abstractmethod
    def input_uri(self):
        """
        :return: The media input uri (e.g. "file:///home/..."), or None
        :rtype: str
        """

    @abstractmethod
    def interrupt(self):
        """Interrupt the playback (no fade) and go in STOPPED state."""

    @abstractmethod
    def pause(self):
        """The media go in PAUSED state and pause the playback."""

    @abstractmethod
    def play(self):
        """The media go in PLAYING state and starts the playback."""

    @abstractmethod
    def seek(self, position):
        """Seek to the specified point.

        :param position: The position to be reached in milliseconds
        :type position: int
        """

    @abstractmethod
    def stop(self):
        """The media go in STOPPED state and stop the playback."""

    @abstractmethod
    def update_elements(self, settings):
        """Update the elements configuration.
class CommandCue(Cue):
    """Cue able to execute system commands.

    Implemented using :class:`subprocess.Popen` with *shell=True*
    """

    Name = QT_TRANSLATE_NOOP('CueName', 'Command Cue')
    CueActions = (CueAction.Default, CueAction.Start, CueAction.Stop)

    command = Property(default='')
    no_output = Property(default=True)
    no_error = Property(default=True)
    kill = Property(default=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.name = translate('CueName', self.Name)

        self.__process = None
        self.__stopped = False

    def __start__(self, fade=False):
        self.__exec_command()
        return True

    @async_function
    def __exec_command(self):
        if not self.command.strip():
            return

        # If no_output is True, discard all the outputs
        std = subprocess.DEVNULL if self.no_output else None
        # Execute the command
        self.__process = subprocess.Popen(self.command,
                                          shell=True,
                                          stdout=std,
                                          stderr=std)
        rcode = self.__process.wait()

        if rcode == 0 or rcode == -9 or self.no_error:
            # If terminate normally, killed or in no-error mode
            if not self.__stopped:
                self._ended()

            self.__process = None
            self.__stopped = False
        elif not self.no_error:
            # If an error occurs and not in no-error mode
            self._error(
                translate('CommandCue', 'Process ended with an error status.'),
                translate('CommandCue', 'Exit code: ') + str(rcode))

    def __stop__(self, fade=False):
        if self.__process is not None:
            self.__stopped = True
            if self.kill:
                self.__process.kill()
            else:
                self.__process.terminate()

        return True

    __interrupt__ = __stop__
class VolumeControl(Cue):
    Name = QT_TRANSLATE_NOOP('CueName', 'Volume Control')

    target_id = Property()
    fade_type = Property(default=FadeInType.Linear.name)
    volume = Property(default=.0)

    CueActions = (CueAction.Default, CueAction.Start, CueAction.Stop,
                  CueAction.Pause)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.name = translate('CueName', self.Name)

        self.__fader = Fader(None, 'current_volume')
        self.__init_fader()

    def __init_fader(self):
        cue = Application().cue_model.get(self.target_id)

        if isinstance(cue, MediaCue):
            volume = cue.media.element('Volume')
            if volume is not None:
                if volume is not self.__fader.target:
                    self.__fader.target = volume
                return True

        return False

    def __start__(self, fade=False):
        if self.__init_fader():
            if self.__fader.is_paused():
                self.__fader.restart()
                return True

            if self.duration > 0:
                if self.__fader.target.current_volume > self.volume:
                    self.__fade(FadeOutType[self.fade_type])
                    return True
                elif self.__fader.target.current_volume < self.volume:
                    self.__fade(FadeInType[self.fade_type])
                    return True
            else:
                self.__fader.target.current_volume = self.volume

        return False

    def __stop__(self, fade=False):
        self.__fader.stop()
        return True

    def __pause__(self, fade=False):
        self.__fader.pause()
        return True

    __interrupt__ = __stop__

    @async
    def __fade(self, fade_type):
        try:
            self.__fader.prepare()
            ended = self.__fader.fade(round(self.duration / 1000, 2),
                                      self.volume, fade_type)

            # to avoid approximation problems
            self.__fader.target.current_volume = self.volume
            if ended:
                self._ended()
        except Exception as e:
            self._error(
                translate('VolumeControl', 'Error during cue execution'),
                str(e))

    def current_time(self):
        return self.__fader.current_time()
Beispiel #16
0
class PresetSrc(GstSrcElement):
    MediaType = MediaType.Audio
    Name = QT_TRANSLATE_NOOP('MediaElementName', 'Preset Input')

    FREQ = 8000
    SILENCE = lambda t: 0
    PRESETS = {
        'The 42 melody':
        lambda t: t * (42 & t >> 10),
        'Mission':
        lambda t: (~t >> 2) * ((127 & t * (7 & t >> 10)) <
                               (245 & t * (2 + (5 & t >> 14)))),
        "80's":
        lambda t: (t << 3) * [8 / 9, 1, 9 / 8, 6 / 5, 4 / 3, 3 / 2, 0]
        [[0xd2d2c8, 0xce4088, 0xca32c8, 0x8e4009][t >> 14 & 3] >>
         (0x3dbe4688 >>
          (18 if (t >> 10 & 15) > 9 else t >> 10 & 15) * 3 & 7) * 3 & 7],
        'Game 1':
        lambda t: (t * (0xCA98 >> (t >> 9 & 14) & 15) | t >> 8),
        'Game 2':
        lambda t: t * 5 & (t >> 7) | t * 3 & (t * 4 >> 10) - int(
            math.cos(t >> 3)) * 10 | t >> 5 & int(math.sin(t >> 4)) | t >> 4,
        'Club':
        lambda t: (((t * (t ^ t % 255) | (t >> 4)) >> 1) if (t & 4096) else
                   (t >> 3) | (t << 2 if (t & 8192) else t)) + t * ((
                       (t >> 9) ^ ((t >> 9) - 1) ^ 1) % 13),
        'Laser 1':
        lambda t: t * (t >> 5 | t >> 15) & 80 & t * 4 >> 9,
        'Laser 2':
        lambda t: (t *
                   (t >> 5 | t >> 23) & 43 & t >> 9) ^ (t & t >> 20 | t >> 9),
        'Generator':
        lambda t: (t *
                   (t >> 22 | t >> 3) & 43 & t >> 8) ^ (t & t >> 12 | t >> 4)
    }

    preset = Property(default='The 42 melody')

    def __init__(self, pipe):
        super().__init__()
        self.n_sample = 0
        self.caps = 'audio/x-raw,format=U8,channels=1,layout=interleaved,' \
                    'rate=' + str(PresetSrc.FREQ)

        self.app_src = Gst.ElementFactory.make('appsrc', 'appsrc')
        self.app_src.set_property('stream-type', GstApp.AppStreamType.SEEKABLE)
        self.app_src.set_property('format', Gst.Format.TIME)
        self.app_src.set_property('caps', Gst.Caps.from_string(self.caps))
        self.app_src.connect('need-data', self.generate_samples)
        self.app_src.connect('seek-data', self.seek)

        self.audio_converter = Gst.ElementFactory.make('audioconvert', None)

        pipe.add(self.app_src)
        pipe.add(self.audio_converter)

        self.app_src.link(self.audio_converter)

    def stop(self):
        self.n_sample = 0

    def interrupt(self):
        self.stop()

    def generate_samples(self, src, need_bytes):
        remaining = int(self.duration / 1000 * PresetSrc.FREQ - self.n_sample)
        if remaining <= 0:
            self.n_sample = 0
            src.emit('end-of-stream')
        else:
            if need_bytes > remaining:
                need_bytes = remaining

            function = PresetSrc.PRESETS.get(self.preset, PresetSrc.SILENCE)
            sample = []
            while len(sample) < need_bytes:
                value = function(self.n_sample)
                sample.append(int(value % 256))
                self.n_sample += 1

            buffer = Gst.Buffer.new_wrapped(bytes(sample))
            src.emit('push-buffer', buffer)

    def seek(self, src, time):
        self.n_sample = int(abs(time / Gst.SECOND) * PresetSrc.FREQ)
        return True

    def src(self):
        return self.audio_converter
Beispiel #17
0
class JackSink(GstMediaElement):
    ElementType = ElementType.Output
    MediaType = MediaType.Audio
    Name = QT_TRANSLATE_NOOP('MediaElementName', 'JACK Out')

    CLIENT_NAME = 'linux-show-player'
    CONNECT_MODE = 'none'

    _ControlClient = None
    _clients = []

    connections = Property(default=[[] for _ in range(8)])

    def __init__(self, pipeline):
        super().__init__()

        if JackSink._ControlClient is None:
            JackSink._ControlClient = jack.Client('LinuxShowPlayer_Control')

        self.pipeline = pipeline
        self.audio_resample = Gst.ElementFactory.make('audioresample')
        self.jack_sink = Gst.ElementFactory.make('jackaudiosink', 'sink')

        self._client_id = JackSink.__register_client_id()
        self._client_name = JackSink.CLIENT_NAME + '-' + str(self._client_id)
        self.jack_sink.set_property('client-name', self._client_name)
        self.jack_sink.set_property('connect', JackSink.CONNECT_MODE)

        self.pipeline.add(self.audio_resample)
        self.pipeline.add(self.jack_sink)

        self.audio_resample.link(self.jack_sink)

        self.connections = self.default_connections(JackSink._ControlClient)
        self.changed('connections').connect(self.__prepare_connections)

        bus = self.pipeline.get_bus()
        bus.add_signal_watch()
        self._handler = bus.connect('message', self.__on_message)

    def sink(self):
        return self.audio_resample

    def dispose(self):
        try:
            self.pipeline.get_bus().disconnect(self._handler)
            JackSink._clients.remove(self._client_id)
        finally:
            if not JackSink._clients:
                JackSink._ControlClient.deactivate()
                JackSink._ControlClient.close()
                JackSink._ControlClient = None

    @classmethod
    def default_connections(cls, client):
        # Up to 8 channels
        connections = [[] for _ in range(8)]

        if isinstance(client, jack.Client):
            # Search for default input ports
            input_ports = client.get_ports(name_pattern='^system:',
                                           is_audio=True,
                                           is_input=True)
            for n, port in enumerate(input_ports):
                if n < len(connections):
                    connections[n].append(port.name)
                else:
                    break

        return connections

    def __prepare_connections(self, value):
        if (self.pipeline.current_state == Gst.State.PLAYING
                or self.pipeline.current_state == Gst.State.PAUSED):
            self.__jack_connect()

    @classmethod
    def __register_client_id(cls):
        n = 0
        for n, client in enumerate(cls._clients):
            if n != client:
                break

        if n == len(cls._clients) - 1:
            n += 1

        cls._clients.insert(n, n)
        return n

    def __jack_connect(self):
        out_ports = JackSink._ControlClient.get_ports(
            name_pattern='^' + self._client_name + ':.+', is_audio=True)

        for port in out_ports:
            for conn_port in JackSink._ControlClient.get_all_connections(port):
                try:
                    JackSink._ControlClient.disconnect(port, conn_port)
                except jack.JackError as e:
                    logging.error('GST: JACK-SINK: {}'.format(e))

        for output, in_ports in enumerate(self.connections):
            for input_name in in_ports:
                if output < len(out_ports):
                    try:
                        JackSink._ControlClient.connect(
                            out_ports[output], input_name)
                    except jack.JackError as e:
                        logging.error('GST: JACK-SINK: {}'.format(e))
                else:
                    break

    def __on_message(self, bus, message):
        if message.src == self.jack_sink:
            if message.type == Gst.MessageType.STATE_CHANGED:
                change = message.parse_state_changed()

                # The jack ports are available when the the jackaudiosink
                # change from READY to PAUSED state
                if (change[0] == Gst.State.READY
                        and change[1] == Gst.State.PAUSED):
                    self.__jack_connect()
Beispiel #18
0
class GstMedia(Media):
    """Media implementation based on the GStreamer framework."""

    pipe = Property(default=())

    def __init__(self):
        super().__init__()

        self._state = MediaState.Null
        self._elements = []
        self._old_pipe = ''
        self._loop_count = 0

        self._gst_pipe = Gst.Pipeline()
        self._gst_state = Gst.State.NULL
        self._time_query = Gst.Query.new_position(Gst.Format.TIME)

        bus = self._gst_pipe.get_bus()
        bus.add_signal_watch()

        # Use a weakref instead of the method or the object will not be
        # garbage-collected
        on_message = weakref.WeakMethod(self.__on_message)
        handler = bus.connect('message', lambda *args: on_message()(*args))
        weakref.finalize(self, self.__finalizer, self._gst_pipe, handler,
                         self._elements)

        self.changed('loop').connect(self.__prepare_loops)
        self.changed('pipe').connect(self.__prepare_pipe)

    @Media.state.getter
    def state(self):
        return self._state

    def __prepare_loops(self, loops):
        self._loop_count = loops

    def __prepare_pipe(self, pipe):
        if pipe != self._old_pipe:
            self._old_pipe = pipe

            # If the pipeline is invalid raise an error
            pipe = validate_pipeline(pipe, rebuild=True)
            if not pipe:
                raise ValueError('Invalid pipeline "{0}"'.format(pipe))

            # Build the pipeline
            elements_properties = self.elements_properties()
            self.__build_pipeline()
            self.update_elements(elements_properties)

            self._elements[0].changed('duration').connect(
                self.__duration_changed)
            self.__duration_changed(self._elements[0].duration)

    def current_time(self):
        ok, position = self._gst_pipe.query_position(Gst.Format.TIME)
        return position // Gst.MSECOND if ok else 0

    def play(self):
        if self.state == MediaState.Stopped or self.state == MediaState.Paused:
            self.on_play.emit(self)

            for element in self._elements:
                element.play()

            self._state = MediaState.Playing
            self._gst_pipe.set_state(Gst.State.PLAYING)
            self._gst_pipe.get_state(Gst.SECOND)

            if self.start_time > 0 or self.stop_time > 0:
                self.seek(self.start_time)

            self.played.emit(self)

    def pause(self):
        if self.state == MediaState.Playing:
            self.on_pause.emit(self)

            for element in self._elements:
                element.pause()

            self._state = MediaState.Paused
            self._gst_pipe.set_state(Gst.State.PAUSED)
            self._gst_pipe.get_state(Gst.SECOND)

            # FIXME: the pipeline is not flushed (f*****g GStreamer)

            self.paused.emit(self)

    def stop(self):
        if self.state == MediaState.Playing or self.state == MediaState.Paused:
            self.on_stop.emit(self)

            for element in self._elements:
                element.stop()

            self.interrupt(emit=False)
            self.stopped.emit(self)

    def __seek(self, position):
        # FIXME: not working when in pause (fix or disallow)
        if self.state == MediaState.Playing or self.state == MediaState.Paused:
            max_position = self.duration
            if 0 < self.stop_time < self.duration:
                max_position = self.stop_time

            if position < max_position:
                # Query segment info for the playback rate
                query = Gst.Query.new_segment(Gst.Format.TIME)
                self._gst_pipe.query(query)
                rate = Gst.Query.parse_segment(query)[0]

                # Check stop_position value
                stop_type = Gst.SeekType.NONE
                if self.stop_time > 0:
                    stop_type = Gst.SeekType.SET

                # Seek the pipeline
                result = self._gst_pipe.seek(
                    rate if rate > 0 else 1,
                    Gst.Format.TIME,
                    Gst.SeekFlags.FLUSH,
                    Gst.SeekType.SET,
                    position * Gst.MSECOND,
                    stop_type,
                    self.stop_time * Gst.MSECOND)

                return result

        return False

    def seek(self, position):
        if self.__seek(position):
            self.sought.emit(self, position)

    def element(self, class_name):
        for element in self._elements:
            if type(element).__name__ == class_name:
                return element

    def elements(self):
        return self._elements.copy()

    def elements_properties(self, only_changed=False):
        properties = {}

        for element in self._elements:
            e_properties = element.properties(only_changed)
            if e_properties:
                properties[type(element).__name__] = e_properties

        return properties

    def input_uri(self):
        try:
            return self._elements[0].input_uri()
        except Exception:
            pass

    def interrupt(self, dispose=False, emit=True):
        for element in self._elements:
            element.interrupt()

        state = self._state

        self._gst_pipe.set_state(Gst.State.NULL)
        if dispose:
            self._state = MediaState.Null
        else:
            self._gst_pipe.set_state(Gst.State.READY)
            self._state = MediaState.Stopped

        self._loop_count = self.loop

        if emit and (state == MediaState.Playing or
                     state == MediaState.Paused):
            self.interrupted.emit(self)

    def properties(self, only_changed=False):
        properties = super().properties(only_changed).copy()
        properties['elements'] = self.elements_properties(only_changed)
        return properties

    def update_elements(self, properties):
        for element in self._elements:
            if type(element).__name__ in properties:
                element.update_properties(properties[type(element).__name__])

    def update_properties(self, properties):
        elements_properties = properties.pop('elements', {})
        super().update_properties(properties)

        self.update_elements(elements_properties)

        if self.state == MediaState.Null or self.state == MediaState.Error:
            self._state = MediaState.Stopped

    @staticmethod
    def _pipe_elements():
        tmp = {}
        tmp.update(elements.inputs())
        tmp.update(elements.outputs())
        tmp.update(elements.plugins())
        return tmp

    def __append_element(self, element):
        if self._elements:
            self._elements[-1].link(element)

        self._elements.append(element)

    def __remove_element(self, index):
        if index > 0:
            self._elements[index - 1].unlink(self._elements[index])
        if index < len(self._elements) - 1:
            self._elements[index - 1].link(self._elements[index + 1])
            self._elements[index].unlink(self._elements[index])

        self._elements.pop(index).dispose()

    def __build_pipeline(self):
        # Set to NULL the pipeline
        self.interrupt(dispose=True)
        # Remove all pipeline children
        for __ in range(self._gst_pipe.get_children_count()):
            self._gst_pipe.remove(self._gst_pipe.get_child_by_index(0))
        # Remove all the elements
        for __ in range(len(self._elements)):
            self.__remove_element(len(self._elements) - 1)

        # Create all the new elements
        pipe_elements = self._pipe_elements()
        for element in self.pipe:
            self.__append_element(pipe_elements[element](self._gst_pipe))

        # Set to Stopped/READY the pipeline
        self._state = MediaState.Stopped
        self._gst_pipe.set_state(Gst.State.READY)

        self.elements_changed.emit(self)

    def __on_message(self, bus, message):
        if message.src == self._gst_pipe:
            if message.type == Gst.MessageType.STATE_CHANGED:
                self._gst_state = message.parse_state_changed()[1]
            elif message.type == Gst.MessageType.EOS:
                self.__on_eos()
            elif message.type == Gst.MessageType.CLOCK_LOST:
                self._gst_pipe.set_state(Gst.State.PAUSED)
                self._gst_pipe.set_state(Gst.State.PLAYING)

        if message.type == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            self._state = MediaState.Error
            self.interrupt(dispose=True, emit=False)
            self.error.emit(self, str(err), str(debug))

    def __on_eos(self):
        if self._loop_count != 0:
            self._loop_count -= 1
            self.seek(self.start_time)
        else:
            self._state = MediaState.Stopped
            self.eos.emit(self)
            self.interrupt(emit=False)

    def __duration_changed(self, duration):
        self.duration = duration

    @staticmethod
    def __finalizer(pipeline, connection_handler, media_elements):
        # Allow pipeline resources to be released
        pipeline.set_state(Gst.State.NULL)

        bus = pipeline.get_bus()
        bus.remove_signal_watch()
        bus.disconnect(connection_handler)

        for element in media_elements:
            element.dispose()