user3704545
user3704545

Reputation: 51

How to create a fast slideshow in python?

I tried to create a slideshow in python to loop over 5500 images faster than I could manually. I used tkinter and the parameter slide_interval should do the job. The slideshow will indeed be longer if I set slide_interval=5000 for example, but it makes no differnce if I set it to 500,50 or 5, it will still take approximately same number of seconds to display each image, while what I would be interested in would be 1 or 0.5 seconds spent per image.

Here is the code:

#!/usr/bin/env python3
"""Display a slideshow from a list of filenames"""

import os
import tkinter

from itertools import cycle
from PIL import Image, ImageTk


class Slideshow(tkinter.Tk):
    """Display a slideshow from a list of filenames"""
    def __init__(self, images, slide_interval):
        """Initialize

        images = a list of filename 
        slide_interval = milliseconds to display image
        """
        tkinter.Tk.__init__(self)
        self.geometry("+0+0")
        self.slide_interval = slide_interval
        self.images = None
        self.set_images(images)
        self.slide = tkinter.Label(self)
        self.slide.pack()

    def set_images(self, images):
         self.images = cycle(images)

    def center(self):
        """Center the slide window on the screen"""
        self.update_idletasks()
        w = self.winfo_screenwidth()
        h = self.winfo_screenheight()
        size = tuple(int(_) for _ in self.geometry().split('+')[0].split('x'))
        x = w / 2 - size[0] / 2
        y = h / 2 - size[1] / 2
        self.geometry("+%d+%d" % (x, y))

    def set_image(self):
        """Setup image to be displayed"""
        self.image_name = next(self.images)
        filename, ext = os.path.splitext(self.image_name)
        self.image = ImageTk.PhotoImage(Image.open(self.image_name))

    def main(self):
        """Display the images"""
        self.set_image()
        self.slide.config(image=self.image)
        self.title(self.image_name)
        self.center()
        self.after(self.slide_interval, self.start)

    def start(self):
        """Start method"""
        self.main()
        self.mainloop()


if __name__ == "__main__":
    slide_interval = 1


    import glob
    images = glob.glob("traralgon/*.jpg")

    # start the slideshow
    slideshow = Slideshow(images, slide_interval)
    slideshow.start()

Upvotes: 3

Views: 9287

Answers (2)

Paul M.
Paul M.

Reputation: 10809

Something like this maybe:

import tkinter as tk


class Application(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.title("Slideshow")
        self.geometry("256x256")
        self.resizable(width=False, height=False)
        self.current_slide = tk.Label(self)
        self.current_slide.pack()
        self.duration_ms = 1000

    def set_image_directory(self, path):
        from pathlib import Path
        from PIL import Image, ImageTk
        from itertools import cycle

        image_paths = Path(path).glob("*.jpg")
        self.images = cycle(zip(map(lambda p: p.name, image_paths), map(ImageTk.PhotoImage, map(Image.open, image_paths))))

    def display_next_slide(self):
        name, self.next_image = next(self.images)
        self.current_slide.config(image=self.next_image)
        self.title(name)
        self.after(self.duration_ms, self.display_next_slide)

    def start(self):
        self.display_next_slide()

def main():

    application = Application()
    application.set_image_directory("dir/to/images")
    application.start()
    application.mainloop()


if __name__ == "__main__":
    import sys
    sys.exit(main())

You'll have to ask yourself if you want to load all images upfront before the slideshow starts, and just keep them in memory (this could take some time depending on how many images you have), or if you want to load images only when they should be displayed (if the interval between images is especially short, you may notice that loading the next image slows things down).

Upvotes: 4

Bryan Oakley
Bryan Oakley

Reputation: 386020

The problem is that you're calling self.main() and self.mainloop() on every interval. That will cause huge performance problems and will probably crash after only a second or two. You should never call mainloop() more than once, and there's no point in recreating the entire UI on every loop.

Instead, you need to write a function that gets the image and then configures the existing label rather than recreating the whole GUI on each iteration.

Example:

def main(self):
    ...
    self.next_image()

def next_image(self):
    self.image_name = next(self.images)
    filename, ext = os.path.splitext(self.image_name)
    self.image = tkinter.PhotoImage(file=self.image_name)
    self.slide.configure(image=self.image)
    self.after(self.slide_interval, self.next_image)

Upvotes: 1

Related Questions