Source code for pympress.media_overlays.gif_backend

# -*- coding: utf-8 -*-
#       media_overlays/
#       Copyright 2018 Cimbali <>
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       GNU General Public License for more details.
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
:mod:`pympress.media_overlays.gif` -- widget to play gif images as videos

import logging
logger = logging.getLogger(__name__)

import gi
import cairo
gi.require_version('Gtk', '3.0')
from gi.repository import Gdk, GLib, GdkPixbuf

from pympress.media_overlays import base

[docs] class GifOverlay(base.VideoOverlay): """ A simple overlay mimicking the functionality of showing videos, but showing gifs instead. """ #: A :class:`~GdkPixbuf.PixbufAnimation` containing all the frames and their timing for the displayed gif anim = None #: A :class:`~GdkPixbuf.PixbufAnimationIter` which will provide the timely access to the frames in `~anim` anim_iter = None #: A `tuple` of (`int`, `int`) indicating the size of the bounding box of the gif base_size = None #: The :class:`~cairo.Matrix` defining the zoom & shift to scale the gif transform = None def __init__(self, *args, **kwargs): super(GifOverlay, self).__init__(*args, **kwargs) # override: no toolbar or interactive stuff for a gif, replace the whole widget area with a GdkPixbuf self.autoplay = True self.toolbar.set_visible(False) # we'll manually draw on the movie zone self.movie_zone.connect('draw', self.draw) self.movie_zone.connect('configure-event', self.set_transform) def _set_file(self, filepath): """ Sets the media file to be played by the widget. Args: filepath (`pathlib.Path`): The path to the media file path """ self.anim = GdkPixbuf.PixbufAnimation.new_from_file(str(filepath)) self.base_size = (self.anim.get_width(), self.anim.get_height()) self.anim_iter = self.anim.get_iter(None) self.set_transform() self.advance_gif()
[docs] def set_transform(self, *args): """ Compute the transform to scale (not stretch nor crop) the gif. """ widget_size = (self.movie_zone.get_allocated_width(), self.movie_zone.get_allocated_height()) scale = min(widget_size[0] / self.base_size[0], widget_size[1] / self.base_size[1]) dx = widget_size[0] - scale * self.base_size[0] dy = widget_size[1] - scale * self.base_size[1] self.transform = cairo.Matrix(xx = scale, yy = scale, x0 = dx / 2, y0 = dy / 2)
[docs] def draw(self, widget, ctx): """ Simple resized drawing: get the pixbuf, set the transform, draw the image. """ if self.anim_iter is None: return False try: ctx.transform(self.transform) Gdk.cairo_set_source_pixbuf(ctx, self.anim_iter.get_pixbuf(), 0, 0) ctx.paint() except cairo.Error: logger.error(_('Cairo can not draw gif'), exc_info = True)
[docs] def advance_gif(self): """ Advance the gif, queue redrawing if the frame changed, and schedule the next frame. """ if self.anim_iter.advance(): self.movie_zone.queue_draw() delay = self.anim_iter.get_delay_time() if delay >= 0: GLib.timeout_add(delay, self.advance_gif)
[docs] def do_set_time(self, t): """ Set the player at time t. Should run on the main thread to ensure we avoid reentrency problems. Args: t (`int`): the timestamp, in ms Returns: `bool`: `True` iff this function should be run again (:meth:`~GLib.idle_add` convention) """ start = GLib.TimeVal() GLib.DateTime.new_now_local().to_timeval(start) start.add(-t) self.anim_iter = self.anim.get_iter(start) self.advance_gif() return False
# a bunch of inherited functions that do nothing, for gifs
[docs] def mute(self, *args): pass
[docs] def is_playing(self): return True
[docs] def do_stop(self): pass
[docs] def do_play(self): return False
[docs] def do_play_pause(self): return False
[docs] @classmethod def setup_backend(cls): """ Returns the name of this backend. """ return _('GdkPixbuf gif player')