Skip to content

Instantly share code, notes, and snippets.

@plallin
Forked from BigglesZX/gifextract.py
Last active June 17, 2019 14:25
Show Gist options
  • Save plallin/46bb8ddec8a1c3279c24482ae48a1e06 to your computer and use it in GitHub Desktop.
Save plallin/46bb8ddec8a1c3279c24482ae48a1e06 to your computer and use it in GitHub Desktop.
Extract frames from an animated GIF, correctly handling palettes and frame update modes
from PIL import Image
'''
Based on a script by BigglesZX: https://gist.github.com/BigglesZX/4016539
BigglesZX was adapted as follows:
- Updated to be compatible with Python 3.
- The original function 'processImage' was renamed 'extract_and_resize_frames' and was adapted as follows:
- It resizes each frames as it extracts them
- It saves all the frames to an array
- It returns the array of all frames
- a function 'resize_gif' was added which calls 'extract_and_resize_frames' to extract all the GIF frames and then
saves all frames to an output file.
- the function main() was modified to call 'resize_gif'
Functionality of the current script:
- Similar functionality to BigglesZX's original script
- Extracts all frames from a GIF and returns as array of all frames
- Resizes the GIF to a given size
JAN 2017
'''
def resize_gif(path, save_as=None, resize_to=None):
"""
Resizes the GIF to a given length:
Args:
path: the path to the GIF file
save_as (optional): Path of the resized gif. If not set, the original gif will be overwritten.
resize_to (optional): new size of the gif. Format: (int, int). If not set, the original GIF will be resized to
half of its size.
"""
all_frames = extract_and_resize_frames(path, resize_to)
if not save_as:
save_as = path
if len(all_frames) == 1:
print("Warning: only 1 frame found")
all_frames[0].save(save_as, optimize=True)
else:
all_frames[0].save(save_as, optimize=True, save_all=True, append_images=all_frames[1:], loop=1000)
def analyseImage(path):
"""
Pre-process pass over the image to determine the mode (full or additive).
Necessary as assessing single frames isn't reliable. Need to know the mode
before processing all frames.
"""
im = Image.open(path)
results = {
'size': im.size,
'mode': 'full',
}
try:
while True:
if im.tile:
tile = im.tile[0]
update_region = tile[1]
update_region_dimensions = update_region[2:]
if update_region_dimensions != im.size:
results['mode'] = 'partial'
break
im.seek(im.tell() + 1)
except EOFError:
pass
return results
def extract_and_resize_frames(path, resize_to=None):
"""
Iterate the GIF, extracting each frame and resizing them
Returns:
An array of all frames
"""
mode = analyseImage(path)['mode']
im = Image.open(path)
if not resize_to:
resize_to = (im.size[0] // 2, im.size[1] // 2)
i = 0
p = im.getpalette()
last_frame = im.convert('RGBA')
all_frames = []
try:
while True:
# print("saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile))
'''
If the GIF uses local colour tables, each frame will have its own palette.
If not, we need to apply the global palette to the new frame.
'''
if not im.getpalette():
im.putpalette(p)
new_frame = Image.new('RGBA', im.size)
'''
Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image?
If so, we need to construct the new frame by pasting it on top of the preceding frames.
'''
if mode == 'partial':
new_frame.paste(last_frame)
new_frame.paste(im, (0, 0), im.convert('RGBA'))
new_frame.thumbnail(resize_to, Image.ANTIALIAS)
all_frames.append(new_frame)
i += 1
last_frame = new_frame
im.seek(im.tell() + 1)
except EOFError:
pass
return all_frames
def main():
resize_gif('foo.gif', save_as='bar.gif')
if __name__ == "__main__":
main()
@clickinfinite
Copy link

hi, it seems that the duration info missed. how to keep the origin duration?

@JankesJanco
Copy link

hi, it seems that the duration info missed. how to keep the origin duration?

you have to copy value from im.info['duration'] to new_frame.info['duration']

@JankesJanco
Copy link

the script still not correctly handle all GIFs, see my comment to original script here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment