From be65de3ce57073216d226f89e475c113e76c040e Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sun, 20 Jul 2014 10:11:12 -0400 Subject: [PATCH 1/3] Add ffpyplayer provider for image. --- kivy/__init__.py | 2 +- kivy/core/image/__init__.py | 1 + kivy/core/image/img_ffpyplayer.py | 86 +++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 kivy/core/image/img_ffpyplayer.py diff --git a/kivy/__init__.py b/kivy/__init__.py index 581903f6c..12a4b72df 100644 --- a/kivy/__init__.py +++ b/kivy/__init__.py @@ -186,7 +186,7 @@ kivy_options = { 'video': ('gstplayer', 'ffmpeg', 'ffpyplayer', 'gi', 'pygst', 'pyglet', 'null'), 'audio': ('gstplayer', 'pygame', 'gi', 'pygst', 'ffpyplayer', 'sdl'), - 'image': ('tex', 'imageio', 'dds', 'gif', 'pil', 'pygame'), + 'image': ('tex', 'imageio', 'dds', 'gif', 'pil', 'pygame', 'ffpy'), 'camera': ('opencv', 'gi', 'pygst', 'videocapture', 'avfoundation'), 'spelling': ('enchant', 'osxappkit', ), 'clipboard': ('android', 'pygame', 'dummy'), } diff --git a/kivy/core/image/__init__.py b/kivy/core/image/__init__.py index ea370c06b..6ea441b4e 100644 --- a/kivy/core/image/__init__.py +++ b/kivy/core/image/__init__.py @@ -797,6 +797,7 @@ image_libs += [ ('tex', 'img_tex'), ('dds', 'img_dds'), ('pygame', 'img_pygame'), + ('ffpy', 'img_ffpyplayer'), ('pil', 'img_pil'), ('gif', 'img_gif')] libs_loaded = core_register_libs('image', image_libs) diff --git a/kivy/core/image/img_ffpyplayer.py b/kivy/core/image/img_ffpyplayer.py new file mode 100644 index 000000000..c5b9a2253 --- /dev/null +++ b/kivy/core/image/img_ffpyplayer.py @@ -0,0 +1,86 @@ +''' +FFPyPlayer: FFmpeg based image loader +''' + +__all__ = ('ImageLoaderFFPy', ) + +import ffpyplayer +from ffpyplayer.pic import ImageLoader as ffImageLoader, SWScale +from ffpyplayer.tools import set_log_callback, loglevels, get_log_callback + +from kivy.logger import Logger +from kivy.core.image import ImageLoaderBase, ImageData, ImageLoader + + +Logger.info('ImageLoaderFFPy: Using ffpyplayer {}'.format(ffpyplayer.version)) + + +logger_func = {'quiet': Logger.critical, 'panic': Logger.critical, + 'fatal': Logger.critical, 'error': Logger.error, + 'warning': Logger.warning, 'info': Logger.info, + 'verbose': Logger.debug, 'debug': Logger.debug} + + +def _log_callback(message, level): + message = message.strip() + if message: + logger_func[level]('ImageLoaderFFPy: {}'.format(message)) + +if not get_log_callback(): + set_log_callback(_log_callback) + + +class ImageLoaderFFPy(ImageLoaderBase): + '''Image loader based on the ffpyplayer library. + + .. versionadded:: 1.8.1 + + .. note: + This provider may support more formats than what is listed in + :meth:`extensions`. + ''' + + @staticmethod + def extensions(): + '''Return accepted extensions for this loader''' + # See https://www.ffmpeg.org/general.html#Image-Formats + return ('bmp', 'dpx', 'exr', 'gif', 'ico', 'jpeg', 'jpg2000', 'jpg', + 'jls', 'pam', 'pbm', 'pcx', 'pgm', 'pgmyuv', 'pic', 'png', + 'ppm', 'ptx', 'sgi', 'ras', 'tga', 'tiff', 'webp', 'xbm', + 'xface', 'xwd') + + def load(self, filename): + try: + loader = ffImageLoader(filename) + except: + Logger.warning('Image: Unable to load image <%s>' % filename) + raise + + # update internals + self.filename = filename + images = [] + + while True: + frame, t = loader.next_frame() + if frame is None: + break + images.append(frame) + if not len(images): + raise Exception('No image found in {}'.format(filename)) + + w, h = images[0].get_size() + ifmt = images[0].get_pixel_format() + if ifmt != 'rgba' and ifmt != 'rgb24': + fmt = 'rgba' + sws = SWScale(w, h, ifmt, ofmt=fmt) + for i, image in enumerate(images): + images[i] = sws.scale(image) + else: + fmt = ifmt if ifmt == 'rgba' else 'rgb' + + return [ImageData(w, h, fmt, img.to_memoryview()[0], source_image=img) + for img in images] + + +# register +ImageLoader.register(ImageLoaderFFPy) From ac625b1f2c7c9a662f8aa4014cbc023050a35665 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sun, 20 Jul 2014 10:16:13 -0400 Subject: [PATCH 2/3] Add ability to store original image data, required sometimes with memoryview to ensure underlying memory doesn't go invalid. --- kivy/core/image/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kivy/core/image/__init__.py b/kivy/core/image/__init__.py index 6ea441b4e..695533cd3 100644 --- a/kivy/core/image/__init__.py +++ b/kivy/core/image/__init__.py @@ -40,13 +40,13 @@ class ImageData(object): The container will always have at least the mipmap level 0. ''' - __slots__ = ('fmt', 'mipmaps', 'source', 'flip_vertical') + __slots__ = ('fmt', 'mipmaps', 'source', 'flip_vertical', 'source_image') _supported_fmts = ('rgb', 'rgba', 'bgr', 'bgra', 's3tc_dxt1', 's3tc_dxt3', 's3tc_dxt5', 'pvrtc_rgb2', 'pvrtc_rgb4', 'pvrtc_rgba2', 'pvrtc_rgba4', 'etc1_rgb8') def __init__(self, width, height, fmt, data, source=None, - flip_vertical=True): + flip_vertical=True, source_image=None): assert fmt in ImageData._supported_fmts #: Decoded image format, one of a available texture format @@ -62,10 +62,14 @@ class ImageData(object): #: Indicate if the texture will need to be vertically flipped self.flip_vertical = flip_vertical + # the original image, which we might need to save if it is a memoryview + self.source_image = source_image + def release_data(self): mm = self.mipmaps for item in mm.values(): item[2] = None + self.source_image = None @property def width(self): @@ -445,7 +449,6 @@ class Image(EventDispatcher): else: raise Exception('Unable to load image type {0!r}'.format(arg)) - def remove_from_cache(self): '''Remove the Image from cache. This facilitates re-loading of images from disk in case the image content has changed. From 67a2ac45da5fe4af8e54e838267cfc7c50ec8732 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sun, 20 Jul 2014 10:28:57 -0400 Subject: [PATCH 3/3] Pass on anim_delay to AsyncLoader. --- kivy/uix/image.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kivy/uix/image.py b/kivy/uix/image.py index d2b868808..8cbc88bb4 100644 --- a/kivy/uix/image.py +++ b/kivy/uix/image.py @@ -57,9 +57,10 @@ from kivy.properties import StringProperty, ObjectProperty, ListProperty, \ AliasProperty, BooleanProperty, NumericProperty from kivy.logger import Logger -# delayed imports +# delayed imports Loader = None + class Image(Widget): '''Image class, see module documentation for more information. ''' @@ -322,8 +323,8 @@ class AsyncImage(Image): if not self.is_uri(source): source = resource_find(source) self._coreimage = image = Loader.image(source, - nocache=self.nocache, - mipmap=self.mipmap) + nocache=self.nocache, mipmap=self.mipmap, + anim_delay=self.anim_delay) image.bind(on_load=self._on_source_load) image.bind(on_texture=self._on_tex_change) self.texture = image.texture