示例#1
0
    def __init__(self,
                 dimensions,
                 fullscreen=False,
                 soundrenderer="pyaudio",
                 loop=False):
        """ Constructor.

		Parameters
		----------
		dimensions : tuple (width, height)
			The dimension of the window in which the video should be shown. Aspect
			ratio is maintained.
		fullscreen : bool, optional
			Indicates whether the video should be displayed in fullscreen.
		soundrenderer : {'pyaudio','pygame'}
			Designates which sound backend should render the sound.	
		"""

        pygame.init()
        (windowWidth, windowHeight) = dimensions
        flags = pygame.DOUBLEBUF | pygame.OPENGL | pygame.HWSURFACE
        self.fullscreen = fullscreen
        if fullscreen:
            flags = flags | pygame.FULLSCREEN
        pygame.display.set_mode((windowWidth, windowHeight), flags)
        self.windowSize = (windowWidth, windowHeight)

        self.soundrenderer = soundrenderer
        self.loop = loop
        self.texUpdated = False

        self.__initGL()

        self.decoder = Decoder(videorenderfunc=self.__texUpdate, )
        self.texture_locked = False
示例#2
0
    def preload(self):
        """Preload stimulus to memory.

        Notes
        -----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        ``expyriment.control.start_audiosystem()``).

        """

        if not self._is_preloaded:
            if not _internals.active_exp.is_initialized:
                message = "Can't preload video. Expyriment needs to be " + \
                          "initialized before preloading a video."
                raise RuntimeError(message)
            if self._backend == "pygame":
                self._file = pygame.movie.Movie(self._filename)
                size = self._file.get_size()
            elif self._backend == "mediadecoder":
                if self.get_ffmpeg_binary() is None:
                    raise RuntimeError("'ffmpeg' not found!")
                from mediadecoder.states import PLAYING
                from mediadecoder.decoder import Decoder
                self._file = Decoder(mediafile=self._filename,
                                     videorenderfunc=self._update_surface)
                if _internals.active_exp._screen.open_gl:
                    import moviepy.video.fx.all as vfx
                    self._file.clip = vfx.mirror_y(self._file.clip)
                if self._file.audioformat:
                    try:
                        from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
                        self._audio = SoundrendererSounddevice(
                            self._file.audioformat)
                        self._audio_renderer = "sounddevice"
                    except ImportError:
                        from mediadecoder.soundrenderers import SoundrendererPygame
                        self._audio = SoundrendererPygame(
                            self._file.audioformat)
                        self._audio_renderer = "pygame"
                    self._file.set_audiorenderer(self._audio)
                size = self._file.clip.size
            else:
                raise RuntimeError("Unknown backend '{0}'!".format(
                    self._backend))
            screen_size = _internals.active_exp.screen.surface.get_size()

            self._pos = [
                screen_size[0] // 2 - size[0] // 2 + self._position[0],
                screen_size[1] // 2 - size[1] // 2 - self._position[1]
            ]
            if self._backend == "pygame":
                self._surface = pygame.surface.Surface(size)
                self._file.set_display(self._surface)
            self._is_paused = False
            self._is_preloaded = True
示例#3
0
文件: test1.py 项目: olakiril/Python
    def __init__(self, dimensions, fullscreen=False, soundrenderer="pyaudio",
                 loop=False):
        """ Constructor.

        Parameters
        ----------
        dimensions : tuple (width, height)
            The dimension of the window in which the video should be shown. Aspect
            ratio is maintained.
        fullscreen : bool, optional
            Indicates whether the video should be displayed in fullscreen.
        soundrenderer : {'pyaudio','pygame'}
            Designates which sound backend should render the sound.
        """

        pygame.init()
        (windowWidth, windowHeight) = dimensions
        flags = pygame.DOUBLEBUF | pygame.OPENGL | pygame.HWSURFACE
        self.fullscreen = fullscreen
        if fullscreen:
            flags = flags | pygame.FULLSCREEN
        pygame.display.set_mode((windowWidth, windowHeight), flags)
        self.windowSize = (windowWidth, windowHeight)

        self.soundrenderer = soundrenderer
        self.loop = loop
        self.texUpdated = False

        self.__initGL()

        self.decoder = Decoder(
            videorenderfunc=self.__texUpdate,
        )
        self.texture_locked = False
示例#4
0
    def preload(self):
        """Preload stimulus to memory.

        Note
        ----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        ``expyriment.control.start_audiosystem()``).

        """

        if not self._is_preloaded:
            if not _internals.active_exp.is_initialized:
                message = "Can't preload video. Expyriment needs to be " + \
                          "initialized before preloading a video."
                raise RuntimeError(message)
            if self._backend == "pygame":
                self._file = pygame.movie.Movie(self._filename)
                size = self._file.get_size()
            elif self._backend == "mediadecoder":
                if self.get_ffmpeg_binary() is None:
                    raise RuntimeError("'ffmpeg' not found!")
                from mediadecoder.states import PLAYING
                from mediadecoder.decoder import Decoder
                self._file = Decoder(mediafile=self._filename,
                                     videorenderfunc=self._update_surface)
                if _internals.active_exp._screen.open_gl:
                    import moviepy.video.fx.all as vfx
                    self._file.clip = vfx.mirror_y(self._file.clip)
                if self._file.audioformat:
                    try:
                        from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
                        self._audio = SoundrendererSounddevice(
                            self._file.audioformat)
                        self._audio_renderer = "sounddevice"
                    except ImportError:
                        from mediadecoder.soundrenderers import SoundrendererPygame
                        self._audio = SoundrendererPygame(
                            self._file.audioformat)
                        self._audio_renderer = "pygame"
                    self._file.set_audiorenderer(self._audio)
                size = self._file.clip.size
            else:
                raise RuntimeError("Unknown backend '{0}'!".format(
                    self._backend))
            screen_size = _internals.active_exp.screen.surface.get_size()

            self._pos = [screen_size[0] // 2 - size[0] // 2 +
                         self._position[0],
                         screen_size[1] // 2 - size[1] // 2 -
                         self._position[1]]
            if self._backend== "pygame":
                self._surface = pygame.surface.Surface(size)
                self._file.set_display(self._surface)
            self._is_paused = False
            self._is_preloaded = True
示例#5
0
    def __init__(
            _,
            soundrenderer="pyaudio",
            media=None,
            end_func=lambda: None,
            loop=False,
            #centered=False,
            audio=True):
        '''
        end func is triggered when the video reach the end
        (and at each loop)
        '''
        #_.width, _.height = dimensions[0],dimensions[1]
        _.soundrenderer = soundrenderer
        _.loop = loop
        _.texUpdated = False

        _.decoder = Decoder(videorenderfunc=_.__texUpdate, end_func=end_func)

        _.texture_locked = False
        media and _.load_media(media, audio=audio)
示例#6
0
class Video(_visual.Stimulus):
    """A class implementing a general video stimulus.

    This class uses a background thread for playing the video!

    Notes
    -----
    When the backend is set to ``"pygame"``, only MPEG-1 videos with MP3 audio
    are supported. You can use ffmpeg (www.ffmpeg.org) to convert from other
    formats:

        ffmpeg -i <inputfile> -vcodec mpeg1video -acodec libmp3lame -intra -qscale 2  <outputfile.mpg>

    The -qscale option is the quality setting. It can take values from 1 to 31.
    1 is the best quality, but big file size. 31 is the worst quality, but
    small file size. Play around with this setting to get a good balance
    between quality and file size.

    When the audio from the video should be played as well, the audiosystem
    has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
    BEFORE the video stimulus is preloaded! After the stimulus has been played
    the audiosystem can be started again (by calling
    ``expyriment.control.start_audiosystem()``).

    When showing videos in large dimensions, and your computer is not fast
    enough, frames might be dropped! When using ``Video.wait_frame()`` or
    ``Video.wait_end()``, dropped video frames will be reported and logged.

    """
    @staticmethod
    def get_ffmpeg_binary():
        try:
            import imageio
            try:
                ffmpeg_binary = imageio.plugins.ffmpeg.get_exe()
                if ffmpeg_binary == "ffmpeg":
                    ffmpeg_binary = which(ffmpeg_binary)
                return ffmpeg_binary
            except imageio.core.NeedDownloadError:
                try:
                    assert has_internet_connection()
                    imageio.plugins.ffmpeg.download()
                    return imageio.plugins.ffmpeg.get_exe()
                except:
                    os.environ['IMAGEIO_NO_INTERNET'] = 'yes'
        except ImportError:
            pass

    def __init__(self, filename, backend=None, position=None):
        """Create a video stimulus.

        Parameters
        ----------
        filename : str
            filename (incl. path) of the video
        backend : str, optional
            'mediadecoder' or 'pygame'
        position : (int, int), optional
            position of the stimulus

        Notes
        -----
        When the backend is set to ``"pygame"``, only MPEG-1 videos with MP3
        audio are supported. You can use ffmpeg (www.ffmpeg.org) to convert from
        other formats:

            ffmpeg -i <inputfile> -vcodec mpeg1video -acodec libmp3lame -intra -qscale 2  <outputfile.mpg>

        The -qscale option is the quality setting. It can take values from 1 to 31.
        1 is the best quality, but big file size. 31 is the worst quality, but
        small file size. Play around with this setting to get a good balance
        between quality and file size.

        """

        from ..io import Keyboard

        _visual.Stimulus.__init__(self, filename)
        self.Keyboard = Keyboard()
        self._filename = filename
        self._is_preloaded = False
        self._is_paused = False
        self._frame = 0
        self._new_frame_available = False
        self._surface_locked = False
        self._audio_started = False
        if backend:
            self._backend = backend
        else:
            self._backend = defaults.video_backend
        if position:
            self._position = position
        else:
            self._position = defaults.video_position

        if not (os.path.isfile(self._filename)):
            raise IOError(u"The video file {0} does not exists".format(
                self._filename))

        if self._backend == "mediadecoder":
            Video.get_ffmpeg_binary(
            )  # in case it still needs to be downloaded
            try:
                import mediadecoder as _mediadecoder
            except ImportError:
                print("Warning: Package 'mediadecoder' not installed!\n" +
                      "Video backend will be set to 'pygame'.")
                self._backend = "pygame"
            try:
                import sounddevice as _sounddevice
            except ImportError:
                print("Warning: Package 'sounddevice' not installed!\n" +
                      "Audio will be played back using Pygame audiosystem.")

    def __del__(self):
        """Destructor for the video stimulus."""

        self._surface = None
        self._file = None

    _getter_exception_message = "Cannot set {0} if preloaded!"

    @property
    def is_preloaded(self):
        """Getter for is_preloaded."""

        return self._is_preloaded

    @property
    def position(self):
        """Getter for position."""

        return self._position

    @property
    def filename(self):
        """Getter for filename."""

        return self._filename

    @filename.setter
    def filename(self, value):
        """Setter for filename."""

        if self._is_preloaded:
            raise AttributeError(
                Video._getter_exception_message.format("filename"))
        else:
            self._filename = value
            if not (os.path.isfile(self._filename)):
                raise IOError(u"The video file {0} does not exists".format(
                    self._filename))

    @property
    def is_playing(self):
        """Property to check if video is playing."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_busy()
            elif self._backend == "mediadecoder":
                return self._file.status == 3

    @property
    def is_paused(self):
        """Property to check if video is paused."""

        if self._is_preloaded:
            return self._is_paused

    @property
    def time(self):
        """Property to get the current playback time."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_time()
            elif self._backend == "mediadecoder":
                return self._file.current_playtime

    @property
    def size(self):
        """Property to get the resolution of the video."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_size()
            elif self._backend == "mediadecoder":
                return self._file.clip.size

    @property
    def frame(self):
        """Property to get the current available video frame."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_frame()
            elif self._backend == "mediadecoder":
                return self._file.current_frame_no

    @property
    def new_frame_available(self):
        """Property to check if new video frame is available to render."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self.frame > self._frame
            elif self._backend == "mediadecoder":
                time.sleep(0.0001)  # Needed for performance for some reason
                return self._new_frame_available

    @property
    def length(self):
        """Property to get the length of the video."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_length()
            elif self._backend == "mediadecoder":
                return self._file.duration

    @property
    def has_video(self):
        """Property to check if video fifle has video information."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.has_video()
            elif self._backend == "mediadecoder":
                return self._file.clip.video is not None

    @property
    def has_audio(self):
        """Property to check if video file has audio information."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.has_audio()
            elif self._backend == "mediadecoder":
                return self._file.clip.audio is not None

    def preload(self):
        """Preload stimulus to memory.

        Notes
        -----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        ``expyriment.control.start_audiosystem()``).

        """

        if not self._is_preloaded:
            if not _internals.active_exp.is_initialized:
                message = "Can't preload video. Expyriment needs to be " + \
                          "initialized before preloading a video."
                raise RuntimeError(message)
            if self._backend == "pygame":
                self._file = pygame.movie.Movie(self._filename)
                size = self._file.get_size()
            elif self._backend == "mediadecoder":
                if self.get_ffmpeg_binary() is None:
                    raise RuntimeError("'ffmpeg' not found!")
                from mediadecoder.states import PLAYING
                from mediadecoder.decoder import Decoder
                self._file = Decoder(mediafile=self._filename,
                                     videorenderfunc=self._update_surface)
                if _internals.active_exp._screen.open_gl:
                    import moviepy.video.fx.all as vfx
                    self._file.clip = vfx.mirror_y(self._file.clip)
                if self._file.audioformat:
                    try:
                        from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
                        self._audio = SoundrendererSounddevice(
                            self._file.audioformat)
                        self._audio_renderer = "sounddevice"
                    except ImportError:
                        from mediadecoder.soundrenderers import SoundrendererPygame
                        self._audio = SoundrendererPygame(
                            self._file.audioformat)
                        self._audio_renderer = "pygame"
                    self._file.set_audiorenderer(self._audio)
                size = self._file.clip.size
            else:
                raise RuntimeError("Unknown backend '{0}'!".format(
                    self._backend))
            screen_size = _internals.active_exp.screen.surface.get_size()

            self._pos = [
                screen_size[0] // 2 - size[0] // 2 + self._position[0],
                screen_size[1] // 2 - size[1] // 2 - self._position[1]
            ]
            if self._backend == "pygame":
                self._surface = pygame.surface.Surface(size)
                self._file.set_display(self._surface)
            self._is_paused = False
            self._is_preloaded = True

    def unload(self, **kwargs):
        """Unload stimulus from memory.

        This removes the reference to the object in memory.
        It is up to the garbage collector to actually remove it from memory.

        """

        if self._is_preloaded:
            self._file = None
            self._surface = None
            self._is_preloaded = False

    def play(self, loop=False, log_event_tag=None, audio=True):
        """Play the video stimulus from the current position.

        Parameters
        ----------
        loop : bool, optional
            loop video playback (will be ignored when using play to unpause!)
        log_event_tag : numeral or string, optional
            if log_event_tag is defined and if logging is switched on for this
            stimulus (default), a summary of the inter-event-intervalls are
            appended at the end of the event file
        audio : bool, optional
            whether audio of video (if present) should be played (default=True)

        Notes
        -----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling expyriment.control.stop_audiosystem() )
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        expyriment.control.start_audiosystem() ).

        When showing videos in large dimensions, and your computer is not fast
        enough, frames might be dropped! When using Video.wait_frame() or
        Video.wait_end(), dropped video frames will be reported and logged.

        """

        if self._is_paused:
            if audio and not self._audio_started:
                self._audio.stream.start()
                self._audio_started = True
            elif not audio and self._audio_started:
                self._audio.stream.stop()
                self._audio_started = False
            self.pause()
        else:
            if self._backend == "pygame" and mixer.get_init() is not None:
                message = "Mixer is still initialized, cannot play audio! Call \
    expyriment.control.stop_audiosystem() before preloading the video."

                print("Warning: ", message)
                if self._logging:
                    _internals.active_exp._event_file_log("Video,warning," +
                                                          message)

            if not self._is_preloaded:
                self.preload()
            if self._logging:
                _internals.active_exp._event_file_log(
                    "Video,playing,{0}".format(self._filename),
                    log_level=1,
                    log_event_tag=log_event_tag)
            if self._backend == "mediadecoder" and self._file.audioformat and audio:
                self._audio.start()
                self._audio_started = True
            self._file.loop = loop
            self._file.play()

    def stop(self):
        """Stop the video stimulus."""

        if self._is_preloaded:
            self._file.stop()
            self.rewind()
            if self._backend == "mediadecoder" and self._file.audioformat:
                self._audio.close_stream()

    def pause(self):
        """Pause the video stimulus."""

        if self._is_preloaded:
            self._file.pause()
            if self._is_paused:
                self._is_paused = False
            else:
                self._is_paused = True

    def forward(self, seconds):
        """Advance playback position.

        Parameters
        ----------
        seconds : int
            amount to advance (in seconds)

        Notes
        -----
        When the backend is set to ``"pygame"``, this will not forward
        immediately, but play a short period of the beginning of the file! This
        is a Pygame issue which we cannot fix right now.

        """

        if self._is_preloaded:
            if self._backend == "pygame":
                self._file.skip(float(seconds))
                new_frame = self._file.get_frame()
            else:
                pos = self._file.current_playtime + seconds
                self._file.seek(pos)
                new_frame = int(pos * self._file.fps)
            self._frame = new_frame

    def rewind(self):
        """Rewind to start of video stimulus."""

        if self._is_preloaded:
            self._file.rewind()
            self._frame = 0

    def _update_surface(self, frame):
        """Update surface with newly available video frame."""

        if self._backend == "mediadecoder" and not self._surface_locked:
            self._surface = frame
            self._new_frame_available = True

    def present(self):
        """Present current frame.

        This method waits for the next frame and presents it on the screen.
        When using OpenGL, the method blocks until this frame is actually being
        written to the screen.

        Notes
        -----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        ``expyriment.control.start_audiosystem()``).

        When showing videos in large dimensions, and your computer is not fast
        enough, frames might be dropped! When using ``Video.wait_frame()`` or
        ``Video.wait_end()``, dropped video frames will be reported and logged.

        """

        start = Clock.monotonic_time()
        while not self.new_frame_available:
            if not self.is_playing:
                return
        diff = self.frame - self._frame
        if diff > 1:
            warn_message = repr(diff - 1) + " video frame(s) dropped!"
            print(warn_message)
            _internals.active_exp._event_file_warn("Video,warning," +
                                                   warn_message)
        self._frame = self.frame
        self.update()
        return (Clock.monotonic_time() - start) * 1000

    def update(self):
        """Update the screen."""

        if not self.is_playing:
            return
        start = Clock.monotonic_time()
        self._surface_locked = True
        if not _internals.active_exp._screen.open_gl:
            _internals.active_exp._screen.surface.blit(
                pygame.surfarray.make_surface(self._surface.swapaxes(0, 1)),
                self._pos)
            self._surface_locked = False
            self._new_frame_available = False
        else:
            ogl_screen = _visual._LaminaPanelSurface(self._surface,
                                                     quadDims=(1, 1, 1, 1),
                                                     position=self._position)
            self._surface_locked = False
            self._new_frame_available = False
            ogl_screen.display()
        _internals.active_exp._screen.update()

    def _wait(self,
              frame=None,
              callback_function=None,
              process_control_events=True):
        """Wait until frame was shown or end of video and update screen.

        Parameters
        ----------
        frame : int, optional
            number of the frame to stop after
        callback_function : function, optional
            function to repeatedly execute during waiting loop
        process_control_events : bool, optional
            process ``io.Keyboard.process_control_keys()`` and
            ``io.Mouse.process_quit_event()`` (default = True)

        Notes
        ------
        This will also by default process control events (quit and pause).
        Thus, keyboard events will be cleared from the cue and cannot be
        received by a Keyboard().check() anymore!

        See Also
        --------
        expyriment.design.Experiment.register_wait_callback_function

        """

        while self.is_playing:

            if _internals.skip_wait_methods:
                return

            self.present()
            if frame is not None and self.frame >= frame:
                break

            if isinstance(callback_function, FunctionType):
                rtn_callback = callback_function()
                if isinstance(rtn_callback, CallbackQuitEvent):
                    return rtn_callback
            if _internals.active_exp.is_initialized:
                rtn_callback = _internals.active_exp._execute_wait_callback()
                if isinstance(rtn_callback, CallbackQuitEvent):
                    return rtn_callback
                if process_control_events:
                    if _internals.active_exp.mouse.process_quit_event():
                        break

            for event in pygame.event.get(pygame.KEYDOWN):
                if _internals.active_exp.is_initialized and \
                   process_control_events and \
                   event.type == pygame.KEYDOWN and (
                       event.key == self.Keyboard.get_quit_key()):
                    self.pause()
                    self.Keyboard.process_control_keys(event, self.stop)
                    self.play()

    def wait_frame(self,
                   frame,
                   callback_function=None,
                   process_control_events=True):
        """Wait until certain frame was shown and constantly update screen.

        Parameters
        ----------
        frame : int
            number of the frame to stop after
        callback_function : function, optional
            function to repeatedly execute during waiting loop
        process_control_events : bool, optional
            process ``io.Keyboard.process_control_keys()`` and
            ``io.Mouse.process_quit_event()`` (default = True)

        Notes
        ------
        This will also by default process control events (quit and pause).
        Thus, keyboard events will be cleared from the cue and cannot be
        received by a Keyboard().check() anymore!
        If keyboard events should not be cleared, a loop has to be created
        manually like::

            video.present()
            while video.is_playing and video.frame < frame:
                while not video.new_frame_available:
                    key = exp.keyboard.check()
                    if key == ...
                video.update()
            video.stop()

        """

        if self.is_playing:
            self._wait(frame, callback_function, process_control_events)

    def wait_end(self, callback_function=None, process_control_events=True):
        """Wait until video has ended and constantly update screen.

        Parameters
        ----------
        callback_function : function, optional
            function to repeatedly execute during waiting loop
        process_control_events : bool, optional
            process ``io.Keyboard.process_control_keys()`` and
            ``io.Mouse.process_quit_event()`` (default = True)

        Notes
        ------
        This will also by default process control events (quit and pause).
        Thus, keyboard events will be cleared from the cue and cannot be
        received by a Keyboard().check() anymore!
        If keyboard events should not be cleared, a loop has to be created
        manually like::

            video.present()
            while video.is_playing and video.frame < frame:
                while not video.new_frame_available:
                    key = exp.keyboard.check()
                    if key == ...
                video.update()
            video.stop()

        """

        self._wait(callback_function, process_control_events)
示例#7
0
文件: test1.py 项目: olakiril/Python
class VideoPlayer():
    """ This is an example videoplayer that uses pygame+pyopengl to render a video.
    It uses the Decoder object to decode the video- and audiostream frame by frame.
    It shows each videoframe in a window and places the audioframes in a buffer
    (or queue) from which they are fetched by the soundrenderer object.
    """

    def __init__(self, dimensions, fullscreen=False, soundrenderer="pyaudio",
                 loop=False):
        """ Constructor.

        Parameters
        ----------
        dimensions : tuple (width, height)
            The dimension of the window in which the video should be shown. Aspect
            ratio is maintained.
        fullscreen : bool, optional
            Indicates whether the video should be displayed in fullscreen.
        soundrenderer : {'pyaudio','pygame'}
            Designates which sound backend should render the sound.
        """

        pygame.init()
        (windowWidth, windowHeight) = dimensions
        flags = pygame.DOUBLEBUF | pygame.OPENGL | pygame.HWSURFACE
        self.fullscreen = fullscreen
        if fullscreen:
            flags = flags | pygame.FULLSCREEN
        pygame.display.set_mode((windowWidth, windowHeight), flags)
        self.windowSize = (windowWidth, windowHeight)

        self.soundrenderer = soundrenderer
        self.loop = loop
        self.texUpdated = False

        self.__initGL()

        self.decoder = Decoder(
            videorenderfunc=self.__texUpdate,
        )
        self.texture_locked = False

    def __initGL(self):
        glViewport(0, 0, self.windowSize[0], self.windowSize[1])

        glPushAttrib(GL_ENABLE_BIT)
        glDisable(GL_DEPTH_TEST)
        glDisable(GL_CULL_FACE)

        glDepthFunc(GL_ALWAYS)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        glOrtho(0.0, self.windowSize[0], self.windowSize[1], 0.0, 0.0, 1.0)

        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()

        glColor4f(1, 1, 1, 1)
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClearDepth(1.0)

    def calc_scaled_res(self, screen_res, image_res):
        """Calculate appropriate texture size.

        Calculate size or required texture so that it will fill the window,
        but retains the movies original aspect ratio.

        Parameters
        ----------
        screen_res : tuple
            Display window size/Resolution
        image_res : tuple
            Image width and height

        Returns
        -------
        tuple
            width and height of image scaled to window/screen
        """
        rs = screen_res[0] / float(screen_res[1])
        ri = image_res[0] / float(image_res[1])

        if rs > ri:
            return (int(image_res[0] * screen_res[1] / image_res[1]), screen_res[1])
        else:
            return (screen_res[0], int(image_res[1] * screen_res[0] / image_res[0]))

    def load_media(self, vidSource):
        """ Loads a video.

        Parameters
        ----------
        vidSource : str
            The path to the video file
        """
        if not os.path.exists(vidSource):
            print("File not found: " + vidSource)
            pygame.display.quit()
            pygame.quit()
            sys.exit(1)

        self.decoder.load_media(vidSource)
        self.decoder.loop = self.loop
        pygame.display.set_caption(os.path.split(vidSource)[1])
        self.vidsize = self.decoder.clip.size

        self.destsize = self.calc_scaled_res(self.windowSize, self.vidsize)
        self.vidPos = ((self.windowSize[0] - self.destsize[0]) / 2, (self.windowSize[1] - self.destsize[1]) / 2)

        self.__textureSetup()

        if (self.decoder.audioformat):
            if self.soundrenderer == "pygame":
                from mediadecoder.soundrenderers import SoundrendererPygame
                self.audio = SoundrendererPygame(self.decoder.audioformat)
            elif self.soundrenderer == "pyaudio":
                from mediadecoder.soundrenderers.pyaudiorenderer import SoundrendererPyAudio
                self.audio = SoundrendererPyAudio(self.decoder.audioformat)
            elif self.soundrenderer == "sounddevice":
                from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
                self.audio = SoundrendererSounddevice(self.decoder.audioformat)
            self.decoder.set_audiorenderer(self.audio)

    def __textureSetup(self):
        # Setup texture in OpenGL to render video to
        glEnable(GL_TEXTURE_2D)
        glMatrixMode(GL_MODELVIEW)
        self.textureNo = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.textureNo)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

        # Fill texture with black to begin with.
        img = np.zeros([self.vidsize[0], self.vidsize[1], 3], dtype=np.uint8)
        img.fill(0)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, self.vidsize[0], self.vidsize[1], 0, GL_RGB, GL_UNSIGNED_BYTE, img)

        # Create display list which draws to the quad to which the texture is rendered
        (x, y) = self.vidPos
        (w, h) = self.destsize

        x, y = int(x), int(y)
        w, h = int(w), int(h)

        self.frameQuad = glGenLists(1);
        glNewList(self.frameQuad, GL_COMPILE)
        glBegin(GL_QUADS)
        glTexCoord2f(0.0, 0.0)
        glVertex3i(x, y, 0)
        glTexCoord2f(1.0, 0.0)
        glVertex3i(x + w, y, 0)
        glTexCoord2f(1.0, 1.0)
        glVertex3i(x + w, y + h, 0)
        glTexCoord2f(0.0, 1.0)
        glVertex3i(x, y + h, 0)
        glEnd()
        glEndList()

        # Clear The Screen And The Depth Buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    def __texUpdate(self, frame):
        """ Update the texture with the newly supplied frame. """
        # Retrieve buffer from videosink
        if self.texture_locked:
            return
        self.buffer = frame
        self.texUpdated = True

    def __drawFrame(self):
        """ Draws a single frame. """
        # Clear The Screen And The Depth Buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glCallList(self.frameQuad)

    def play(self):
        """ Starts playback. """
        # Signal player to start video playback
        self.paused = False

        # Start listening for incoming audio frames
        if self.decoder.audioformat:
            self.audio.start()

        self.decoder.play()

        # While video is playing, render frames
        while self.decoder.status in [mediadecoder.PLAYING, mediadecoder.PAUSED]:
            texture_update_time = 0
            if self.texUpdated:
                t1 = time.time()
                # Update texture
                self.texture_locked = True
                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, self.vidsize[0],
                                self.vidsize[1], GL_RGB, GL_UNSIGNED_BYTE, self.buffer)
                self.texture_locked = False
                texture_update_time = int((time.time() - t1) * 1000)
                self.texUpdated = False

            # Draw the texture to the back buffer
            t1 = time.time()
            self.__drawFrame()
            draw_time = (time.time() - t1) * 1000
            # Flip the buffer to show frame to screen
            t1 = time.time()
            pygame.display.flip()
            flip_time = (time.time() - t1) * 1000

            logger.debug("================== Frame {} ========================"
                         "".format(self.decoder.current_frame_no))
            if texture_update_time:
                logger.debug("Texture updated in {0} ms".format(texture_update_time))
            logger.debug("Texture drawn in {0} ms".format(draw_time))
            logger.debug("Screen flipped in {0} ms".format(flip_time))
            logger.debug("-----------------------------------------------------")
            logger.debug("Total: {} ms".format(texture_update_time + draw_time + flip_time))

            for e in pygame.event.get():
                if e.type == pygame.QUIT:
                    self.stop()
                if e.type == pygame.KEYDOWN:
                    # Quitting
                    if e.key == pygame.K_ESCAPE:
                        self.stop()
                    # Pausing
                    elif e.key == pygame.K_SPACE:
                        self.pause()
                    # Seeking
                    elif e.key == pygame.K_RIGHT:
                        new_time = min(
                            self.decoder.current_playtime + 10,
                            self.decoder.duration)
                        self.decoder.seek(new_time)
                    elif e.key == pygame.K_LEFT:
                        new_time = max(
                            self.decoder.current_playtime - 10,
                            0)
                        self.decoder.seek(new_time)

            pygame.event.pump()  # Prevent freezing of screen while dragging

            # Without this sleep, the video rendering threard goes haywire...
            time.sleep(0.005)

        if self.decoder.audioformat:
            self.audio.close_stream()

        pygame.quit()

    def stop(self):
        """ Stops playback. """
        self.decoder.stop()

    def pause(self):
        """ Pauses playback. """
        if self.decoder.status == mediadecoder.PAUSED:
            self.decoder.pause()
            self.paused = False
        elif self.decoder.status == mediadecoder.PLAYING:
            self.decoder.pause()
            self.paused = True
        else:
            print("Player not in pausable state")
示例#8
0
class VideoPlayer():
    """ This is an example videoplayer that uses pygame+pyopengl to render a video.
	It uses the Decoder object to decode the video- and audiostream frame by frame.
	It shows each videoframe in a window and places the audioframes in a buffer
	(or queue) from which they are fetched by the soundrenderer object.
	"""
    def __init__(self,
                 dimensions,
                 fullscreen=False,
                 soundrenderer="pyaudio",
                 loop=False):
        """ Constructor.

		Parameters
		----------
		dimensions : tuple (width, height)
			The dimension of the window in which the video should be shown. Aspect
			ratio is maintained.
		fullscreen : bool, optional
			Indicates whether the video should be displayed in fullscreen.
		soundrenderer : {'pyaudio','pygame'}
			Designates which sound backend should render the sound.	
		"""

        pygame.init()
        (windowWidth, windowHeight) = dimensions
        flags = pygame.DOUBLEBUF | pygame.OPENGL | pygame.HWSURFACE
        self.fullscreen = fullscreen
        if fullscreen:
            flags = flags | pygame.FULLSCREEN
        pygame.display.set_mode((windowWidth, windowHeight), flags)
        self.windowSize = (windowWidth, windowHeight)

        self.soundrenderer = soundrenderer
        self.loop = loop
        self.texUpdated = False

        self.__initGL()

        self.decoder = Decoder(videorenderfunc=self.__texUpdate, )
        self.texture_locked = False

    def __initGL(self):
        glViewport(0, 0, self.windowSize[0], self.windowSize[1])

        glPushAttrib(GL_ENABLE_BIT)
        glDisable(GL_DEPTH_TEST)
        glDisable(GL_CULL_FACE)

        glDepthFunc(GL_ALWAYS)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        glOrtho(0.0, self.windowSize[0], self.windowSize[1], 0.0, 0.0, 1.0)

        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()

        glColor4f(1, 1, 1, 1)
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClearDepth(1.0)

    def calc_scaled_res(self, screen_res, image_res):
        """Calculate appropriate texture size.

		Calculate size or required texture so that it will fill the window, 
		but retains the movies original aspect ratio.
		
		Parameters
		----------
		screen_res : tuple
			Display window size/Resolution
		image_res : tuple
		    Image width and height

		Returns
		-------
		tuple 
			width and height of image scaled to window/screen
		"""
        rs = screen_res[0] / float(screen_res[1])
        ri = image_res[0] / float(image_res[1])

        if rs > ri:
            return (int(image_res[0] * screen_res[1] / image_res[1]),
                    screen_res[1])
        else:
            return (screen_res[0],
                    int(image_res[1] * screen_res[0] / image_res[0]))

    def load_media(self, vidSource):
        """ Loads a video.

		Parameters
		----------
		vidSource : str
			The path to the video file
		"""
        if not os.path.exists(vidSource):
            print("File not found: " + vidSource)
            pygame.display.quit()
            pygame.quit()
            sys.exit(1)

        self.decoder.load_media(vidSource)
        self.decoder.loop = self.loop
        pygame.display.set_caption(os.path.split(vidSource)[1])
        self.vidsize = self.decoder.clip.size

        self.destsize = self.calc_scaled_res(self.windowSize, self.vidsize)
        self.vidPos = ((self.windowSize[0] - self.destsize[0]) / 2,
                       (self.windowSize[1] - self.destsize[1]) / 2)

        self.__textureSetup()

        if (self.decoder.audioformat):
            if self.soundrenderer == "pygame":
                from mediadecoder.soundrenderers import SoundrendererPygame
                self.audio = SoundrendererPygame(self.decoder.audioformat)
            elif self.soundrenderer == "pyaudio":
                from mediadecoder.soundrenderers.pyaudiorenderer import SoundrendererPyAudio
                self.audio = SoundrendererPyAudio(self.decoder.audioformat)
            elif self.soundrenderer == "sounddevice":
                from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
                self.audio = SoundrendererSounddevice(self.decoder.audioformat)
            self.decoder.set_audiorenderer(self.audio)

    def __textureSetup(self):
        # Setup texture in OpenGL to render video to
        glEnable(GL_TEXTURE_2D)
        glMatrixMode(GL_MODELVIEW)
        self.textureNo = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.textureNo)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

        # Fill texture with black to begin with.
        img = np.zeros([self.vidsize[0], self.vidsize[1], 3], dtype=np.uint8)
        img.fill(0)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, self.vidsize[0],
                     self.vidsize[1], 0, GL_RGB, GL_UNSIGNED_BYTE, img)

        # Create display list which draws to the quad to which the texture is rendered
        (x, y) = self.vidPos
        (w, h) = self.destsize

        x, y = int(x), int(y)
        w, h = int(w), int(h)

        self.frameQuad = glGenLists(1)
        glNewList(self.frameQuad, GL_COMPILE)
        glBegin(GL_QUADS)
        glTexCoord2f(0.0, 0.0)
        glVertex3i(x, y, 0)
        glTexCoord2f(1.0, 0.0)
        glVertex3i(x + w, y, 0)
        glTexCoord2f(1.0, 1.0)
        glVertex3i(x + w, y + h, 0)
        glTexCoord2f(0.0, 1.0)
        glVertex3i(x, y + h, 0)
        glEnd()
        glEndList()

        # Clear The Screen And The Depth Buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    def __texUpdate(self, frame):
        """ Update the texture with the newly supplied frame. """
        # Retrieve buffer from videosink
        if self.texture_locked:
            return
        self.buffer = frame
        self.texUpdated = True

    def __drawFrame(self):
        """ Draws a single frame. """
        # Clear The Screen And The Depth Buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glCallList(self.frameQuad)

    def play(self):
        """ Starts playback. """
        # Signal player to start video playback
        self.paused = False

        # Start listening for incoming audio frames
        if self.decoder.audioformat:
            self.audio.start()

        self.decoder.play()

        # While video is playing, render frames
        while self.decoder.status in [
                mediadecoder.PLAYING, mediadecoder.PAUSED
        ]:
            texture_update_time = 0
            if self.texUpdated:
                t1 = time.time()
                # Update texture
                self.texture_locked = True
                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, self.vidsize[0],
                                self.vidsize[1], GL_RGB, GL_UNSIGNED_BYTE,
                                self.buffer)
                self.texture_locked = False
                texture_update_time = int((time.time() - t1) * 1000)
                self.texUpdated = False

            # Draw the texture to the back buffer
            t1 = time.time()
            self.__drawFrame()
            draw_time = (time.time() - t1) * 1000
            # Flip the buffer to show frame to screen
            t1 = time.time()
            pygame.display.flip()
            flip_time = (time.time() - t1) * 1000

            logger.debug("================== Frame {} ========================"
                         "".format(self.decoder.current_frame_no))
            if texture_update_time:
                logger.debug(
                    "Texture updated in {0} ms".format(texture_update_time))
            logger.debug("Texture drawn in {0} ms".format(draw_time))
            logger.debug("Screen flipped in {0} ms".format(flip_time))
            logger.debug(
                "-----------------------------------------------------")
            logger.debug("Total: {} ms".format(texture_update_time +
                                               draw_time + flip_time))

            for e in pygame.event.get():
                if e.type == pygame.QUIT:
                    self.stop()
                if e.type == pygame.KEYDOWN:
                    # Quitting
                    if e.key == pygame.K_ESCAPE:
                        self.stop()
                    # Pausing
                    elif e.key == pygame.K_SPACE:
                        self.pause()
                    # Seeking
                    elif e.key == pygame.K_RIGHT:
                        new_time = min(self.decoder.current_playtime + 10,
                                       self.decoder.duration)
                        self.decoder.seek(new_time)
                    elif e.key == pygame.K_LEFT:
                        new_time = max(self.decoder.current_playtime - 10, 0)
                        self.decoder.seek(new_time)

            pygame.event.pump()  # Prevent freezing of screen while dragging

            # Without this sleep, the video rendering threard goes haywire...
            time.sleep(0.005)

        if self.decoder.audioformat:
            self.audio.close_stream()

        pygame.quit()

    def stop(self):
        """ Stops playback. """
        self.decoder.stop()

    def pause(self):
        """ Pauses playback. """
        if self.decoder.status == mediadecoder.PAUSED:
            self.decoder.pause()
            self.paused = False
        elif self.decoder.status == mediadecoder.PLAYING:
            self.decoder.pause()
            self.paused = True
        else:
            print("Player not in pausable state")
示例#9
0
class Video(_visual.Stimulus):
    """A class implementing a general video stimulus.

    This class uses a background thread for playing the video!

    Note
    ----
    When the backend is set to ``"pygame"``, only MPEG-1 videos with MP3 audio
    are supported. You can use ffmpeg (www.ffmpeg.org) to convert from other
    formats:

        ffmpeg -i <inputfile> -vcodec mpeg1video -acodec libmp3lame -intra -qscale 2  <outputfile.mpg>

    The -qscale option is the quality setting. It can take values from 1 to 31.
    1 is the best quality, but big file size. 31 is the worst quality, but
    small file size. Play around with this setting to get a good balance
    between quality and file size.

    When the audio from the video should be played as well, the audiosystem
    has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
    BEFORE the video stimulus is preloaded! After the stimulus has been played
    the audiosystem can be started again (by calling
    ``expyriment.control.start_audiosystem()``).

    When showing videos in large dimensions, and your computer is not fast
    enough, frames might be dropped! When using ``Video.wait_frame()`` or
    ``Video.wait_end()``, dropped video frames will be reported and logged.

    """

    @staticmethod
    def get_ffmpeg_binary():
        try:
            import imageio
            try:
                ffmpeg_binary = imageio.plugins.ffmpeg.get_exe()
                if ffmpeg_binary == "ffmpeg":
                    ffmpeg_binary = which(ffmpeg_binary)
                return ffmpeg_binary
            except imageio.core.NeedDownloadError:
                try:
                    assert has_internet_connection()
                    imageio.plugins.ffmpeg.download()
                    return imageio.plugins.ffmpeg.get_exe()
                except:
                    os.environ['IMAGEIO_NO_INTERNET'] = 'yes'
        except ImportError:
            pass

    def __init__(self, filename, backend=None, position=None):
        """Create a video stimulus.

        Parameters
        ----------
        filename : str
            filename (incl. path) of the video
        backend : str, optional
            'mediadecoder' or 'pygame'
        position : (int, int), optional
            position of the stimulus

        Note
        ----
        When the backend is set to ``"pygame"``, only MPEG-1 videos with MP3
        audio are supported. You can use ffmpeg (www.ffmpeg.org) to convert from
        other formats:

            ffmpeg -i <inputfile> -vcodec mpeg1video -acodec libmp3lame -intra -qscale 2  <outputfile.mpg>

        The -qscale option is the quality setting. It can take values from 1 to 31.
        1 is the best quality, but big file size. 31 is the worst quality, but
        small file size. Play around with this setting to get a good balance
        between quality and file size.

        """

        from ..io import Keyboard

        _visual.Stimulus.__init__(self, filename)
        self.Keyboard = Keyboard()
        self._filename = filename
        self._is_preloaded = False
        self._is_paused = False
        self._frame = 0
        self._new_frame_available = False
        self._surface_locked = False
        self._audio_started = False
        if backend:
            self._backend = backend
        else:
            self._backend = defaults.video_backend
        if position:
            self._position = position
        else:
            self._position = defaults.video_position

        if not(os.path.isfile(self._filename)):
            raise IOError("The video file {0} does not exists".format(
                unicode2byte(self._filename)))

        if self._backend == "mediadecoder":
            Video.get_ffmpeg_binary()  # in case it still needs to be downloaded
            try:
                import mediadecoder as _mediadecoder
            except ImportError:
                print("Warning: Package 'mediadecoder' not installed!\n" +
                      "Video backend will be set to 'pygame'.")
                self._backend = "pygame"
            try:
                import sounddevice as _sounddevice
            except ImportError:
                print("Warning: Package 'sounddevice' not installed!\n" +
                      "Audio will be played back using Pygame audiosystem.")


    def __del__(self):
        """Destructor for the video stimulus."""

        self._surface = None
        self._file = None

    _getter_exception_message = "Cannot set {0} if preloaded!"

    @property
    def is_preloaded(self):
        """Getter for is_preloaded."""

        return self._is_preloaded

    @property
    def position(self):
        """Getter for position."""

        return self._position

    @property
    def filename(self):
        """Getter for filename."""

        return self._filename

    @filename.setter
    def filename(self, value):
        """Setter for filename."""

        if self._is_preloaded:
            raise AttributeError(Video._getter_exception_message.format(
                "filename"))
        else:
            self._filename = value
            if not(os.path.isfile(self._filename)):
                raise IOError("The video file {0} does not exists".format(
                              unicode2byte(self._filename)))

    @property
    def is_playing(self):
        """Property to check if video is playing."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_busy()
            elif self._backend == "mediadecoder":
                return self._file.status == 3

    @property
    def is_paused(self):
        """Property to check if video is paused."""

        if self._is_preloaded:
            return self._is_paused

    @property
    def time(self):
        """Property to get the current playback time."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_time()
            elif self._backend == "mediadecoder":
                return self._file.current_playtime

    @property
    def size(self):
        """Property to get the resolution of the video."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_size()
            elif self._backend == "mediadecoder":
                return self._file.clip.size

    @property
    def frame(self):
        """Property to get the current available video frame."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_frame()
            elif self._backend == "mediadecoder":
                return self._file.current_frame_no

    @property
    def new_frame_available(self):
        """Property to check if new video frame is available to render."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self.frame > self._frame
            elif self._backend == "mediadecoder":
                time.sleep(0.0001)  # Needed for performance for some reason
                return self._new_frame_available

    @property
    def length(self):
        """Property to get the length of the video."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.get_length()
            elif self._backend == "mediadecoder":
                return self._file.duration

    @property
    def has_video(self):
        """Property to check if video fifle has video information."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.has_video()
            elif self._backend == "mediadecoder":
                return self._file.clip.video is not None

    @property
    def has_audio(self):
        """Property to check if video file has audio information."""

        if self._is_preloaded:
            if self._backend == "pygame":
                return self._file.has_audio()
            elif self._backend == "mediadecoder":
                return self._file.clip.audio is not None

    def preload(self):
        """Preload stimulus to memory.

        Note
        ----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        ``expyriment.control.start_audiosystem()``).

        """

        if not self._is_preloaded:
            if not _internals.active_exp.is_initialized:
                message = "Can't preload video. Expyriment needs to be " + \
                          "initialized before preloading a video."
                raise RuntimeError(message)
            if self._backend == "pygame":
                self._file = pygame.movie.Movie(self._filename)
                size = self._file.get_size()
            elif self._backend == "mediadecoder":
                if self.get_ffmpeg_binary() is None:
                    raise RuntimeError("'ffmpeg' not found!")
                from mediadecoder.states import PLAYING
                from mediadecoder.decoder import Decoder
                self._file = Decoder(mediafile=self._filename,
                                     videorenderfunc=self._update_surface)
                if _internals.active_exp._screen.open_gl:
                    import moviepy.video.fx.all as vfx
                    self._file.clip = vfx.mirror_y(self._file.clip)
                if self._file.audioformat:
                    try:
                        from mediadecoder.soundrenderers.sounddevicerenderer import SoundrendererSounddevice
                        self._audio = SoundrendererSounddevice(
                            self._file.audioformat)
                        self._audio_renderer = "sounddevice"
                    except ImportError:
                        from mediadecoder.soundrenderers import SoundrendererPygame
                        self._audio = SoundrendererPygame(
                            self._file.audioformat)
                        self._audio_renderer = "pygame"
                    self._file.set_audiorenderer(self._audio)
                size = self._file.clip.size
            else:
                raise RuntimeError("Unknown backend '{0}'!".format(
                    self._backend))
            screen_size = _internals.active_exp.screen.surface.get_size()

            self._pos = [screen_size[0] // 2 - size[0] // 2 +
                         self._position[0],
                         screen_size[1] // 2 - size[1] // 2 -
                         self._position[1]]
            if self._backend== "pygame":
                self._surface = pygame.surface.Surface(size)
                self._file.set_display(self._surface)
            self._is_paused = False
            self._is_preloaded = True

    def unload(self, **kwargs):
        """Unload stimulus from memory.

        This removes the reference to the object in memory.
        It is up to the garbage collector to actually remove it from memory.

        """

        if self._is_preloaded:
            self._file = None
            self._surface = None
            self._is_preloaded = False

    def play(self, loop=False, log_event_tag=None, audio=True):
        """Play the video stimulus from the current position.

        Parameters
        ----------
        loop : bool, optional
            loop video playback (will be ignored when using play to unpause!)
        log_event_tag : numeral or string, optional
            if log_event_tag is defined and if logging is switched on for this
            stimulus (default), a summary of the inter-event-intervalls are
            appended at the end of the event file
        audio : bool, optional
            whether audio of video (if present) should be played (default=True)

        Note
        ----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling expyriment.control.stop_audiosystem() )
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        expyriment.control.start_audiosystem() ).

        When showing videos in large dimensions, and your computer is not fast
        enough, frames might be dropped! When using Video.wait_frame() or
        Video.wait_end(), dropped video frames will be reported and logged.

        """

        if self._is_paused:
            if audio and not self._audio_started:
                self._audio.stream.start()
                self._audio_started = True
            elif not audio and self._audio_started:
                self._audio.stream.stop()
                self._audio_started = False
            self.pause()
        else:
            if self._backend == "pygame" and mixer.get_init() is not None:
                message = "Mixer is still initialized, cannot play audio! Call \
    expyriment.control.stop_audiosystem() before preloading the video."
                print("Warning: ", message)
                if self._logging:
                    _internals.active_exp._event_file_log(
                        "Video,warning," + message)

            if not self._is_preloaded:
                self.preload()
            if self._logging:
                _internals.active_exp._event_file_log(
                    "Video,playing,{0}".format(unicode2byte(self._filename)),
                    log_level=1, log_event_tag=log_event_tag)
            if self._backend == "mediadecoder" and self._file.audioformat and audio:
                self._audio.start()
                self._audio_started = True
            self._file.loop = loop
            self._file.play()

    def stop(self):
        """Stop the video stimulus."""

        if self._is_preloaded:
            self._file.stop()
            self.rewind()
            if self._backend == "mediadecoder" and self._file.audioformat:
                self._audio.close_stream()

    def pause(self):
        """Pause the video stimulus."""

        if self._is_preloaded:
            self._file.pause()
            if self._is_paused:
                self._is_paused = False
            else:
                self._is_paused = True

    def forward(self, seconds):
        """Advance playback position.

        Note
        ----
        When the backend is set to ``"pygame"``, this will not forward
        immediately, but play a short period of the beginning of the file! This
        is a Pygame issue which we cannot fix right now.

        Parameters
        ----------
        seconds : int
            amount to advance (in seconds)

        """

        if self._is_preloaded:
            if self._backend == "pygame":
                self._file.skip(float(seconds))
                new_frame = self._file.get_frame()
            else:
                pos = self._file.current_playtime + seconds
                self._file.seek(pos)
                new_frame = int(pos * self._file.fps)
            self._frame = new_frame

    def rewind(self):
        """Rewind to start of video stimulus."""

        if self._is_preloaded:
            self._file.rewind()
            self._frame = 0

    def _update_surface(self, frame):
        """Update surface with newly available video frame."""

        if self._backend == "mediadecoder" and not self._surface_locked:
            self._surface = frame
            self._new_frame_available = True

    def present(self):
        """Present current frame.

        This method waits for the next frame and presents it on the screen.
        When using OpenGL, the method blocks until this frame is actually being
        written to the screen.

        Note
        ----
        When the audio from the video should be played as well, the audiosystem
        has to be stopped (by calling ``expyriment.control.stop_audiosystem()``)
        BEFORE the video stimulus is preloaded! After the stimulus has been
        played the audiosystem can be started again (by calling
        ``expyriment.control.start_audiosystem()``).

        When showing videos in large dimensions, and your computer is not fast
        enough, frames might be dropped! When using ``Video.wait_frame()`` or
        ``Video.wait_end()``, dropped video frames will be reported and logged.

        """

        start = Clock.monotonic_time()
        while not self.new_frame_available:
            if not self.is_playing:
                return
        diff = self.frame - self._frame
        if diff > 1:
            warn_message = repr(diff - 1) + " video frame(s) dropped!"
            print(warn_message)
            _internals.active_exp._event_file_warn(
                "Video,warning," + warn_message)
        self._frame = self.frame
        self.update()
        return (Clock.monotonic_time() - start) * 1000

    def update(self):
        """Update the screen."""

        if not self.is_playing:
            return
        start = Clock.monotonic_time()
        self._surface_locked = True
        if not _internals.active_exp._screen.open_gl:
            _internals.active_exp._screen.surface.blit(
                pygame.surfarray.make_surface(self._surface.swapaxes(0,1)),
                self._pos)
            self._surface_locked = False
            self._new_frame_available = False
        else:
            ogl_screen = _visual._LaminaPanelSurface(
                self._surface, quadDims=(1,1,1,1), position=self._position)
            self._surface_locked = False
            self._new_frame_available = False
            ogl_screen.display()
        _internals.active_exp._screen.update()

    def _wait(self, frame=None, callback_function=None,
              process_control_events=True):
        """Wait until frame was shown or end of video and update screen.

        Parameters
        ----------
        frame : int, optional
            number of the frame to stop after
        callback_function : function, optional
            function to repeatedly execute during waiting loop
        process_control_events : bool, optional
            process ``io.Keyboard.process_control_keys()`` and
            ``io.Mouse.process_quit_event()`` (default = True)

        Notes
        ------
        This will also by default process control events (quit and pause).
        Thus, keyboard events will be cleared from the cue and cannot be
        received by a Keyboard().check() anymore!

        See Also
        --------
        design.experiment.register_wait_callback_function

        """

        while self.is_playing:

            if _internals.skip_wait_methods:
                return

            self.present()
            if frame is not None and self.frame >= frame:
                break

            if isinstance(callback_function, FunctionType):
                rtn_callback = callback_function()
                if isinstance(rtn_callback, CallbackQuitEvent):
                    return rtn_callback
            if _internals.active_exp.is_initialized:
                rtn_callback = _internals.active_exp._execute_wait_callback()
                if isinstance(rtn_callback, CallbackQuitEvent):
                    return rtn_callback
                if process_control_events:
                    if _internals.active_exp.mouse.process_quit_event():
                        break

            for event in pygame.event.get(pygame.KEYDOWN):
                if _internals.active_exp.is_initialized and \
                   process_control_events and \
                   event.type == pygame.KEYDOWN and (
                       event.key == self.Keyboard.get_quit_key() or
                       event.key == self.Keyboard.get_pause_key()):
                    self.pause()
                    self.Keyboard.process_control_keys(event, self.stop)
                    self.play()

    def wait_frame(self, frame, callback_function=None,
                   process_control_events=True):
        """Wait until certain frame was shown and constantly update screen.

        Parameters
        ----------
        frame : int
            number of the frame to stop after
        callback_function : function, optional
            function to repeatedly execute during waiting loop
        process_control_events : bool, optional
            process ``io.Keyboard.process_control_keys()`` and
            ``io.Mouse.process_quit_event()`` (default = True)

        Notes
        ------
        This will also by default process control events (quit and pause).
        Thus, keyboard events will be cleared from the cue and cannot be
        received by a Keyboard().check() anymore!
        If keyboard events should not be cleared, a loop has to be created
        manually like::

            video.present()
            while video.is_playing and video.frame < frame:
                while not video.new_frame_available:
                    key = exp.keyboard.check()
                    if key == ...
                video.update()
            video.stop()

        """

        if self.is_playing:
            self._wait(frame, callback_function, process_control_events)

    def wait_end(self, callback_function=None, process_control_events=True):
        """Wait until video has ended and constantly update screen.

        Parameters
        ----------
        callback_function : function, optional
            function to repeatedly execute during waiting loop
        process_control_events : bool, optional
            process ``io.Keyboard.process_control_keys()`` and
            ``io.Mouse.process_quit_event()`` (default = True)

        Notes
        ------
        This will also by default process control events (quit and pause).
        Thus, keyboard events will be cleared from the cue and cannot be
        received by a Keyboard().check() anymore!
        If keyboard events should not be cleared, a loop has to be created
        manually like::

            video.present()
            while video.is_playing and video.frame < frame:
                while not video.new_frame_available:
                    key = exp.keyboard.check()
                    if key == ...
                video.update()
            video.stop()

        """

        self._wait(callback_function, process_control_events)