Files
JC3636W518EN/tools/gif_to_360.py
T

71 lines
2.6 KiB
Python
Raw 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()