Coverage for pyguymer3/media/images2gif.py: 4%
24 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
1#!/usr/bin/env python3
3# Define function ...
4def images2gif(
5 imgs,
6 gif,
7 /,
8 *,
9 chunksize = 1048576,
10 debug = __debug__,
11 exiftoolPath = None,
12 fps = 25.0,
13 gifsiclePath = None,
14 jpegtranPath = None,
15 mode = "RGB",
16 optimise = True,
17 optipngPath = None,
18 screenHeight = -1,
19 screenWidth = -1,
20 strip = False,
21 timeout = 60.0,
22):
23 """Convert a sequence of images to a GIF animation.
25 This function makes a GIF animation from either a list of PIL Images or a
26 list of file paths. The user is able to set the framerate.
28 Parameters
29 ----------
30 imgs : list of PIL.Image.Image or list of str
31 the list of input PIL Images or list of paths to the input images
32 gif : str
33 the path to the output GIF
34 chunksize : int, optional
35 the size of the chunks of any files which are read in (in bytes)
36 debug : bool, optional
37 print debug messages (default False)
38 exiftoolPath : str, optional
39 the path to the "exiftool" binary (if not provided then Python will attempt to
40 find the binary itself)
41 fps : float, optional
42 the framerate, default 25.0
43 gifsiclePath : str, optional
44 the path to the "gifsicle" binary (if not provided then Python will attempt to
45 find the binary itself)
46 jpegtranPath : str, optional
47 the path to the "jpegtran" binary (if not provided then Python will attempt to
48 find the binary itself)
49 mode : str, optional
50 the mode of the outout GIF (default "RGB")
51 optimise : bool, optional
52 optimise the output GIF (default True)
53 optipngPath : str, optional
54 the path to the "optipng" binary (if not provided then Python will attempt to
55 find the binary itself)
56 screenHeight : int, optional
57 the height of the screen to downscale the input images to fit within,
58 currently only implemented if "imgs" is a list of str (default -1;
59 integers less than 100 imply no downscaling)
60 screenWidth : int, optional
61 the width of the screen to downscale the input images to fit within,
62 currently only implemented if "imgs" is a list of str (default -1;
63 integers less than 100 imply no downscaling)
64 strip : bool, optional
65 strip metadata from the output GIF (default False)
66 timeout : float, optional
67 the timeout for any requests/subprocess calls
69 Notes
70 -----
71 Copyright 2017 Thomas Guymer [1]_
73 References
74 ----------
75 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3
76 """
78 # Import special modules ...
79 try:
80 import PIL
81 import PIL.Image
82 PIL.Image.MAX_IMAGE_PIXELS = 1024 * 1024 * 1024 # [px]
83 except:
84 raise Exception("\"PIL\" is not installed; run \"pip install --user Pillow\"") from None
86 # Import sub-functions ...
87 from ..image import optimise_image
89 # **************************************************************************
91 # Initialize list ...
92 tmpImgs = []
94 # Loop over input ...
95 for img in imgs:
96 # Find out what the user supplied ...
97 match img:
98 case str():
99 # Open image as RGB (even if it is paletted) ...
100 with PIL.Image.open(img) as iObj:
101 tmpImg = iObj.convert("RGB")
103 # Check if the user wants to scale the image down to fit within
104 # a screen size ...
105 if screenWidth >= 100 and screenHeight >= 100:
106 # Resize image in place ...
107 tmpImg.thumbnail(
108 (screenWidth, screenHeight),
109 resample = PIL.Image.Resampling.LANCZOS,
110 )
112 # Convert it to whatever mode the user asked for ...
113 tmpImgs.append(tmpImg.convert(mode))
114 case PIL.Image.Image():
115 # Convert image to whatever mode the user asked for ...
116 tmpImgs.append(img.convert(mode))
117 case _:
118 # Crash ...
119 raise TypeError(f"\"img\" is an unexpected type ({repr(type(img))})") from None
121 # Save it as a GIF ...
122 # NOTE: See https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#gif
123 tmpImgs[0].save(
124 gif,
125 append_images = tmpImgs[1:],
126 duration = round(1000.0 / fps),
127 loop = 0,
128 optimise = optimise,
129 save_all = True,
130 )
132 # Optimise GIF ...
133 if strip:
134 optimise_image(
135 gif,
136 chunksize = chunksize,
137 debug = debug,
138 exiftoolPath = exiftoolPath,
139 gifsiclePath = gifsiclePath,
140 jpegtranPath = jpegtranPath,
141 optipngPath = optipngPath,
142 pool = None,
143 strip = strip,
144 timeout = timeout,
145 )