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

1#!/usr/bin/env python3 

2 

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. 

24 

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. 

27 

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 

68 

69 Notes 

70 ----- 

71 Copyright 2017 Thomas Guymer [1]_ 

72 

73 References 

74 ---------- 

75 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3 

76 """ 

77 

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 

85 

86 # Import sub-functions ... 

87 from ..image import optimise_image 

88 

89 # ************************************************************************** 

90 

91 # Initialize list ... 

92 tmpImgs = [] 

93 

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") 

102 

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 ) 

111 

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 

120 

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 ) 

131 

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 )