main
  1#!/usr/bin/env python3
  2"""
  3Validators - Check if GIFs meet Slack's requirements.
  4
  5These validators help ensure your GIFs meet Slack's size and dimension constraints.
  6"""
  7
  8from pathlib import Path
  9
 10
 11def validate_gif(
 12    gif_path: str | Path, is_emoji: bool = True, verbose: bool = True
 13) -> tuple[bool, dict]:
 14    """
 15    Validate GIF for Slack (dimensions, size, frame count).
 16
 17    Args:
 18        gif_path: Path to GIF file
 19        is_emoji: True for emoji (128x128 recommended), False for message GIF
 20        verbose: Print validation details
 21
 22    Returns:
 23        Tuple of (passes: bool, results: dict with all details)
 24    """
 25    from PIL import Image
 26
 27    gif_path = Path(gif_path)
 28
 29    if not gif_path.exists():
 30        return False, {"error": f"File not found: {gif_path}"}
 31
 32    # Get file size
 33    size_bytes = gif_path.stat().st_size
 34    size_kb = size_bytes / 1024
 35    size_mb = size_kb / 1024
 36
 37    # Get dimensions and frame info
 38    try:
 39        with Image.open(gif_path) as img:
 40            width, height = img.size
 41
 42            # Count frames
 43            frame_count = 0
 44            try:
 45                while True:
 46                    img.seek(frame_count)
 47                    frame_count += 1
 48            except EOFError:
 49                pass
 50
 51            # Get duration
 52            try:
 53                duration_ms = img.info.get("duration", 100)
 54                total_duration = (duration_ms * frame_count) / 1000
 55                fps = frame_count / total_duration if total_duration > 0 else 0
 56            except:
 57                total_duration = None
 58                fps = None
 59
 60    except Exception as e:
 61        return False, {"error": f"Failed to read GIF: {e}"}
 62
 63    # Validate dimensions
 64    if is_emoji:
 65        optimal = width == height == 128
 66        acceptable = width == height and 64 <= width <= 128
 67        dim_pass = acceptable
 68    else:
 69        aspect_ratio = (
 70            max(width, height) / min(width, height)
 71            if min(width, height) > 0
 72            else float("inf")
 73        )
 74        dim_pass = aspect_ratio <= 2.0 and 320 <= min(width, height) <= 640
 75
 76    results = {
 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    }
 89
 90    # Print if verbose
 91    if verbose:
 92        print(f"\nValidating {gif_path.name}:")
 93        print(
 94            f"  Dimensions: {width}x{height}"
 95            + (
 96                f" ({'optimal' if optimal else 'acceptable'})"
 97                if is_emoji and acceptable
 98                else ""
 99            )
100        )
101        print(
102            f"  Size: {size_kb:.1f} KB"
103            + (f" ({size_mb:.2f} MB)" if size_mb >= 1.0 else "")
104        )
105        print(
106            f"  Frames: {frame_count}"
107            + (f" @ {fps:.1f} fps ({total_duration:.1f}s)" if fps else "")
108        )
109
110        if not dim_pass:
111            print(
112                f"  Note: {'Emoji should be 128x128' if is_emoji else 'Unusual dimensions for Slack'}"
113            )
114
115        if size_mb > 5.0:
116            print(f"  Note: Large file size - consider fewer frames/colors")
117
118    return dim_pass, results
119
120
121def is_slack_ready(
122    gif_path: str | Path, is_emoji: bool = True, verbose: bool = True
123) -> bool:
124    """
125    Quick check if GIF is ready for Slack.
126
127    Args:
128        gif_path: Path to GIF file
129        is_emoji: True for emoji GIF, False for message GIF
130        verbose: Print feedback
131
132    Returns:
133        True if dimensions are acceptable
134    """
135    passes, _ = validate_gif(gif_path, is_emoji, verbose)
136    return passes