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
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
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
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)
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
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)
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()
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
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)
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()
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
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()
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()