示例#1
0
class Video(object):
    """Read video file and draw it to the screen.

    Parameters
    ----------
    ec : instance of expyfun.ExperimentController
    file_name : str
        the video file path
    pos : array-like
        2-element array-like with X, Y elements.
    units : str
        Units to use for the position. See ``check_units`` for options.
    scale : float | str
        The scale factor. 1 is native size (pixel-to-pixel), 2 is twice as
        large, etc. If scale is a string, it must be either ``'fill'``
        (which ensures the entire ``ExperimentController`` window is
        covered by the video, at the expense of some parts of the video
        potentially being offscreen), or ``'fit'`` (which scales maximally
        while ensuring none of the video is offscreen, and may result in
        letterboxing or pillarboxing).
    center : bool
        If ``False``, the elements of ``pos`` specify the position of the lower
        left corner of the video frame; otherwise they position the center of
        the frame.
    visible : bool
        Whether to show the video when initialized. Can be toggled later using
        `Video.set_visible` method.

    Notes
    -----
    This is a somewhat pared-down implementation of video playback. Looping is
    not available, and the audio stream from the video file is discarded.
    Timing of individual frames is relegated to the pyglet media player's
    internal clock. Recommended for use only in paradigms where the relative
    timing of audio and video are unimportant (e.g., if the video is merely
    entertainment for the participant during a passive auditory task).
    """
    def __init__(self,
                 ec,
                 file_name,
                 pos=(0, 0),
                 units='norm',
                 scale=1.,
                 center=True,
                 visible=True):
        from pyglet.media import load, Player
        self._ec = ec
        # On Windows, the default is unaccelerated WMF, which is terribly slow.
        decoder = None
        if _new_pyglet():
            try:
                from pyglet.media.codecs.ffmpeg import FFmpegDecoder
                decoder = FFmpegDecoder()
            except Exception as exc:
                warnings.warn(
                    'FFmpeg decoder could not be instantiated, decoding '
                    f'performance could be compromised:\n{exc}')
        self._source = load(file_name, decoder=decoder)
        self._player = Player()
        with warnings.catch_warnings(record=True):  # deprecated eos_action
            self._player.queue(self._source)
        self._player._audio_player = None
        frame_rate = self.frame_rate
        if frame_rate is None:
            logger.warning('Frame rate could not be determined')
            frame_rate = 60.
        self._dt = 1. / frame_rate
        self._playing = False
        self._finished = False
        self._pos = pos
        self._units = units
        self._center = center
        self.set_scale(scale)  # also calls set_pos
        self._visible = visible
        self._eos_fun = self._eos_new if _new_pyglet() else self._eos_old

        self._program = _create_program(ec, tex_vert, tex_frag)
        gl.glUseProgram(self._program)
        self._buffers = dict()
        for key in ('position', 'texcoord'):
            self._buffers[key] = gl.GLuint(0)
            gl.glGenBuffers(1, pointer(self._buffers[key]))
        w, h = self.source_width, self.source_height
        tex = np.array([(0, h), (w, h), (w, 0), (0, 0)], np.float32)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._buffers['texcoord'])
        gl.glBufferData(gl.GL_ARRAY_BUFFER, tex.nbytes, tex.tobytes(),
                        gl.GL_DYNAMIC_DRAW)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
        gl.glUseProgram(0)

    def play(self, auto_draw=True):
        """Play video from current position.

        Parameters
        ----------
        auto_draw : bool
            If True, add ``self.draw`` to ``ec.on_every_flip``.

        Returns
        -------
        time : float
            The timestamp (on the parent ``ExperimentController`` timeline) at
            which ``play()`` was called.
        """
        if not self._playing:
            if auto_draw:
                self._ec.call_on_every_flip(self.draw)
            self._player.play()
            self._playing = True
        else:
            warnings.warn('ExperimentController.video.play() called when '
                          'already playing.')
        return self._ec.get_time()

    def pause(self):
        """Halt video playback.

        Returns
        -------
        time : float
            The timestamp (on the parent ``ExperimentController`` timeline) at
            which ``pause()`` was called.
        """
        if self._playing:
            try:
                idx = self._ec.on_every_flip_functions.index(self.draw)
            except ValueError:  # not auto_draw
                pass
            else:
                self._ec.on_every_flip_functions.pop(idx)
            self._player.pause()
            self._playing = False
        else:
            warnings.warn('ExperimentController.video.pause() called when '
                          'already paused.')
        return self._ec.get_time()

    def _delete(self):
        """Halt video playback and remove player."""
        if self._playing:
            self.pause()
        self._player.delete()

    def set_scale(self, scale=1.):
        """Set video scale.

        Parameters
        ----------
        scale : float | str
            The scale factor. 1 is native size (pixel-to-pixel), 2 is twice as
            large, etc. If scale is a string, it must be either ``'fill'``
            (which ensures the entire ``ExperimentController`` window is
            covered by the video, at the expense of some parts of the video
            potentially being offscreen), or ``'fit'`` (which scales maximally
            while ensuring none of the video is offscreen, which may result in
            letterboxing).
        """
        if isinstance(scale, string_types):
            _scale = self._ec.window_size_pix / np.array(
                (self.source_width, self.source_height), dtype=float)
            if scale == 'fit':
                scale = _scale.min()
            elif scale == 'fill':
                scale = _scale.max()
        self._scale = float(scale)  # allows [1, 1., '1']; others: ValueError
        if self._scale <= 0:
            raise ValueError('Video scale factor must be strictly positive.')
        self.set_pos(self._pos, self._units, self._center)

    def set_pos(self, pos, units='norm', center=True):
        """Set video position.

        Parameters
        ----------
        pos : array-like
            2-element array-like with X, Y elements.
        units : str
            Units to use for the position. See ``check_units`` for options.
        center : bool
            If ``False``, the elements of ``pos`` specify the position of the
            lower left corner of the video frame; otherwise they position the
            center of the frame.
        """
        pos = np.array(pos, float)
        if pos.size != 2:
            raise ValueError('pos must be a 2-element array')
        pos = np.reshape(pos, (2, 1))
        pix = self._ec._convert_units(pos, units, 'pix').ravel()
        offset = np.array((self.width, self.height)) // 2 if center else 0
        self._pos = pos
        self._actual_pos = pix - offset
        self._pos_unit = units
        self._pos_centered = center

    def _draw(self):
        tex = self._player.get_texture()
        gl.glUseProgram(self._program)
        gl.glActiveTexture(gl.GL_TEXTURE0)
        gl.glBindTexture(tex.target, tex.id)
        gl.glBindVertexArray(0)
        x, y = self._actual_pos
        w = self.source_width * self._scale
        h = self.source_height * self._scale
        pos = np.array([(x, y), (x + w, y), (x + w, y + h), (x, y + h)],
                       np.float32)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._buffers['position'])
        gl.glBufferData(gl.GL_ARRAY_BUFFER, pos.nbytes, pos.tobytes(),
                        gl.GL_DYNAMIC_DRAW)
        loc_pos = gl.glGetAttribLocation(self._program, b'a_position')
        gl.glEnableVertexAttribArray(loc_pos)
        gl.glVertexAttribPointer(loc_pos, 2, gl.GL_FLOAT, gl.GL_FALSE, 0, 0)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._buffers['texcoord'])
        loc_tex = gl.glGetAttribLocation(self._program, b'a_texcoord')
        gl.glEnableVertexAttribArray(loc_tex)
        gl.glVertexAttribPointer(loc_tex, 2, gl.GL_FLOAT, gl.GL_FALSE, 0, 0)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
        gl.glDrawArrays(gl.GL_QUADS, 0, 4)
        gl.glDisableVertexAttribArray(loc_pos)
        gl.glDisableVertexAttribArray(loc_tex)
        gl.glUseProgram(0)
        gl.glBindTexture(tex.target, 0)

    def draw(self):
        """Draw the video texture to the screen buffer."""
        self._player.update_texture()
        # detect end-of-stream to prevent pyglet from hanging:
        if not self._eos:
            if self._visible:
                self._draw()
        else:
            self._finished = True
            self.pause()
        self._ec.check_force_quit()

    def set_visible(self, show, flip=False):
        """Show/hide the video frame.

        Parameters
        ----------
        show : bool
            Show or hide.
        flip : bool
            If True, flip after showing or hiding.
        """
        if show:
            self._visible = True
            self._draw()
        else:
            self._visible = False
            self._ec.flip()
        if flip:
            self._ec.flip()

    # PROPERTIES
    @property
    def _eos(self):
        return self._eos_fun()

    def _eos_old(self):
        return (self._player._last_video_timestamp is not None
                and self._player._last_video_timestamp
                == self._source.get_next_video_timestamp())

    def _eos_new(self):
        ts = self._source.get_next_video_timestamp()
        dur = self._source._duration
        return ts is None or ts >= dur

    @property
    def playing(self):
        return self._playing

    @property
    def finished(self):
        return self._finished

    @property
    def position(self):
        return np.squeeze(self._pos)

    @property
    def scale(self):
        return self._scale

    @property
    def duration(self):
        return self._source.duration

    @property
    def frame_rate(self):
        return self._source.video_format.frame_rate

    @property
    def dt(self):
        return self._dt

    @property
    def time(self):
        return self._player.time

    @property
    def width(self):
        return self.source_width * self._scale

    @property
    def height(self):
        return self.source_height * self._scale

    @property
    def source_width(self):
        return self._source.video_format.width

    @property
    def source_height(self):
        return self._source.video_format.height

    @property
    def time_offset(self):
        return self._ec.get_time() - self._player.time
示例#2
0
class Video(object):
    """Read video file and draw it to the screen

    Parameters
    ----------
    ec : instance of expyfun.ExperimentController
    file_name : str
        the video file path
    pos : array-like
        2-element array-like with X, Y elements.
    units : str
        Units to use for the position. See ``check_units`` for options.
    scale : float | str
        The scale factor. 1 is native size (pixel-to-pixel), 2 is twice as
        large, etc. If `scale` is a string, it must be either ``'fill'``
        (which ensures the entire ``ExperimentController`` window is
        covered by the video, at the expense of some parts of the video
        potentially being offscreen), or ``'fit'`` (which scales maximally
        while ensuring none of the video is offscreen, and may result in
        letterboxing or pillarboxing).
    center : bool
        If ``False``, the elements of ``pos`` specify the position of the lower
        left corner of the video frame; otherwise they position the center of
        the frame.
    visible : bool
        Whether to show the video when initialized. Can be toggled later using
        ``set_visible`` method.

    Returns
    -------
    None

    Notes
    -----
    This is a somewhat pared-down implementation of video playback. Looping is
    not available, and the audio stream from the video file is discarded.
    Timing of individual frames is relegated to the pyglet media player's
    internal clock. Recommended for use only in paradigms where the relative
    timing of audio and video are unimportant (e.g., if the video is merely
    entertainment for the participant during a passive auditory task).
    """
    def __init__(self,
                 ec,
                 file_name,
                 pos=(0, 0),
                 units='norm',
                 scale=1.,
                 center=True,
                 visible=True):
        from pyglet.media import load, Player
        self._ec = ec
        self._source = load(file_name)
        self._player = Player()
        with warnings.catch_warnings(record=True):  # deprecated eos_action
            self._player.queue(self._source)
        self._player._audio_player = None
        frame_rate = self.frame_rate
        if frame_rate is None:
            logger.warning('Frame rate could not be determined')
            frame_rate = 60.
        self._dt = 1. / frame_rate
        self._texture = None
        self._playing = False
        self._finished = False
        self._pos = pos
        self._units = units
        self._center = center
        self.set_scale(scale)  # also calls set_pos
        self._visible = visible

    def play(self):
        """Play video from current position.

        Returns
        -------
        time : float
            The timestamp (on the parent ``ExperimentController`` timeline) at
            which ``play()`` was called.
        """
        if not self._playing:
            self._ec.call_on_every_flip(self.draw)
            self._player.play()
            self._playing = True
        else:
            warnings.warn('ExperimentController.video.play() called when '
                          'already playing.')
        return self._ec.get_time()

    def pause(self):
        """Halt video playback.

        Returns
        -------
        time : float
            The timestamp (on the parent ``ExperimentController`` timeline) at
            which ``pause()`` was called.
        """
        if self._playing:
            idx = self._ec.on_every_flip_functions.index(self.draw)
            self._ec.on_every_flip_functions.pop(idx)
            self._player.pause()
            self._playing = False
        else:
            warnings.warn('ExperimentController.video.pause() called when '
                          'already paused.')
        return self._ec.get_time()

    def _delete(self):
        """Halt video playback and remove player."""
        if self._playing:
            self.pause()
        self._player.delete()

    def _scale_texture(self):
        if self._texture:
            self._texture.width = self.source_width * self._scale
            self._texture.height = self.source_height * self._scale

    def set_scale(self, scale=1.):
        """Set video scale.

        Parameters
        ----------
        scale : float | str
            The scale factor. 1 is native size (pixel-to-pixel), 2 is twice as
            large, etc. If `scale` is a string, it must be either ``'fill'``
            (which ensures the entire ``ExperimentController`` window is
            covered by the video, at the expense of some parts of the video
            potentially being offscreen), or ``'fit'`` (which scales maximally
            while ensuring none of the video is offscreen, which may result in
            letterboxing).
        """
        if isinstance(scale, string_types):
            _scale = self._ec.window_size_pix / np.array(
                (self.source_width, self.source_height), dtype=float)
            if scale == 'fit':
                scale = _scale.min()
            elif scale == 'fill':
                scale = _scale.max()
        self._scale = float(scale)  # allows [1, 1., '1']; others: ValueError
        if self._scale <= 0:
            raise ValueError('Video scale factor must be strictly positive.')
        self._scale_texture()
        self.set_pos(self._pos, self._units, self._center)

    def set_pos(self, pos, units='norm', center=True):
        """Set video position.

        Parameters
        ----------
        pos : array-like
            2-element array-like with X, Y elements.
        units : str
            Units to use for the position. See ``check_units`` for options.
        center : bool
            If ``False``, the elements of ``pos`` specify the position of the
            lower left corner of the video frame; otherwise they position the
            center of the frame.
        """
        pos = np.array(pos, float)
        if pos.size != 2:
            raise ValueError('pos must be a 2-element array')
        pos = np.reshape(pos, (2, 1))
        pix = self._ec._convert_units(pos, units, 'pix').ravel()
        offset = np.array((self.width, self.height)) // 2 if center else 0
        self._pos = pos
        self._actual_pos = pix - offset
        self._pos_unit = units
        self._pos_centered = center

    def _draw(self):
        self._texture = self._player.get_texture()
        self._scale_texture()
        self._texture.blit(*self._actual_pos)

    def draw(self):
        """Draw the video texture to the screen buffer."""
        self._player.update_texture()
        # detect end-of-stream to prevent pyglet from hanging:
        if not self._eos:
            if self._visible:
                self._draw()
        else:
            self._finished = True
            self.pause()
        self._ec.check_force_quit()

    def set_visible(self, show, flip=False):
        """Show/hide the video frame."""
        if show:
            self._visible = True
            self._draw()
        else:
            self._visible = False
            self._ec.flip()
        if flip:
            self._ec.flip()

    # PROPERTIES
    @property
    def _eos(self):
        return (self._player._last_video_timestamp is not None
                and self._player._last_video_timestamp
                == self._source.get_next_video_timestamp())

    @property
    def playing(self):
        return self._playing

    @property
    def finished(self):
        return self._finished

    @property
    def position(self):
        return np.squeeze(self._pos)

    @property
    def scale(self):
        return self._scale

    @property
    def duration(self):
        return self._source.duration

    @property
    def frame_rate(self):
        return self._source.video_format.frame_rate

    @property
    def dt(self):
        return self._dt

    @property
    def time(self):
        return self._player.time

    @property
    def width(self):
        return self.source_width * self._scale

    @property
    def height(self):
        return self.source_height * self._scale

    @property
    def source_width(self):
        return self._source.video_format.width

    @property
    def source_height(self):
        return self._source.video_format.height

    @property
    def time_offset(self):
        return self._ec.get_time() - self._player.time
示例#3
0
class Video(object):
    """Read video file and draw it to the screen

    Parameters
    ----------
    ec : instance of expyfun.ExperimentController
    file_name : str
        the video file path
    pos : array-like
        2-element array-like with X, Y elements.
    units : str
        Units to use for the position. See ``check_units`` for options.
    scale : float | str
        The scale factor. 1 is native size (pixel-to-pixel), 2 is twice as
        large, etc. If `scale` is a string, it must be either ``'fill'``
        (which ensures the entire ``ExperimentController`` window is
        covered by the video, at the expense of some parts of the video
        potentially being offscreen), or ``'fit'`` (which scales maximally
        while ensuring none of the video is offscreen, and may result in
        letterboxing or pillarboxing).
    center : bool
        If ``False``, the elements of ``pos`` specify the position of the lower
        left corner of the video frame; otherwise they position the center of
        the frame.
    visible : bool
        Whether to show the video when initialized. Can be toggled later using
        ``set_visible`` method.

    Returns
    -------
    None

    Notes
    -----
    This is a somewhat pared-down implementation of video playback. Looping is
    not available, and the audio stream from the video file is discarded.
    Timing of individual frames is relegated to the pyglet media player's
    internal clock. Recommended for use only in paradigms where the relative
    timing of audio and video are unimportant (e.g., if the video is merely
    entertainment for the participant during a passive auditory task).
    """
    def __init__(self, ec, file_name, pos=(0, 0), units='norm', scale=1.,
                 center=True, visible=True):
        from pyglet.media import load, Player
        self._ec = ec
        self._source = load(file_name)
        self._player = Player()
        self._player.queue(self._source)
        self._player._audio_player = None
        frame_rate = self.frame_rate
        if frame_rate is None:
            logger.warning('Frame rate could not be determined')
            frame_rate = 60.
        self._dt = 1. / frame_rate
        self._texture = None
        self._playing = False
        self._finished = False
        self._pos = pos
        self._units = units
        self._center = center
        self.set_scale(scale)  # also calls set_pos
        self._visible = visible

    def play(self):
        """Play video from current position.

        Returns
        -------
        time : float
            The timestamp (on the parent ``ExperimentController`` timeline) at
            which ``play()`` was called.
        """
        if not self._playing:
            self._ec.call_on_every_flip(self.draw)
            self._player.play()
            self._playing = True
        else:
            warnings.warn('ExperimentController.video.play() called when '
                          'already playing.')
        return self._ec.get_time()

    def pause(self):
        """Halt video playback.

        Returns
        -------
        time : float
            The timestamp (on the parent ``ExperimentController`` timeline) at
            which ``pause()`` was called.
        """
        if self._playing:
            idx = self._ec.on_every_flip_functions.index(self.draw)
            self._ec.on_every_flip_functions.pop(idx)
            self._player.pause()
            self._playing = False
        else:
            warnings.warn('ExperimentController.video.pause() called when '
                          'already paused.')
        return self._ec.get_time()

    def _delete(self):
        """Halt video playback and remove player."""
        if self._playing:
            self.pause()
        self._player.delete()

    def _scale_texture(self):
        if self._texture:
            self._texture.width = self.source_width * self._scale
            self._texture.height = self.source_height * self._scale

    def set_scale(self, scale=1.):
        """Set video scale.

        Parameters
        ----------
        scale : float | str
            The scale factor. 1 is native size (pixel-to-pixel), 2 is twice as
            large, etc. If `scale` is a string, it must be either ``'fill'``
            (which ensures the entire ``ExperimentController`` window is
            covered by the video, at the expense of some parts of the video
            potentially being offscreen), or ``'fit'`` (which scales maximally
            while ensuring none of the video is offscreen, which may result in
            letterboxing).
        """
        if isinstance(scale, string_types):
            _scale = self._ec.window_size_pix / np.array((self.source_width,
                                                          self.source_height),
                                                         dtype=float)
            if scale == 'fit':
                scale = _scale.min()
            elif scale == 'fill':
                scale = _scale.max()
        self._scale = float(scale)  # allows [1, 1., '1']; others: ValueError
        if self._scale <= 0:
            raise ValueError('Video scale factor must be strictly positive.')
        self._scale_texture()
        self.set_pos(self._pos, self._units, self._center)

    def set_pos(self, pos, units='norm', center=True):
        """Set video position.

        Parameters
        ----------
        pos : array-like
            2-element array-like with X, Y elements.
        units : str
            Units to use for the position. See ``check_units`` for options.
        center : bool
            If ``False``, the elements of ``pos`` specify the position of the
            lower left corner of the video frame; otherwise they position the
            center of the frame.
        """
        pos = np.array(pos, float)
        if pos.size != 2:
            raise ValueError('pos must be a 2-element array')
        pos = np.reshape(pos, (2, 1))
        pix = self._ec._convert_units(pos, units, 'pix').ravel()
        offset = np.array((self.width, self.height)) // 2 if center else 0
        self._pos = pos
        self._actual_pos = pix - offset
        self._pos_unit = units
        self._pos_centered = center

    def _draw(self):
        self._texture = self._player.get_texture()
        self._scale_texture()
        self._texture.blit(*self._actual_pos)

    def draw(self):
        """Draw the video texture to the screen buffer."""
        self._player.update_texture()
        # detect end-of-stream to prevent pyglet from hanging:
        if not self._eos:
            if self._visible:
                self._draw()
        else:
            self._finished = True
            self.pause()
        self._ec.check_force_quit()

    def set_visible(self, show, flip=False):
        """Show/hide the video frame."""
        if show:
            self._visible = True
            self._draw()
        else:
            self._visible = False
            self._ec.flip()
        if flip:
            self._ec.flip()

    # PROPERTIES
    @property
    def _eos(self):
        return (self._player._last_video_timestamp is not None and
                self._player._last_video_timestamp ==
                self._source.get_next_video_timestamp())

    @property
    def playing(self):
        return self._playing

    @property
    def finished(self):
        return self._finished

    @property
    def position(self):
        return np.squeeze(self._pos)

    @property
    def scale(self):
        return self._scale

    @property
    def duration(self):
        return self._source.duration

    @property
    def frame_rate(self):
        return self._source.video_format.frame_rate

    @property
    def dt(self):
        return self._dt

    @property
    def time(self):
        return self._player.time

    @property
    def width(self):
        return self.source_width * self._scale

    @property
    def height(self):
        return self.source_height * self._scale

    @property
    def source_width(self):
        return self._source.video_format.width

    @property
    def source_height(self):
        return self._source.video_format.height

    @property
    def time_offset(self):
        return self._ec.get_time() - self._player.time