Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Creates optimized animated GIFs for Slack emoji (128x128) or messages (480x480) using Python PIL with polished drawing primitives.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
core/validators.py
1#!/usr/bin/env python32"""3Validators - Check if GIFs meet Slack's requirements.45These validators help ensure your GIFs meet Slack's size and dimension constraints.6"""78from pathlib import Path91011def validate_gif(12gif_path: str | Path, is_emoji: bool = True, verbose: bool = True13) -> tuple[bool, dict]:14"""15Validate GIF for Slack (dimensions, size, frame count).1617Args:18gif_path: Path to GIF file19is_emoji: True for emoji (128x128 recommended), False for message GIF20verbose: Print validation details2122Returns:23Tuple of (passes: bool, results: dict with all details)24"""25from PIL import Image2627gif_path = Path(gif_path)2829if not gif_path.exists():30return False, {"error": f"File not found: {gif_path}"}3132# Get file size33size_bytes = gif_path.stat().st_size34size_kb = size_bytes / 102435size_mb = size_kb / 10243637# Get dimensions and frame info38try:39with Image.open(gif_path) as img:40width, height = img.size4142# Count frames43frame_count = 044try:45while True:46img.seek(frame_count)47frame_count += 148except EOFError:49pass5051# Get duration52try:53duration_ms = img.info.get("duration", 100)54total_duration = (duration_ms * frame_count) / 100055fps = frame_count / total_duration if total_duration > 0 else 056except:57total_duration = None58fps = None5960except Exception as e:61return False, {"error": f"Failed to read GIF: {e}"}6263# Validate dimensions64if is_emoji:65optimal = width == height == 12866acceptable = width == height and 64 <= width <= 12867dim_pass = acceptable68else:69aspect_ratio = (70max(width, height) / min(width, height)71if min(width, height) > 072else float("inf")73)74dim_pass = aspect_ratio <= 2.0 and 320 <= min(width, height) <= 6407576results = {77"file": str(gif_path),78"passes": dim_pass,79"width": width,80"height": height,81"size_kb": size_kb,82"size_mb": size_mb,83"frame_count": frame_count,84"duration_seconds": total_duration,85"fps": fps,86"is_emoji": is_emoji,87"optimal": optimal if is_emoji else None,88}8990# Print if verbose91if verbose:92print(f"\nValidating {gif_path.name}:")93print(94f" Dimensions: {width}x{height}"95+ (96f" ({'optimal' if optimal else 'acceptable'})"97if is_emoji and acceptable98else ""99)100)101print(102f" Size: {size_kb:.1f} KB"103+ (f" ({size_mb:.2f} MB)" if size_mb >= 1.0 else "")104)105print(106f" Frames: {frame_count}"107+ (f" @ {fps:.1f} fps ({total_duration:.1f}s)" if fps else "")108)109110if not dim_pass:111print(112f" Note: {'Emoji should be 128x128' if is_emoji else 'Unusual dimensions for Slack'}"113)114115if size_mb > 5.0:116print(f" Note: Large file size - consider fewer frames/colors")117118return dim_pass, results119120121def is_slack_ready(122gif_path: str | Path, is_emoji: bool = True, verbose: bool = True123) -> bool:124"""125Quick check if GIF is ready for Slack.126127Args:128gif_path: Path to GIF file129is_emoji: True for emoji GIF, False for message GIF130verbose: Print feedback131132Returns:133True if dimensions are acceptable134"""135passes, _ = validate_gif(gif_path, is_emoji, verbose)136return passes137