Skip to content

Instantly share code, notes, and snippets.

@nimatrueway
Forked from Nua07/python_pillow_webp2gif.py
Last active December 2, 2024 15:58
Show Gist options
  • Save nimatrueway/0e743d92056e2c5f995e25b848a1bdcd to your computer and use it in GitHub Desktop.
Save nimatrueway/0e743d92056e2c5f995e25b848a1bdcd to your computer and use it in GitHub Desktop.
Convert webp animations to gif preserving image quality (RGB = 24bit)
#!/usr/bin/env python3
from sys import argv
from pathlib import Path
from PIL import Image, ImageSequence
def webp2gif(path: Path):
img = Image.open(path)
frames: list[Image.Image] = []
for frame in ImageSequence.Iterator(img):
im2 = Image.new("RGB", frame.size, (255, 255, 255))
bands = frame.split()
mask = bands[3] if len(bands)>3 else None
im2.paste(frame, mask=mask)
frames.append(im2.convert('RGB'))
frames[0].save(path.with_suffix('.gif'),
format='gif',
save_all=True,
append_images=frames[1:],
optimize=True,
duration=img.info.get("duration", 10),
loop=img.info.get("loop", 0),
quality=100)
webp2gif(Path(argv[1]))
@Vanawy
Copy link

Vanawy commented Feb 9, 2022

Thank you!

@reviveMC74
Copy link

I suggest replacing line 13 (im2.paste(frame, mask=frame.split()[3])) with these 3 lines:

bands = frame.split()
mask = bands[3] if len(bands)>3 else None
im2.paste(frame, mask=mask)

If the .webp contains only R, G, B and no A (opacity) bands, there are only 3 bands, so 'frame.split()[3]' causes an exception.

Thanks for the example.

@WebLeach
Copy link

@reviveMC74 thanksss i was stuck here

@nimatrueway
Copy link
Author

I suggest replacing line 13 (im2.paste(frame, mask=frame.split()[3])) with these 3 lines:

bands = frame.split()
mask = bands[3] if len(bands)>3 else None
im2.paste(frame, mask=mask)

If the .webp contains only R, G, B and no A (opacity) bands, there are only 3 bands, so 'frame.split()[3]' causes an exception.

Thanks for the example.

Thanks, applied.

@jacobeva
Copy link

Thanks man!

@crupest
Copy link

crupest commented Dec 2, 2024

@nimatrueway Thanks for the great script!

@reviveMC74 Your handling of alpha is still a little buggy. It works good for me for a long time, until I meet a special image today. After conversion, the transparent background turn into green. I guess maybe R G B A are not the good order.

Here is my patch. Firstly you don't have to convert alpha for each frame. Just keep it and let Pillow handle it at last when combining them to gif.
Secondly, you have to add some arguments to make alpha work in final git.

for frame in ImageSequence.Iterator(img):
        im2 = Image.new("RGBA", frame.size, (255, 255, 255, 255))
        im2.paste(frame)
        frames.append(im2)

If you don't care about background, you can even just do this.

for frame in ImageSequence.Iterator(img):
       frames.append(frame.convert('RGBA').copy())

You have to add an argument to make alpha work when creating gif.

frames[0].save(path.with_suffix('.gif'),
                format='gif',
                save_all=True,
                append_images=frames[1:],
                optimize=True,
                duration=img.info.get("duration", 10),
                loop=img.info.get("loop", 0),
                disposal=2,
                quality=100)

Note the disposal=2. If you don't set it, the new frame will not swipe the old content of last frame and produce a messy view.

Anyway, thanks for your work. It really resolves my demand in a really easy way!🥰

UPDATE:

for frame in ImageSequence.Iterator(img):
       frames.append(frame.copy().convert('RGBA'))

This should be better. Copy first, then convert.

UPDATE 2:

Looks like alpha channel will lead to palette size problem when combining to final gif. But I have to figure this out in the future, as I have no much time for now.

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