VulpesOS/tundra/tools/prepare-beatles-cover-animation.py
2026-10-22 20:42:26 +02:00

40 lines
1.8 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: MPL-2.0
"""Decode a private GIF test into bounded RGB565 frames for the cover panel."""
import argparse
from array import array
from pathlib import Path
import struct
import sys
from PIL import Image, ImageOps
def prepare(source, destination):
if source.stat().st_size > 16 * 1024 * 1024:
raise ValueError('GIF exceeds 16 MiB')
with Image.open(source) as image:
if image.format != 'GIF' or not 1 <= image.n_frames <= 240:
raise ValueError('Expected a GIF with at most 240 frames')
if image.width * image.height > 4_000_000:
raise ValueError('GIF dimensions exceed limit')
with destination.open('wb') as output:
output.write(b'VULPANM1' + struct.pack('<I', image.n_frames))
for index in range(image.n_frames):
image.seek(index)
duration = max(50, min(1000, int(image.info.get('duration', 100))))
fitted = ImageOps.contain(image.convert('RGB'), (240, 240), Image.Resampling.LANCZOS)
frame = Image.new('RGB', (240, 240))
frame.paste(fitted, ((240-fitted.width)//2, (240-fitted.height)//2))
rgb=frame.tobytes()
pixels = array('H', (((r>>3)<<11) | ((g>>2)<<5) | (b>>3)
for r,g,b in zip(rgb[0::3],rgb[1::3],rgb[2::3])))
if sys.byteorder != 'little': pixels.byteswap()
output.write(struct.pack('<I', duration)); output.write(pixels.tobytes())
return destination.stat().st_size
if __name__ == '__main__':
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('source',type=Path);parser.add_argument('destination',type=Path)
args=parser.parse_args()
print('Cover animation bytes:',prepare(args.source,args.destination))