Ajout du GUI
This commit is contained in:
219
kivy/core/video/__init__.py
Normal file
219
kivy/core/video/__init__.py
Normal 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)
|
||||
BIN
kivy/core/video/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/video/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/video/__pycache__/video_ffmpeg.cpython-310.pyc
Normal file
BIN
kivy/core/video/__pycache__/video_ffmpeg.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/video/__pycache__/video_ffpyplayer.cpython-310.pyc
Normal file
BIN
kivy/core/video/__pycache__/video_ffpyplayer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/video/__pycache__/video_gstplayer.cpython-310.pyc
Normal file
BIN
kivy/core/video/__pycache__/video_gstplayer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/video/__pycache__/video_null.cpython-310.pyc
Normal file
BIN
kivy/core/video/__pycache__/video_null.cpython-310.pyc
Normal file
Binary file not shown.
106
kivy/core/video/video_ffmpeg.py
Normal file
106
kivy/core/video/video_ffmpeg.py
Normal 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)
|
||||
446
kivy/core/video/video_ffpyplayer.py
Normal file
446
kivy/core/video/video_ffpyplayer.py
Normal 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)
|
||||
140
kivy/core/video/video_gstplayer.py
Normal file
140
kivy/core/video/video_gstplayer.py
Normal 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
|
||||
12
kivy/core/video/video_null.py
Normal file
12
kivy/core/video/video_null.py
Normal 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
|
||||
Reference in New Issue
Block a user