Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3ce77b9
Initial video support via WaylandSink.
essboyer Dec 11, 2024
7413153
Translations
essboyer Dec 11, 2024
2d22911
gitignore vscode files.
essboyer Dec 11, 2024
7a9386a
Prevent gst_pipe_edit from crashing when editing the pipe of a video …
essboyer Dec 12, 2024
3a4921c
Merge remote-tracking branch 'extra-video/video_support' into extra-v…
tobi08151405 Apr 4, 2025
7c029b4
added fading support
tobi08151405 Apr 4, 2025
e9f01b1
added audio and video fade
tobi08151405 Apr 4, 2025
88027ed
fixed video pipe linking
tobi08151405 Apr 5, 2025
0472af2
added support for AV pipeline components
tobi08151405 Apr 5, 2025
e7b3d7e
added video to auto sink
tobi08151405 Apr 5, 2025
4320304
reworked alpha fade
tobi08151405 Apr 5, 2025
b640768
dispose and remove audio / video for gst
tobi08151405 Apr 5, 2025
7fc4b8b
perfomance improfments
tobi08151405 Apr 5, 2025
c96a02a
renamed
tobi08151405 Apr 5, 2025
0e3a348
removed video pipeline
tobi08151405 Apr 11, 2025
e66eac0
i18n update
tobi08151405 Apr 11, 2025
3466972
started on fader group
tobi08151405 Apr 12, 2025
cec7f61
removed media type from mediacuefactory
tobi08151405 Apr 12, 2025
ed6e383
refactor alpha
tobi08151405 Apr 12, 2025
a325226
fixed video speed issue
tobi08151405 Apr 12, 2025
6c10a25
i18n update
tobi08151405 Apr 12, 2025
fad0d25
added selectable background to alpha
tobi08151405 Apr 12, 2025
c037ad8
fixed alpha settings
tobi08151405 Apr 12, 2025
a6eb9b4
added video balance
tobi08151405 Apr 12, 2025
712c9e9
added video flip plugin
tobi08151405 Apr 12, 2025
db1fcdd
fixed flip
tobi08151405 Apr 12, 2025
1ff9794
added blur
tobi08151405 Apr 12, 2025
ef72f33
added rotate
tobi08151405 Apr 12, 2025
613b97b
fixed review changes of #333
tobi08151405 Apr 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ eggs/
/.project
/.pydevproject

# VSCode IDE
.vscode/*

# Pycharm IDE
.idea/*

Expand Down
3 changes: 2 additions & 1 deletion lisp/backend/media_element.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is part of Linux Show Player
#
# Copyright 2018 Francesco Ceruti <ceppofrancy@gmail.com>
# Copyright 2024 Francesco Ceruti <ceppofrancy@gmail.com>
#
# Linux Show Player is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -36,6 +36,7 @@ class MediaType(Enum):
Audio = 0
Video = 1
AudioAndVideo = 2
Unknown = 4


class MediaElement(HasProperties):
Expand Down
115 changes: 115 additions & 0 deletions lisp/core/fader_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# This file is part of Linux Show Player
#
# Copyright 2017 Francesco Ceruti <ceppofrancy@gmail.com>
#
# Linux Show Player is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Linux Show Player is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Linux Show Player. If not, see <http://www.gnu.org/licenses/>.

from concurrent.futures import ThreadPoolExecutor, Future, as_completed
from typing import Iterable

from lisp.core.decorators import locked_method
from lisp.core.fader import BaseFader
from lisp.core.fade_functions import FadeInType, FadeOutType
from lisp.core.util import typename


class FaderGroup:
_faders: "list[BaseFader]" = []

def __init__(self, faders: "list[BaseFader]" = []):
"""
:param faders: The faders of this FaderGroup
"""
self._faders = faders.copy()

def add_fader(self, fader: "BaseFader") -> bool:
"""
:param fader: the fader to add

:return: False if fader is already part of Group, True otherwise
"""
if fader in self._faders:
return False
self._faders.append(fader)
return True

def rem_fader(self, fader: "BaseFader") -> bool:
"""
:param fader: the fader to remove

:return: False if fader not in group, True otherwise
"""
if fader not in self._faders:
return False
self._faders.remove(fader)
return True

def clear(self):
self._faders.clear()

def __len__(self):
return len(self._faders)

@locked_method(blocking=False)
def fade(
self,
duration: float,
to_value: "float | list[float]",
fade_type: "FadeInType | FadeOutType"
) -> bool:
"""
:param duration: How much the fade should be long (in seconds)
:param to_value: The value to reach
:param fade_type: The fade type

:return: False if the fade as been interrupted, True otherwise
"""
if duration <= 0:
return True

if not isinstance(fade_type, (FadeInType, FadeOutType)):
raise AttributeError(
"fade_type must be one of FadeInType or FadeOutType members,"
f"not {typename(fade_type)}"
)

if not isinstance(to_value, Iterable):
to_value = [to_value] * len(self._faders)

if len(to_value) != len(self._faders):
raise AttributeError(
"to_value must have the same number of values as faders in the group,"
f"was: {len(to_value)}, should_be: {len(self._faders)}"
)

with ThreadPoolExecutor(len(self._faders), "fade") as executor:
futures: 'list[Future]' = []
for fader, value in zip(self._faders, to_value):
futures.append(executor.submit(fader.fade, duration, value, fade_type))

for future in as_completed(futures):
if not future.result():
return False
return True

def prepare(self):
for fader in self._faders:
fader.prepare()

def stop(self):
for fader in self._faders:
fader.stop()

def is_running(self) -> bool:
return any(fader.is_running() for fader in self._faders)
91 changes: 60 additions & 31 deletions lisp/cues/media_cue.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from lisp.core.decorators import async_function
from lisp.core.fade_functions import FadeInType, FadeOutType
from lisp.core.fader_group import FaderGroup
from lisp.core.properties import Property
from lisp.cues.cue import Cue, CueAction, CueState

Expand Down Expand Up @@ -61,28 +62,33 @@ def __init__(self, app, media, id=None):
self.__in_fadeout = False

self.__fade_lock = Lock()
self.__fader = None
self.__faders = FaderGroup()
self.__volume = None
self.__alpha = None

self.__elements_changed()

def __elements_changed(self):
# Ensure the current fade, if any, is not running.
if self.__fader is not None:
self.__fader.stop()
self.__faders.stop()

# Create a new fader, if possible
self.__faders.clear()
self.__volume = self.media.element("Volume")
self.__alpha = self.media.element("Alpha")
if self.__volume is not None:
self.__fader = self.__volume.get_fader("live_volume")
else:
self.__fader = None
self.__faders.add_fader(self.__volume.get_fader("live_volume"))
if self.__alpha is not None:
self.__faders.add_fader(self.__alpha.get_fader("live_alpha"))

def __start__(self, fade=False):
# If we are fading-in on the start of the media, we need to ensure
# that the volume starts at 0
if fade and self.fadein_duration > 0 and self._can_fade():
self.__volume.live_volume = 0
if fade and self.fadein_duration > 0:
if self._can_fade_audio():
self.__volume.live_volume = 0
if self._can_fade_video():
self.__alpha.live_alpha = 0

self.media.play()

Expand All @@ -94,16 +100,16 @@ def __start__(self, fade=False):

def __stop__(self, fade=False):
if self.__in_fadeout:
self.__fader.stop()
self.__faders.stop()
else:
if self.__in_fadein:
self.__fader.stop()
self.__faders.stop()

if fade and self._state & CueState.Running and self._can_fade():
self._st_lock.release()
ended = self.__fadeout(
self.fadeout_duration,
0,
[0] * len(self.__faders),
FadeOutType[self.fadeout_type],
)
self._st_lock.acquire()
Expand All @@ -115,16 +121,16 @@ def __stop__(self, fade=False):

def __pause__(self, fade=False):
if self.__in_fadeout:
self.__fader.stop()
self.__faders.stop()
else:
if self.__in_fadein:
self.__fader.stop()
self.__faders.stop()

if fade and self._state & CueState.Running and self._can_fade():
self._st_lock.release()
ended = self.__fadeout(
self.fadeout_duration,
0,
[0] * len(self.__faders),
FadeOutType[self.fadeout_type],
)
self._st_lock.acquire()
Expand All @@ -136,11 +142,11 @@ def __pause__(self, fade=False):

def __interrupt__(self, fade=False):
if self._can_fade():
self.__fader.stop()
self.__faders.stop()

fade_duration = self._interrupt_fade_duration()
if fade and fade_duration > 0 and self._state & CueState.Running:
self.__fadeout(fade_duration, 0, self._interrupt_fade_type())
self.__fadeout(fade_duration, [0] * len(self.__faders), self._interrupt_fade_type())

self.media.stop()

Expand All @@ -150,13 +156,22 @@ def fadein(self, duration, fade_type):
return

if self._state & CueState.Running and self._can_fade():
self.__fader.stop()
self.__faders.stop()

if duration <= 0:
self.__volume.live_volume = self.__volume.volume
if self._can_fade_audio():
self.__volume.live_volume = self.__volume.volume
if self._can_fade_video():
self.__alpha.live_alpha = self.__alpha.alpha
else:
self._st_lock.release()
self.__fadein(duration, self.__volume.volume, fade_type)
values = []
if self._can_fade_audio():
values.append(self._volume.volume)
if self._can_fade_video():
values.append(self.__alpha.alpha)
if values:
self.__fadein(duration, values, fade_type)
return

self._st_lock.release()
Expand All @@ -170,13 +185,16 @@ def fadeout(self, duration, fade_type):
return

if self._state & CueState.Running and self._can_fade():
self.__fader.stop()
self.__faders.stop()

if duration <= 0:
self.__volume.live_volume = 0
if self._can_fade_audio():
self.__volume.live_volume = 0
if self._can_fade_video():
self.__alpha.live_alpha = 0
else:
self._st_lock.release()
self.__fadeout(duration, 0, fade_type)
self.__fadeout(duration, [0] * len(self.__faders), fade_type)
return

self._st_lock.release()
Expand All @@ -188,8 +206,8 @@ def __fadein(self, duration, to_value, fade_type):
self.__in_fadein = True
self.fadein_start.emit()
try:
self.__fader.prepare()
ended = self.__fader.fade(duration, to_value, fade_type)
self.__faders.prepare()
ended = self.__faders.fade(duration, to_value, fade_type)
finally:
self.__in_fadein = False
self.fadein_end.emit()
Expand All @@ -203,8 +221,8 @@ def __fadeout(self, duration, to_value, fade_type):
self.__in_fadeout = True
self.fadeout_start.emit()
try:
self.__fader.prepare()
ended = self.__fader.fade(duration, to_value, fade_type)
self.__faders.prepare()
ended = self.__faders.fade(duration, to_value, fade_type)
finally:
self.__in_fadeout = False
self.fadeout_end.emit()
Expand All @@ -225,22 +243,33 @@ def _duration_change(self, value):

def _on_eos(self):
with self._st_lock:
self.__fader.stop()
self.__faders.stop()
self._ended()

def _on_error(self):
with self._st_lock:
self.__fader.stop()
self.__faders.stop()
self._error()

def _can_fade(self):
return self.__volume is not None and self.__fader is not None
return self._can_fade_audio() or self._can_fade_video()

def _can_fade_audio(self):
return self.__volume is not None and len(self.__faders) > 0

def _can_fade_video(self):
return self.__alpha is not None and len(self.__faders) > 0

@async_function
def _on_start_fade(self):
if self._can_fade():
values = []
if self._can_fade_audio():
values.append(self.__volume.volume)
if self._can_fade_video():
values.append(self.__alpha.alpha)
if values:
self.__fadein(
self.fadein_duration,
self.__volume.volume,
values,
FadeInType[self.fadein_type],
)
Loading