diff options
Diffstat (limited to 'src/samples/slideshow.py')
-rwxr-xr-x | src/samples/slideshow.py | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/src/samples/slideshow.py b/src/samples/slideshow.py new file mode 100755 index 0000000..98704d0 --- /dev/null +++ b/src/samples/slideshow.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# libavg - Media Playback Engine. +# Copyright (C) 2003-2014 Ulrich von Zadow +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Current versions can be found at www.libavg.de +# +# Original author of this file: Thomas Schott <scotty@c-base.org> +# + +""" +Image slideshow example which shows all images found in the current working +directory (default) or the one provided via the "app_image_dir" setting. + +Images are cross-faded and some random motion/scaling is applied while they're shown. +This example also shows how to use libavg's BitmapManager to asynchronously load images +in background. +""" + +# TODO: +# add app settings for: +# * show/transition intervals +# * max. move distance +# * sorted/shuffled show order (shuffled yet) + +import sys +import os +from random import shuffle, randint +from libavg import avg, player, app + + +IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tga', '.tif', '.tiff'] +SHOW_INTERVAL = 6000 # [ms] +TRANS_INTERVAL = 2000 # [ms] +CHANGE_INTERVAL = SHOW_INTERVAL + TRANS_INTERVAL +ANIM_INTERVAL = CHANGE_INTERVAL + TRANS_INTERVAL +ANIM_MAX_MOVE = 200 # [px] + +BitmapMgr = avg.BitmapManager.get() + + +def scaleMax(srcSize, maxSize): + """ + Returns scrSize aspect correct scaled to fit right into maxSize. + """ + aspect = srcSize.x / srcSize.y + if aspect < maxSize.x / maxSize.y: + return avg.Point2D(maxSize.y * aspect, maxSize.y) + return avg.Point2D(maxSize.x, maxSize.x / aspect) + +def scaleMin(srcSize, minSize): + """ + Returns scrSize aspect correct scaled to completely fill minSize. + """ + aspect = srcSize.x / srcSize.y + if aspect < minSize.x / minSize.y: + return avg.Point2D(minSize.x, minSize.x / aspect) + return avg.Point2D(minSize.y * aspect, minSize.y) + + +class Slide(avg.ImageNode): + HIDE_DONE = avg.Publisher.genMessageID() + + def __init__(self, parent=None, **kwargs): + super(Slide, self).__init__(opacity=0.0, **kwargs) + self.registerInstance(self, parent) + self.publish(Slide.HIDE_DONE) + + def show(self): + s = self.getMediaSize() + assert s.x and s.y + # initial size and position (scaled to screen size and centered) + self.size = scaleMax(s, self.parent.size) + self.pos = (self.parent.size - self.size) * 0.5 + # random final size and position (center moved by (dx, dy) and scaled up accordingly) + dx = float(randint(-ANIM_MAX_MOVE, ANIM_MAX_MOVE)) + dy = float(randint(-ANIM_MAX_MOVE, ANIM_MAX_MOVE)) + size = scaleMin(s, self.size + avg.Point2D(abs(dx), abs(dy)) * 2.0) + pos = self.pos + avg.Point2D(dx, dy) + (self.size - size) * 0.5 + # start in-transition + avg.fadeIn(self, TRANS_INTERVAL) + # start move/scale animation + avg.ParallelAnim([ + avg.LinearAnim(self, 'size', ANIM_INTERVAL, self.size, size), + avg.LinearAnim(self, 'pos', ANIM_INTERVAL, self.pos, pos)]).start() + + def hide(self): + # start out-transition, notify subscribers when finished + avg.fadeOut(self, TRANS_INTERVAL, + lambda: self.notifySubscribers(Slide.HIDE_DONE, [self])) + + +class Slideshow(app.MainDiv): + def onArgvParserCreated(self, parser): + parser.set_usage('%prog <images dir>') + + def onArgvParsed(self, options, args, parser): + if len(args) != 1: + parser.print_help() + sys.exit(1) + self._imagesDir = args[0] + + def onInit(self): + if not os.path.isdir(self._imagesDir): + avg.logger.error('Directory [%s] not found' % self._imagesDir) + exit(1) + avg.logger.info('Scanning directory [%s] ...' % self._imagesDir) + + imgExts = tuple(IMAGE_EXTENSIONS + [ext.upper() for ext in IMAGE_EXTENSIONS]) + self.__imgFiles = [os.path.join(self._imagesDir, imgFile) for imgFile in + filter(lambda f: f.endswith(imgExts), os.listdir(self._imagesDir))] + if not self.__imgFiles: + avg.logger.error('No image files found, ' + 'scanned file extensions:\n%s' % (', '.join(imgExts))) + exit(1) + l = len(self.__imgFiles) + avg.logger.info('%d image file%s found' % (l, 's' if l > 1 else '')) + shuffle(self.__imgFiles) + + self.__slidesDiv = avg.DivNode(size=self.size, parent=self) + # ping-pong two slides for cross-fade transition + self.__newSlide = Slide(parent=self.__slidesDiv) + self.__oldSlide = Slide(href=self.__imgFiles[0], parent=self.__slidesDiv) + # HIDE_DONE notifications will trigger asynchronous pre-loading of the next image + self.__newSlide.subscribe(Slide.HIDE_DONE, self.__asyncPreload) + self.__oldSlide.subscribe(Slide.HIDE_DONE, self.__asyncPreload) + self.__currentIdx = 0 + self.__changeSlide() + player.setInterval(CHANGE_INTERVAL, self.__changeSlide) + + def __asyncPreload(self, slide): + self.__currentIdx = (self.__currentIdx + 1) % len(self.__imgFiles) + BitmapMgr.loadBitmap(self.__imgFiles[self.__currentIdx], slide.setBitmap) + + def __changeSlide(self): + self.__oldSlide, self.__newSlide = self.__newSlide, self.__oldSlide + self.__slidesDiv.reorderChild(self.__newSlide, 1) # move to top + self.__newSlide.show() + self.__oldSlide.hide() + + +if __name__ == '__main__': + app.App().run(Slideshow()) + |