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