Skip to content

Instantly share code, notes, and snippets.

@tdhooper
Created April 9, 2020 21:48
Show Gist options
  • Save tdhooper/d024da3d80834685c395c795820628a1 to your computer and use it in GitHub Desktop.
Save tdhooper/d024da3d80834685c395c795820628a1 to your computer and use it in GitHub Desktop.
Optimize a gif to a specific file size
#!/usr/bin/env python3
import sys
import os
import ffmpeg
from pygifsicle import optimize
frames_path = sys.argv[1]
output_name = sys.argv[2]
palette_filename = 'palette.png'
def generate_gif(colors, suffix, **kwargs):
print('.', end='', flush=True)
output_filename = '{}_{}.gif'.format(output_name, suffix)
# Generate palette
(
ffmpeg
.input('{}/*.png'.format(frames_path), pattern_type='glob')
.filter('palettegen', stats_mode='diff', max_colors=colors, reserve_transparent=False)
.output(palette_filename)
.overwrite_output()
.run(quiet=True)
)
# Generate gif
palette = (
ffmpeg
.input(palette_filename)
)
frames = (
ffmpeg
.input('{}/*.png'.format(frames_path), pattern_type='glob')
)
(
ffmpeg
.filter((frames, palette), 'paletteuse', **kwargs)
.output(output_filename)
.overwrite_output()
.run(quiet=True)
)
optimize(output_filename, options=['--optimize=03', '--delay=6', '--no-warnings'])
size = os.stat(output_filename).st_size
return size
def binary_search(func, low, high, target):
while low <= high:
mid = low + (high - low) // 2
candidate = func(mid)
# Check if x is present at mid
if candidate == target:
return (candidate, mid)
# If candidate is greater, ignore left half
elif candidate < target:
low = mid + 1
# If candidate is smaller, ignore right half
else:
high = mid - 1
if candidate > target:
candidate = func(mid - 1)
return (candidate, mid)
def configure_generate_gif(**kwargs):
suffix = '-'.join(str(v) for v in kwargs.values())
def func(colors):
return generate_gif(colors, suffix, **kwargs)
return (suffix, func)
configurations = [
configure_generate_gif(dither='bayer', bayer_scale=1),
configure_generate_gif(dither='bayer', bayer_scale=2),
configure_generate_gif(dither='bayer', bayer_scale=3),
configure_generate_gif(dither='floyd_steinberg'),
configure_generate_gif(dither='sierra2'),
configure_generate_gif(dither='sierra2_4a'),
]
for suffix, func in configurations:
print(suffix, end='', flush=True)
final_size, final_colors = binary_search(func, 1, 256, 2000000)
print(' {} bytes, {} colours'.format(final_size, final_colors))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment