Ajout du GUI

This commit is contained in:
thatscringebro
2022-08-08 16:31:52 -04:00
parent db362ccdca
commit abd15f28b6
851 changed files with 99957 additions and 1 deletions

219
kivy/core/video/__init__.py Normal file
View File

@@ -0,0 +1,219 @@
'''
Video
=====
Core class for reading video files and managing the video
:class:`~kivy.graphics.texture.Texture`.
.. versionchanged:: 1.10.0
The pyglet, pygst and gi providers have been removed.
.. versionchanged:: 1.8.0
There are now 2 distinct Gstreamer implementations: one using Gi/Gst
working for both Python 2+3 with Gstreamer 1.0, and one using PyGST
working only for Python 2 + Gstreamer 0.10.
.. note::
Recording is not supported.
'''
__all__ = ('VideoBase', 'Video')
from kivy.clock import Clock
from kivy.core import core_select_lib
from kivy.event import EventDispatcher
from kivy.logger import Logger
from kivy.compat import PY2
class VideoBase(EventDispatcher):
'''VideoBase, a class used to implement a video reader.
:Parameters:
`filename`: str
Filename of the video. Can be a file or an URI.
`eos`: str, defaults to 'pause'
Action to take when EOS is hit. Can be one of 'pause', 'stop' or
'loop'.
.. versionchanged:: 1.4.0
added 'pause'
`async`: bool, defaults to True
Load the video asynchronously (may be not supported by all
providers).
`autoplay`: bool, defaults to False
Auto play the video on init.
:Events:
`on_eos`
Fired when EOS is hit.
`on_load`
Fired when the video is loaded and the texture is available.
`on_frame`
Fired when a new frame is written to the texture.
'''
__slots__ = ('_wantplay', '_buffer', '_filename', '_texture',
'_volume', 'eos', '_state', '_async', '_autoplay')
__events__ = ('on_eos', 'on_load', 'on_frame')
def __init__(self, **kwargs):
kwargs.setdefault('filename', None)
kwargs.setdefault('eos', 'stop')
kwargs.setdefault('async', True)
kwargs.setdefault('autoplay', False)
super(VideoBase, self).__init__()
self._wantplay = False
self._buffer = None
self._filename = None
self._texture = None
self._volume = 1.
self._state = ''
self._autoplay = kwargs.get('autoplay')
self._async = kwargs.get('async')
self.eos = kwargs.get('eos')
if self.eos == 'pause':
Logger.warning("'pause' is deprecated. Use 'stop' instead.")
self.eos = 'stop'
self.filename = kwargs.get('filename')
Clock.schedule_interval(self._update, 1 / 30.)
if self._autoplay:
self.play()
def __del__(self):
self.unload()
def on_eos(self):
pass
def on_load(self):
pass
def on_frame(self):
pass
def _get_filename(self):
return self._filename
def _set_filename(self, filename):
if filename == self._filename:
return
self.unload()
self._filename = filename
if self._filename is None:
return
self.load()
filename = property(lambda self: self._get_filename(),
lambda self, x: self._set_filename(x),
doc='Get/set the filename/uri of the current video')
def _get_position(self):
return 0
def _set_position(self, pos):
self.seek(pos)
position = property(lambda self: self._get_position(),
lambda self, x: self._set_position(x),
doc='Get/set the position in the video (in seconds)')
def _get_volume(self):
return self._volume
def _set_volume(self, volume):
self._volume = volume
volume = property(lambda self: self._get_volume(),
lambda self, x: self._set_volume(x),
doc='Get/set the volume in the video (1.0 = 100%)')
def _get_duration(self):
return 0
duration = property(lambda self: self._get_duration(),
doc='Get the video duration (in seconds)')
def _get_texture(self):
return self._texture
texture = property(lambda self: self._get_texture(),
doc='Get the video texture')
def _get_state(self):
return self._state
state = property(lambda self: self._get_state(),
doc='Get the video playing status')
def _do_eos(self, *args):
'''
.. versionchanged:: 1.4.0
Now dispatches the `on_eos` event.
'''
if self.eos == 'pause':
self.pause()
elif self.eos == 'stop':
self.stop()
elif self.eos == 'loop':
self.position = 0
self.play()
self.dispatch('on_eos')
def _update(self, dt):
'''Update the video content to texture.
'''
pass
def seek(self, percent, precise=True):
'''Move to position as percentage (strictly, a proportion from
0 - 1) of the duration'''
pass
def stop(self):
'''Stop the video playing'''
self._state = ''
def pause(self):
'''Pause the video
.. versionadded:: 1.4.0
'''
self._state = 'paused'
def play(self):
'''Play the video'''
self._state = 'playing'
def load(self):
'''Load the video from the current filename'''
pass
def unload(self):
'''Unload the actual video'''
self._state = ''
# Load the appropriate provider
video_providers = []
try:
from kivy.lib.gstplayer import GstPlayer # NOQA
video_providers += [('gstplayer', 'video_gstplayer', 'VideoGstplayer')]
except ImportError:
pass
video_providers += [
('ffmpeg', 'video_ffmpeg', 'VideoFFMpeg'),
('ffpyplayer', 'video_ffpyplayer', 'VideoFFPy'),
('null', 'video_null', 'VideoNull')]
Video = core_select_lib('video', video_providers)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,106 @@
'''
FFmpeg video abstraction
========================
.. versionadded:: 1.0.8
This abstraction requires ffmpeg python extensions. We have made a special
extension that is used for the android platform but can also be used on x86
platforms. The project is available at::
http://github.com/tito/ffmpeg-android
The extension is designed for implementing a video player.
Refer to the documentation of the ffmpeg-android project for more information
about the requirements.
'''
try:
import ffmpeg
except:
raise
from kivy.core.video import VideoBase
from kivy.graphics.texture import Texture
class VideoFFMpeg(VideoBase):
def __init__(self, **kwargs):
self._do_load = False
self._player = None
super(VideoFFMpeg, self).__init__(**kwargs)
def unload(self):
if self._player:
self._player.stop()
self._player = None
self._state = ''
self._do_load = False
def load(self):
self.unload()
def play(self):
if self._player:
self.unload()
self._player = ffmpeg.FFVideo(self._filename)
self._player.set_volume(self._volume)
self._do_load = True
def stop(self):
self.unload()
def seek(self, percent, precise=True):
if self._player is None:
return
self._player.seek(percent)
def _do_eos(self):
self.unload()
self.dispatch('on_eos')
super(VideoFFMpeg, self)._do_eos()
def _update(self, dt):
if self._do_load:
self._player.open()
self._do_load = False
return
player = self._player
if player is None:
return
if not player.is_open:
self._do_eos()
return
frame = player.get_next_frame()
if frame is None:
return
# first time we got a frame, we know that video is read now.
if self._texture is None:
self._texture = Texture.create(size=(
player.get_width(), player.get_height()),
colorfmt='rgb')
self._texture.flip_vertical()
self.dispatch('on_load')
if self._texture:
self._texture.blit_buffer(frame)
self.dispatch('on_frame')
def _get_duration(self):
if self._player is None:
return 0
return self._player.get_duration()
def _get_position(self):
if self._player is None:
return 0
return self._player.get_position()
def _set_volume(self, value):
self._volume = value
if self._player:
self._player.set_volume(self._volume)

View File

@@ -0,0 +1,446 @@
'''
FFmpeg based video abstraction
==============================
To use, you need to install ffpyplayer and have a compiled ffmpeg shared
library.
https://github.com/matham/ffpyplayer
The docs there describe how to set this up. But briefly, first you need to
compile ffmpeg using the shared flags while disabling the static flags (you'll
probably have to set the fPIC flag, e.g. CFLAGS=-fPIC). Here are some
instructions: https://trac.ffmpeg.org/wiki/CompilationGuide. For Windows, you
can download compiled GPL binaries from http://ffmpeg.zeranoe.com/builds/.
Similarly, you should download SDL2.
Now, you should have ffmpeg and sdl directories. In each, you should have an
'include', 'bin' and 'lib' directory, where e.g. for Windows, 'lib' contains
the .dll.a files, while 'bin' contains the actual dlls. The 'include' directory
holds the headers. The 'bin' directory is only needed if the shared libraries
are not already in the path. In the environment, define FFMPEG_ROOT and
SDL_ROOT, each pointing to the ffmpeg and SDL directories respectively. (If
you're using SDL2, the 'include' directory will contain an 'SDL2' directory,
which then holds the headers).
Once defined, download the ffpyplayer git repo and run
python setup.py build_ext --inplace
Finally, before running you need to ensure that ffpyplayer is in python's path.
..Note::
When kivy exits by closing the window while the video is playing,
it appears that the __del__method of VideoFFPy
is not called. Because of this, the VideoFFPy object is not
properly deleted when kivy exits. The consequence is that because
MediaPlayer creates internal threads which do not have their daemon
flag set, when the main threads exists, it'll hang and wait for the other
MediaPlayer threads to exit. But since __del__ is not called to delete the
MediaPlayer object, those threads will remain alive, hanging kivy. What
this means is that you have to be sure to delete the MediaPlayer object
before kivy exits by setting it to None.
'''
__all__ = ('VideoFFPy', )
try:
import ffpyplayer
from ffpyplayer.player import MediaPlayer
from ffpyplayer.tools import set_log_callback, get_log_callback
except:
raise
from threading import Thread
from queue import Queue, Empty, Full
from kivy.clock import Clock, mainthread
from kivy.logger import Logger
from kivy.core.video import VideoBase
from kivy.graphics import Rectangle, BindTexture
from kivy.graphics.texture import Texture
from kivy.graphics.fbo import Fbo
from kivy.weakmethod import WeakMethod
import time
Logger.info('VideoFFPy: Using ffpyplayer {}'.format(ffpyplayer.version))
logger_func = {'quiet': Logger.critical, 'panic': Logger.critical,
'fatal': Logger.critical, 'error': Logger.error,
'warning': Logger.warning, 'info': Logger.info,
'verbose': Logger.debug, 'debug': Logger.debug}
def _log_callback(message, level):
message = message.strip()
if message:
logger_func[level]('ffpyplayer: {}'.format(message))
if not get_log_callback():
set_log_callback(_log_callback)
class VideoFFPy(VideoBase):
YUV_RGB_FS = """
$HEADER$
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;
void main(void) {
float y = texture2D(tex_y, tex_coord0).r;
float u = texture2D(tex_u, tex_coord0).r - 0.5;
float v = texture2D(tex_v, tex_coord0).r - 0.5;
float r = y + 1.402 * v;
float g = y - 0.344 * u - 0.714 * v;
float b = y + 1.772 * u;
gl_FragColor = vec4(r, g, b, 1.0);
}
"""
_trigger = None
def __init__(self, **kwargs):
self._ffplayer = None
self._thread = None
self._next_frame = None
self._seek_queue = []
self._ffplayer_need_quit = False
self._wakeup_queue = Queue(maxsize=1)
self._trigger = Clock.create_trigger(self._redraw)
super(VideoFFPy, self).__init__(**kwargs)
def __del__(self):
self.unload()
def _wakeup_thread(self):
try:
self._wakeup_queue.put(None, False)
except Full:
pass
def _wait_for_wakeup(self, timeout):
try:
self._wakeup_queue.get(True, timeout)
except Empty:
pass
def _player_callback(self, selector, value):
if self._ffplayer is None:
return
if selector == 'quit':
def close(*args):
self.unload()
Clock.schedule_once(close, 0)
def _get_position(self):
if self._ffplayer is not None:
return self._ffplayer.get_pts()
return 0
def _set_position(self, pos):
self.seek(pos)
def _set_volume(self, volume):
self._volume = volume
if self._ffplayer is not None:
self._ffplayer.set_volume(self._volume)
def _get_duration(self):
if self._ffplayer is None:
return 0
return self._ffplayer.get_metadata()['duration']
@mainthread
def _do_eos(self):
if self.eos == 'pause':
self.pause()
elif self.eos == 'stop':
self.stop()
elif self.eos == 'loop':
# this causes a seek to zero
self.position = 0
self.dispatch('on_eos')
@mainthread
def _finish_setup(self):
# once setup is done, we make sure player state matches what user
# could have requested while player was being setup and it was in limbo
# also, thread starts player in internal paused mode, so this unpauses
# it if user didn't request pause meanwhile
if self._ffplayer is not None:
self._ffplayer.set_volume(self._volume)
self._ffplayer.set_pause(self._state == 'paused')
self._wakeup_thread()
def _redraw(self, *args):
if not self._ffplayer:
return
next_frame = self._next_frame
if not next_frame:
return
img, pts = next_frame
if img.get_size() != self._size or self._texture is None:
self._size = w, h = img.get_size()
if self._out_fmt == 'yuv420p':
w2 = int(w / 2)
h2 = int(h / 2)
self._tex_y = Texture.create(
size=(w, h), colorfmt='luminance')
self._tex_u = Texture.create(
size=(w2, h2), colorfmt='luminance')
self._tex_v = Texture.create(
size=(w2, h2), colorfmt='luminance')
self._fbo = fbo = Fbo(size=self._size)
with fbo:
BindTexture(texture=self._tex_u, index=1)
BindTexture(texture=self._tex_v, index=2)
Rectangle(size=fbo.size, texture=self._tex_y)
fbo.shader.fs = VideoFFPy.YUV_RGB_FS
fbo['tex_y'] = 0
fbo['tex_u'] = 1
fbo['tex_v'] = 2
self._texture = fbo.texture
else:
self._texture = Texture.create(size=self._size,
colorfmt='rgba')
# XXX FIXME
# self.texture.add_reload_observer(self.reload_buffer)
self._texture.flip_vertical()
self.dispatch('on_load')
if self._texture:
if self._out_fmt == 'yuv420p':
dy, du, dv, _ = img.to_memoryview()
if dy and du and dv:
self._tex_y.blit_buffer(dy, colorfmt='luminance')
self._tex_u.blit_buffer(du, colorfmt='luminance')
self._tex_v.blit_buffer(dv, colorfmt='luminance')
self._fbo.ask_update()
self._fbo.draw()
else:
self._texture.blit_buffer(
img.to_memoryview()[0], colorfmt='rgba')
self.dispatch('on_frame')
def _next_frame_run(self, ffplayer):
sleep = time.sleep
trigger = self._trigger
did_dispatch_eof = False
wait_for_wakeup = self._wait_for_wakeup
seek_queue = self._seek_queue
# video starts in internal paused state
# fast path, if the source video is yuv420p, we'll use a glsl shader
# for buffer conversion to rgba
# wait until we get frame metadata
while not self._ffplayer_need_quit:
src_pix_fmt = ffplayer.get_metadata().get('src_pix_fmt')
if not src_pix_fmt:
wait_for_wakeup(0.005)
continue
if src_pix_fmt == 'yuv420p':
self._out_fmt = 'yuv420p'
ffplayer.set_output_pix_fmt(self._out_fmt)
break
if self._ffplayer_need_quit:
ffplayer.close_player()
return
# wait until loaded or failed, shouldn't take long, but just to make
# sure metadata is available.
while not self._ffplayer_need_quit:
if ffplayer.get_metadata()['src_vid_size'] != (0, 0):
break
wait_for_wakeup(0.005)
if self._ffplayer_need_quit:
ffplayer.close_player()
return
self._ffplayer = ffplayer
self._finish_setup()
# now, we'll be in internal paused state and loop will wait until
# mainthread unpauses us when finishing setup
while not self._ffplayer_need_quit:
seek_happened = False
if seek_queue:
vals = seek_queue[:]
del seek_queue[:len(vals)]
percent, precise = vals[-1]
ffplayer.seek(
percent * ffplayer.get_metadata()['duration'],
relative=False,
accurate=precise
)
seek_happened = True
did_dispatch_eof = False
self._next_frame = None
# Get next frame if paused:
if seek_happened and ffplayer.get_pause():
ffplayer.set_volume(0.0) # Try to do it silently.
ffplayer.set_pause(False)
try:
# We don't know concrete number of frames to skip,
# this number worked fine on couple of tested videos:
to_skip = 6
while True:
frame, val = ffplayer.get_frame(show=False)
# Exit loop on invalid val:
if val in ('paused', 'eof'):
break
# Exit loop on seek_queue updated:
if seek_queue:
break
# Wait for next frame:
if frame is None:
sleep(0.005)
continue
# Wait until we skipped enough frames:
to_skip -= 1
if to_skip == 0:
break
# Assuming last frame is actual, just get it:
frame, val = ffplayer.get_frame(force_refresh=True)
finally:
ffplayer.set_pause(bool(self._state == 'paused'))
# todo: this is not safe because user could have updated
# volume between us reading it and setting it
ffplayer.set_volume(self._volume)
# Get next frame regular:
else:
frame, val = ffplayer.get_frame()
if val == 'eof':
if not did_dispatch_eof:
self._do_eos()
did_dispatch_eof = True
wait_for_wakeup(None)
elif val == 'paused':
did_dispatch_eof = False
wait_for_wakeup(None)
else:
did_dispatch_eof = False
if frame:
self._next_frame = frame
trigger()
else:
val = val if val else (1 / 30.)
wait_for_wakeup(val)
ffplayer.close_player()
def seek(self, percent, precise=True):
# still save seek while thread is setting up
self._seek_queue.append((percent, precise,))
self._wakeup_thread()
def stop(self):
self.unload()
def pause(self):
# if state hasn't been set (empty), there's no player. If it's
# paused, nothing to do so just handle playing
if self._state == 'playing':
# we could be in limbo while player is setting up so check. Player
# will pause when finishing setting up
if self._ffplayer is not None:
self._ffplayer.set_pause(True)
# even in limbo, indicate to start in paused state
self._state = 'paused'
self._wakeup_thread()
def play(self):
# _state starts empty and is empty again after unloading
if self._ffplayer:
# player is already setup, just handle unpausing
assert self._state in ('paused', 'playing')
if self._state == 'paused':
self._ffplayer.set_pause(False)
self._state = 'playing'
self._wakeup_thread()
return
# we're now either in limbo state waiting for thread to setup,
# or no thread has been started
if self._state == 'playing':
# in limbo, just wait for thread to setup player
return
elif self._state == 'paused':
# in limbo, still unpause for when player becomes ready
self._state = 'playing'
self._wakeup_thread()
return
# load first unloads
self.load()
self._out_fmt = 'rgba'
# it starts internally paused, but unpauses itself
ff_opts = {
'paused': True,
'out_fmt': self._out_fmt,
'sn': True,
'volume': self._volume,
}
ffplayer = MediaPlayer(
self._filename, callback=self._player_callback,
thread_lib='SDL',
loglevel='info', ff_opts=ff_opts
)
# Disabled as an attempt to fix kivy issue #6210
# self._ffplayer.set_volume(self._volume)
self._thread = Thread(
target=self._next_frame_run,
name='Next frame',
args=(ffplayer, )
)
# todo: remove
self._thread.daemon = True
# start in playing mode, but _ffplayer isn't set until ready. We're
# now in a limbo state
self._state = 'playing'
self._thread.start()
def load(self):
self.unload()
def unload(self):
# no need to call self._trigger.cancel() because _ffplayer is set
# to None below, and it's not safe to call clock stuff from __del__
# if thread is still alive, set it to exit and wake it
self._wakeup_thread()
self._ffplayer_need_quit = True
# wait until it exits
if self._thread:
# TODO: use callback, don't block here
self._thread.join()
self._thread = None
if self._ffplayer:
self._ffplayer = None
self._next_frame = None
self._size = (0, 0)
self._state = ''
self._seek_queue = []
# reset for next load since thread is dead for sure
self._ffplayer_need_quit = False
self._wakeup_queue = Queue(maxsize=1)

View File

@@ -0,0 +1,140 @@
'''
Video Gstplayer
===============
.. versionadded:: 1.8.0
Implementation of a VideoBase with Kivy :class:`~kivy.lib.gstplayer.GstPlayer`
This player is the preferred player, using Gstreamer 1.0, working on both
Python 2 and 3.
'''
try:
from kivy.lib.gstplayer import GstPlayer, get_gst_version
except ImportError:
from kivy.core import handle_win_lib_import_error
handle_win_lib_import_error(
'VideoGstplayer', 'gst', 'kivy.lib.gstplayer._gstplayer')
raise
from kivy.graphics.texture import Texture
from kivy.core.video import VideoBase
from kivy.logger import Logger
from kivy.clock import Clock
from kivy.compat import PY2
from threading import Lock
from functools import partial
from os.path import realpath
from weakref import ref
if PY2:
from urllib import pathname2url
else:
from urllib.request import pathname2url
Logger.info('VideoGstplayer: Using Gstreamer {}'.format(
'.'.join(map(str, get_gst_version()))))
def _on_gstplayer_buffer(video, width, height, data):
video = video()
# if we still receive the video but no more player, remove it.
if not video:
return
with video._buffer_lock:
video._buffer = (width, height, data)
def _on_gstplayer_message(mtype, message):
if mtype == 'error':
Logger.error('VideoGstplayer: {}'.format(message))
elif mtype == 'warning':
Logger.warning('VideoGstplayer: {}'.format(message))
elif mtype == 'info':
Logger.info('VideoGstplayer: {}'.format(message))
class VideoGstplayer(VideoBase):
def __init__(self, **kwargs):
self.player = None
self._buffer = None
self._buffer_lock = Lock()
super(VideoGstplayer, self).__init__(**kwargs)
def _on_gst_eos_sync(self):
Clock.schedule_once(self._do_eos, 0)
def load(self):
Logger.debug('VideoGstplayer: Load <{}>'.format(self._filename))
uri = self._get_uri()
wk_self = ref(self)
self.player_callback = partial(_on_gstplayer_buffer, wk_self)
self.player = GstPlayer(uri, self.player_callback,
self._on_gst_eos_sync, _on_gstplayer_message)
self.player.load()
def unload(self):
if self.player:
self.player.unload()
self.player = None
with self._buffer_lock:
self._buffer = None
self._texture = None
def stop(self):
super(VideoGstplayer, self).stop()
self.player.stop()
def pause(self):
super(VideoGstplayer, self).pause()
self.player.pause()
def play(self):
super(VideoGstplayer, self).play()
self.player.set_volume(self.volume)
self.player.play()
def seek(self, percent, precise=True):
self.player.seek(percent)
def _get_position(self):
return self.player.get_position()
def _get_duration(self):
return self.player.get_duration()
def _set_volume(self, value):
self._volume = value
if self.player:
self.player.set_volume(self._volume)
def _update(self, dt):
buf = None
with self._buffer_lock:
buf = self._buffer
self._buffer = None
if buf is not None:
self._update_texture(buf)
self.dispatch('on_frame')
def _update_texture(self, buf):
width, height, data = buf
# texture is not allocated yet, create it first
if not self._texture:
self._texture = Texture.create(size=(width, height),
colorfmt='rgb')
self._texture.flip_vertical()
self.dispatch('on_load')
if self._texture:
self._texture.blit_buffer(
data, size=(width, height), colorfmt='rgb')
def _get_uri(self):
uri = self.filename
if not uri:
return
if '://' not in uri:
uri = 'file:' + pathname2url(realpath(uri))
return uri

View File

@@ -0,0 +1,12 @@
'''
VideoNull: empty implementation of VideoBase for the no provider case
'''
from kivy.core.video import VideoBase
class VideoNull(VideoBase):
'''VideoBase implementation when there is no provider.
'''
pass