Coverage for pyguymer3/image/gifsicle.py: 4%

23 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 gifsicle( 

5 fname1, 

6 /, 

7 *, 

8 chunksize = 1048576, 

9 debug = __debug__, 

10 gifsiclePath = None, 

11 timeout = 60.0, 

12): 

13 """ 

14 "gifsicle" does modify, and it does touch, the image even if it cannot make 

15 it smaller, therefore it is NOT safe to keep on running "gifsicle" on the 

16 same GIF over and over again. 

17 

18 In my own testing (August 2020) I have found that "gifsicle" switches 

19 between two different images when it is run repeatadly (try finding the MD5 

20 hash of the images) and that the only differences between these identically 

21 sized images is the order of the colours in the colour map (try running 

22 "gifsicle --color-info file.gif" yourself after each call and then "diff" 

23 the output). 

24 

25 chunksize : int, optional 

26 the size of the chunks of any files which are read in (in bytes) 

27 """ 

28 

29 # Import standard modules ... 

30 import os 

31 import shutil 

32 import subprocess 

33 import tempfile 

34 

35 # Import sub-functions ... 

36 from ..sha512 import sha512 

37 

38 # ************************************************************************** 

39 

40 # Try to find the paths if the user did not provide them ... 

41 if gifsiclePath is None: 

42 gifsiclePath = shutil.which("gifsicle") 

43 assert gifsiclePath is not None, "\"gifsicle\" is not installed" 

44 

45 # Check that the image exists ... 

46 if not os.path.exists(fname1): 

47 raise Exception(f"\"{fname1}\" does not exist") from None 

48 

49 # Create temporary directory ... 

50 with tempfile.TemporaryDirectory(prefix = "gifsicle.") as tname: 

51 # Create temporary name ... 

52 fname2 = f"{tname}/image.gif" 

53 

54 # Optimise GIF ... 

55 subprocess.run( 

56 [ 

57 gifsiclePath, 

58 "--unoptimize", 

59 "--optimize=3", 

60 "--output", fname2, 

61 fname1, 

62 ], 

63 check = True, 

64 encoding = "utf-8", 

65 stderr = subprocess.DEVNULL, 

66 stdout = subprocess.DEVNULL, 

67 timeout = timeout, 

68 ) 

69 

70 # Find the two sizes and don't replace the original if the new one is 

71 # larger, or equal ... 

72 if os.path.getsize(fname2) >= os.path.getsize(fname1): 

73 if debug: 

74 print(f"INFO: Skipping because \"{fname2}\" is larger than, or equal to, \"{fname1}\"") 

75 return 

76 

77 # Find the two hashes and don't replace the original if the new one is 

78 # the same ... 

79 if sha512(fname1, chunksize = chunksize) == sha512(fname2, chunksize = chunksize): 

80 if debug: 

81 print(f"INFO: Skipping because \"{fname2}\" is the same as \"{fname1}\"") 

82 return 

83 

84 # Replace the original ... 

85 shutil.move(fname2, fname1)