Files

71 lines
2.6 KiB
Python
Raw Permalink Normal View History

"""Convert GIFs to 360x360 for the JC3636W518 (round 360x360 display).
Each frame is scaled so the GIF covers 360x360, then center-cropped. Frame durations and looping are kept.
Pillow stores only the changed part of each frame, which keeps files small.
Usage:
pip install pillow
python gif_to_360.py <input.gif or folder> [more ...] -o <output folder>
The device stores one background GIF in an 11.5 MB flash area: bigger results are reported as too large.
"""
import argparse
import os
import sys
from PIL import Image, ImageSequence
SIZE = 360
MAX_BYTES = 11_500_000
def convert(src, dst):
im = Image.open(src)
w, h = im.size
scale = max(SIZE / w, SIZE / h)
nw, nh = max(SIZE, round(w * scale)), max(SIZE, round(h * scale))
left, top = (nw - SIZE) // 2, (nh - SIZE) // 2
frames, durations = [], []
for frame in ImageSequence.Iterator(im):
durations.append(frame.info.get("duration", im.info.get("duration", 100)) or 100)
rgba = frame.convert("RGBA") # fully composited frame
if (nw, nh) != (w, h):
rgba = rgba.resize((nw, nh), Image.LANCZOS)
rgba = rgba.crop((left, top, left + SIZE, top + SIZE))
# the device shows the GIF on black: flatten transparency onto black
bg = Image.new("RGBA", rgba.size, (0, 0, 0, 255))
bg.alpha_composite(rgba)
frames.append(bg.convert("RGB"))
frames[0].save(dst, save_all=True, append_images=frames[1:], duration=durations,
loop=im.info.get("loop", 0), optimize=True, disposal=1)
size = os.path.getsize(dst)
fps = len(durations) * 1000 / max(1, sum(durations))
note = "" if size <= MAX_BYTES else " ** TOO LARGE for the device (max 11.5 MB) **"
print(f"{os.path.basename(src)}: {w}x{h}, {len(frames)} frames, {fps:.1f} fps "
f"-> {os.path.basename(dst)} {size / 1e6:.2f} MB{note}")
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("inputs", nargs="+", help="GIF files or folders")
ap.add_argument("-o", "--out", required=True, help="output folder")
args = ap.parse_args()
os.makedirs(args.out, exist_ok=True)
files = []
for p in args.inputs:
if os.path.isdir(p):
files += [os.path.join(p, f) for f in sorted(os.listdir(p)) if f.lower().endswith(".gif")]
else:
files.append(p)
if not files:
sys.exit("no GIF files found")
for f in files:
name = os.path.splitext(os.path.basename(f))[0]
convert(f, os.path.join(args.out, name + ".gif"))
if __name__ == "__main__":
main()