#!/usr/bin/env python3 """Generate MiniPlayer app icons (tri-color play button + ring)""" from PIL import Image, ImageDraw import math import os def create_icon(size): """Create icon at given size""" img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Background: dark rounded rect margin = int(size * 0.05) radius = int(size * 0.18) draw.rounded_rectangle( [margin, margin, size - margin, size - margin], radius=radius, fill=(28, 28, 30, 255) ) cx, cy = size / 2, size / 2 ring_radius = size * 0.38 ring_width = size * 0.06 tri_size = size * 0.28 # Colors RED = (255, 59, 48) GREEN = (52, 199, 89) BLUE = (0, 122, 255) # === Outer Ring: 3 arc segments === # Red: 150-270°, Green: 270-30°, Blue: 30-150° # Draw as thick arcs inner_r = ring_radius - ring_width / 2 outer_r = ring_radius + ring_width / 2 def draw_arc_segment(color, start_deg, end_deg): """Draw a thick arc segment""" for angle in range(int(start_deg * 10), int(end_deg * 10)): a = angle / 10.0 rad = math.radians(a) ix = cx + inner_r * math.cos(rad) iy = cy + inner_r * math.sin(rad) ox = cx + outer_r * math.cos(rad) oy = cy + outer_r * math.sin(rad) draw.line([(ix, iy), (ox, oy)], fill=color, width=max(1, int(size * 0.005))) # Ring segments (start from top, clockwise) # Red: top-left to bottom-left (180° arc, offset) draw_arc_segment(RED, -60, 60) # right side draw_arc_segment(GREEN, 60, 180) # bottom draw_arc_segment(BLUE, 180, 300) # left/top # === Play Triangle: split into 3 colored sections from center === # Triangle pointing right: vertices relative to center # Shift slightly right for visual centering of play button offset_x = tri_size * 0.08 # Triangle vertices (pointing right) top = (cx - tri_size * 0.4 + offset_x, cy - tri_size * 0.55) bottom = (cx - tri_size * 0.4 + offset_x, cy + tri_size * 0.55) right = (cx + tri_size * 0.55 + offset_x, cy) # Center of triangle tcx = (top[0] + bottom[0] + right[0]) / 3 tcy = (top[1] + bottom[1] + right[1]) / 3 # Draw 3 sections from center to each edge # Section 1 (Red): center -> top -> right draw.polygon([ (tcx, tcy), top, right ], fill=RED) # Section 2 (Green): center -> right -> bottom draw.polygon([ (tcx, tcy), right, bottom ], fill=GREEN) # Section 3 (Blue): center -> bottom -> top draw.polygon([ (tcx, tcy), bottom, top ], fill=BLUE) # Thin white lines from center to vertices (separators) line_w = max(1, int(size * 0.004)) draw.line([(tcx, tcy), top], fill=(255, 255, 255, 180), width=line_w) draw.line([(tcx, tcy), bottom], fill=(255, 255, 255, 180), width=line_w) draw.line([(tcx, tcy), right], fill=(255, 255, 255, 180), width=line_w) return img def generate_macos_icons(output_dir): """Generate macOS .appiconset""" appiconset = os.path.join(output_dir, "MiniPlayer.appiconset") os.makedirs(appiconset, exist_ok=True) sizes = { "icon_16": 16, "icon_16@2x": 32, "icon_32": 32, "icon_32@2x": 64, "icon_128": 128, "icon_128@2x": 256, "icon_256": 256, "icon_256@2x": 512, "icon_512": 512, "icon_512@2x": 1024, } images_json = [] for name, px in sizes.items(): fname = f"{name}.png" icon = create_icon(px) icon.save(os.path.join(appiconset, fname)) # Parse size and scale if "@2x" in name: base = name.replace("@2x", "") scale = "2x" sz = base.replace("icon_", "") else: scale = "1x" sz = name.replace("icon_", "") images_json.append({ "filename": fname, "idiom": "mac", "scale": scale, "size": f"{sz}x{sz}" }) print(f" {fname}: {px}x{px}") # Write Contents.json import json contents = { "images": images_json, "info": {"author": "MiniPlayer", "version": 1} } with open(os.path.join(appiconset, "Contents.json"), "w") as f: json.dump(contents, f, indent=2) print(f" Contents.json written") def generate_ios_icons(output_dir): """Generate iOS AppIcon set""" appiconset = os.path.join(output_dir, "AppIcon.appiconset") os.makedirs(appiconset, exist_ok=True) # iOS icon sizes (points x scale = pixels) ios_sizes = [ ("20x20", "2x", 40), ("20x20", "3x", 60), ("29x29", "2x", 58), ("29x29", "3x", 87), ("40x40", "2x", 80), ("40x40", "3x", 120), ("60x60", "2x", 120), ("60x60", "3x", 180), ("76x76", "2x", 152), ("83.5x83.5", "2x", 167), ("1024x1024", "1x", 1024), ] images_json = [] for sz, scale, px in ios_sizes: fname = f"icon_{px}.png" fpath = os.path.join(appiconset, fname) if not os.path.exists(fpath): icon = create_icon(px) icon.save(fpath) images_json.append({ "filename": fname, "idiom": "ios-marketing" if px == 1024 else "iphone" if "x20" in sz or "x29" in sz or "x40" in sz or "x60" in sz else "ipad", "scale": scale, "size": sz }) print(f" {fname}: {px}x{px}") import json contents = { "images": images_json, "info": {"author": "MiniPlayer", "version": 1} } with open(os.path.join(appiconset, "Contents.json"), "w") as f: json.dump(contents, f, indent=2) print(f" Contents.json written") if __name__ == "__main__": base = os.path.dirname(os.path.abspath(__file__)) print("=== macOS Icons ===") macos_dir = os.path.join(base, "Sources", "Resources", "macOS") generate_macos_icons(macos_dir) print("\n=== iOS Icons ===") ios_dir = os.path.join(base, "Sources", "Resources", "iOS") generate_ios_icons(ios_dir) # Also save a preview preview = create_icon(512) preview_path = os.path.join(base, "build", "icon_preview.png") os.makedirs(os.path.dirname(preview_path), exist_ok=True) preview.save(preview_path) print(f"\nPreview: {preview_path}") print("\nDone!")