303 lines
8.9 KiB
Python
303 lines
8.9 KiB
Python
|
'''
|
||
|
Video
|
||
|
=====
|
||
|
|
||
|
The :class:`Video` widget is used to display video files and streams.
|
||
|
Depending on your Video core provider, platform, and plugins, you will
|
||
|
be able to play different formats. For example, the pygame video
|
||
|
provider only supports MPEG1 on Linux and OSX. GStreamer is more
|
||
|
versatile, and can read many video containers and codecs such as MKV,
|
||
|
OGV, AVI, MOV, FLV (if the correct gstreamer plugins are installed). Our
|
||
|
:class:`~kivy.core.video.VideoBase` implementation is used under the
|
||
|
hood.
|
||
|
|
||
|
Video loading is asynchronous - many properties are not available until
|
||
|
the video is loaded (when the texture is created)::
|
||
|
|
||
|
def on_position_change(instance, value):
|
||
|
print('The position in the video is', value)
|
||
|
|
||
|
def on_duration_change(instance, value):
|
||
|
print('The duration of the video is', value)
|
||
|
|
||
|
video = Video(source='PandaSneezes.avi')
|
||
|
video.bind(
|
||
|
position=on_position_change,
|
||
|
duration=on_duration_change
|
||
|
)
|
||
|
|
||
|
One can define a preview image which gets displayed until the video is
|
||
|
started/loaded by passing ``preview`` to the constructor::
|
||
|
|
||
|
video = Video(
|
||
|
source='PandaSneezes.avi',
|
||
|
preview='PandaSneezes_preview.png'
|
||
|
)
|
||
|
|
||
|
One can display the placeholder image when the video stops by reacting on eos::
|
||
|
|
||
|
def on_eos_change(self, inst, val):
|
||
|
if val and self.preview:
|
||
|
self.set_texture_from_resource(self.preview)
|
||
|
|
||
|
video.bind(eos=on_eos_change)
|
||
|
'''
|
||
|
|
||
|
__all__ = ('Video', )
|
||
|
|
||
|
from kivy.clock import Clock
|
||
|
from kivy.uix.image import Image
|
||
|
from kivy.core.video import Video as CoreVideo
|
||
|
from kivy.resources import resource_find
|
||
|
from kivy.properties import (BooleanProperty, NumericProperty, ObjectProperty,
|
||
|
OptionProperty, StringProperty)
|
||
|
|
||
|
|
||
|
class Video(Image):
|
||
|
'''Video class. See module documentation for more information.
|
||
|
'''
|
||
|
|
||
|
preview = StringProperty(None, allownone=True)
|
||
|
'''Filename / source of a preview image displayed before video starts.
|
||
|
|
||
|
:attr:`preview` is a :class:`~kivy.properties.StringProperty` and
|
||
|
defaults to None.
|
||
|
|
||
|
If set, it gets displayed until the video is loaded/started.
|
||
|
|
||
|
.. versionadded:: 2.1.0
|
||
|
'''
|
||
|
|
||
|
state = OptionProperty('stop', options=('play', 'pause', 'stop'))
|
||
|
'''String, indicates whether to play, pause, or stop the video::
|
||
|
|
||
|
# start playing the video at creation
|
||
|
video = Video(source='movie.mkv', state='play')
|
||
|
|
||
|
# create the video, and start later
|
||
|
video = Video(source='movie.mkv')
|
||
|
# and later
|
||
|
video.state = 'play'
|
||
|
|
||
|
:attr:`state` is an :class:`~kivy.properties.OptionProperty` and defaults
|
||
|
to 'stop'.
|
||
|
'''
|
||
|
|
||
|
play = BooleanProperty(False, deprecated=True)
|
||
|
'''
|
||
|
.. deprecated:: 1.4.0
|
||
|
Use :attr:`state` instead.
|
||
|
|
||
|
Boolean, indicates whether the video is playing or not.
|
||
|
You can start/stop the video by setting this property::
|
||
|
|
||
|
# start playing the video at creation
|
||
|
video = Video(source='movie.mkv', play=True)
|
||
|
|
||
|
# create the video, and start later
|
||
|
video = Video(source='movie.mkv')
|
||
|
# and later
|
||
|
video.play = True
|
||
|
|
||
|
:attr:`play` is a :class:`~kivy.properties.BooleanProperty` and defaults to
|
||
|
False.
|
||
|
|
||
|
.. deprecated:: 1.4.0
|
||
|
Use :attr:`state` instead.
|
||
|
'''
|
||
|
|
||
|
eos = BooleanProperty(False)
|
||
|
'''Boolean, indicates whether the video has finished playing or not
|
||
|
(reached the end of the stream).
|
||
|
|
||
|
:attr:`eos` is a :class:`~kivy.properties.BooleanProperty` and defaults to
|
||
|
False.
|
||
|
'''
|
||
|
|
||
|
loaded = BooleanProperty(False)
|
||
|
'''Boolean, indicates whether the video is loaded and ready for playback
|
||
|
or not.
|
||
|
|
||
|
.. versionadded:: 1.6.0
|
||
|
|
||
|
:attr:`loaded` is a :class:`~kivy.properties.BooleanProperty` and defaults
|
||
|
to False.
|
||
|
'''
|
||
|
|
||
|
position = NumericProperty(-1)
|
||
|
'''Position of the video between 0 and :attr:`duration`. The position
|
||
|
defaults to -1 and is set to a real position when the video is loaded.
|
||
|
|
||
|
:attr:`position` is a :class:`~kivy.properties.NumericProperty` and
|
||
|
defaults to -1.
|
||
|
'''
|
||
|
|
||
|
duration = NumericProperty(-1)
|
||
|
'''Duration of the video. The duration defaults to -1, and is set to a real
|
||
|
duration when the video is loaded.
|
||
|
|
||
|
:attr:`duration` is a :class:`~kivy.properties.NumericProperty` and
|
||
|
defaults to -1.
|
||
|
'''
|
||
|
|
||
|
volume = NumericProperty(1.)
|
||
|
'''Volume of the video, in the range 0-1. 1 means full volume, 0
|
||
|
means mute.
|
||
|
|
||
|
:attr:`volume` is a :class:`~kivy.properties.NumericProperty` and defaults
|
||
|
to 1.
|
||
|
'''
|
||
|
|
||
|
options = ObjectProperty({})
|
||
|
'''Options to pass at Video core object creation.
|
||
|
|
||
|
.. versionadded:: 1.0.4
|
||
|
|
||
|
:attr:`options` is an :class:`kivy.properties.ObjectProperty` and defaults
|
||
|
to {}.
|
||
|
'''
|
||
|
|
||
|
_video_load_event = None
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
self._video = None
|
||
|
super(Video, self).__init__(**kwargs)
|
||
|
self.fbind('source', self._trigger_video_load)
|
||
|
|
||
|
if "eos" in kwargs:
|
||
|
self.options["eos"] = kwargs["eos"]
|
||
|
if self.source:
|
||
|
self._trigger_video_load()
|
||
|
|
||
|
def texture_update(self, *largs):
|
||
|
if self.preview:
|
||
|
self.set_texture_from_resource(self.preview)
|
||
|
else:
|
||
|
self.set_texture_from_resource(self.source)
|
||
|
|
||
|
def seek(self, percent, precise=True):
|
||
|
'''Change the position to a percentage (strictly, a proportion)
|
||
|
of duration.
|
||
|
|
||
|
:Parameters:
|
||
|
`percent`: float or int
|
||
|
Position to seek as a proportion of the total duration,
|
||
|
must be between 0-1.
|
||
|
`precise`: bool, defaults to True
|
||
|
Precise seeking is slower, but seeks to exact requested
|
||
|
percent.
|
||
|
|
||
|
.. warning::
|
||
|
Calling seek() before the video is loaded has no effect.
|
||
|
|
||
|
.. versionadded:: 1.2.0
|
||
|
|
||
|
.. versionchanged:: 1.10.1
|
||
|
The `precise` keyword argument has been added.
|
||
|
'''
|
||
|
if self._video is None:
|
||
|
raise Exception('Video not loaded.')
|
||
|
self._video.seek(percent, precise=precise)
|
||
|
|
||
|
def _trigger_video_load(self, *largs):
|
||
|
ev = self._video_load_event
|
||
|
if ev is None:
|
||
|
ev = self._video_load_event = Clock.schedule_once(
|
||
|
self._do_video_load, -1)
|
||
|
ev()
|
||
|
|
||
|
def _do_video_load(self, *largs):
|
||
|
if CoreVideo is None:
|
||
|
return
|
||
|
self.unload()
|
||
|
if not self.source:
|
||
|
self._video = None
|
||
|
self.texture = None
|
||
|
else:
|
||
|
filename = self.source
|
||
|
# Check if filename is not url
|
||
|
if '://' not in filename:
|
||
|
filename = resource_find(filename)
|
||
|
self._video = CoreVideo(filename=filename, **self.options)
|
||
|
self._video.volume = self.volume
|
||
|
self._video.bind(on_load=self._on_load,
|
||
|
on_frame=self._on_video_frame,
|
||
|
on_eos=self._on_eos)
|
||
|
if self.state == 'play' or self.play:
|
||
|
self._video.play()
|
||
|
self.duration = 1.
|
||
|
self.position = 0.
|
||
|
|
||
|
def on_play(self, instance, value):
|
||
|
value = 'play' if value else 'stop'
|
||
|
return self.on_state(instance, value)
|
||
|
|
||
|
def on_state(self, instance, value):
|
||
|
if not self._video:
|
||
|
return
|
||
|
if value == 'play':
|
||
|
if self.eos:
|
||
|
self._video.stop()
|
||
|
self._video.position = 0.
|
||
|
self.eos = False
|
||
|
self._video.play()
|
||
|
elif value == 'pause':
|
||
|
self._video.pause()
|
||
|
else:
|
||
|
self._video.stop()
|
||
|
self._video.position = 0
|
||
|
|
||
|
def _on_video_frame(self, *largs):
|
||
|
video = self._video
|
||
|
if not video:
|
||
|
return
|
||
|
self.duration = video.duration
|
||
|
self.position = video.position
|
||
|
self.texture = video.texture
|
||
|
self.canvas.ask_update()
|
||
|
|
||
|
def _on_eos(self, *largs):
|
||
|
if not self._video or self._video.eos != 'loop':
|
||
|
self.state = 'stop'
|
||
|
self.eos = True
|
||
|
|
||
|
def _on_load(self, *largs):
|
||
|
self.loaded = True
|
||
|
self._on_video_frame(largs)
|
||
|
|
||
|
def on_volume(self, instance, value):
|
||
|
if self._video:
|
||
|
self._video.volume = value
|
||
|
|
||
|
def unload(self):
|
||
|
'''Unload the video. The playback will be stopped.
|
||
|
|
||
|
.. versionadded:: 1.8.0
|
||
|
'''
|
||
|
if self._video:
|
||
|
self._video.stop()
|
||
|
self._video.unload()
|
||
|
self._video = None
|
||
|
self.loaded = False
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
from kivy.app import App
|
||
|
import sys
|
||
|
|
||
|
if len(sys.argv) != 2:
|
||
|
print("usage: %s file" % sys.argv[0])
|
||
|
sys.exit(1)
|
||
|
|
||
|
class VideoApp(App):
|
||
|
def build(self):
|
||
|
self.v = Video(source=sys.argv[1], state='play')
|
||
|
self.v.bind(state=self.replay)
|
||
|
return self.v
|
||
|
|
||
|
def replay(self, *args):
|
||
|
if self.v.state == 'stop':
|
||
|
self.v.state = 'play'
|
||
|
|
||
|
VideoApp().run()
|