infographic_generator.py 9.31 KB
#!/usr/bin/env python3
"""
WeChat Group Daily Digest - Infographic Generator

Generates a PNG infographic summarizing WeChat group messages.
Requires: Pillow (pip install Pillow)

Usage:
    from PIL import Image, ImageDraw, ImageFont
    import textwrap
    
    # Set up your data
    stats = {"messages": 25, "articles": 5, "videos": 3, "participants": 8}
    highlight = "Key takeaway text..."
    discussions = [("sender", "message text"), ...]
    articles = [("title", "summary", "tag"), ...]
    videos = [("title", "desc"), ...]
    
    # Generate image
    generate_infographic(
        group_name="1-TGO和朋友们的AGI",
        date_str="2026-06-21",
        stats=stats,
        highlight=highlight,
        discussions=discussions,
        articles=articles,
        videos=videos,
        output_path="/data/data/com.termux/files/home/daily_digest.png"
    )
"""

from PIL import Image, ImageDraw, ImageFont
import textwrap
import os


def get_fonts():
    """Load fonts with fallbacks for Termux/Android."""
    font_paths = [
        "/data/data/com.termux/files/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        "/system/fonts/NotoSansCJK-Regular.ttc",
        "/system/fonts/NotoSansCJK-Regular.otf",
    ]
    
    font_path = None
    for fp in font_paths:
        if os.path.exists(fp):
            font_path = fp
            break
    
    if font_path:
        try:
            return {
                'title': ImageFont.truetype(font_path, 28),
                'header': ImageFont.truetype(font_path, 20),
                'body': ImageFont.truetype(font_path, 14),
                'small': ImageFont.truetype(font_path, 12),
                'tag': ImageFont.truetype(font_path, 11),
            }
        except:
            pass
    
    # Fallback to default font
    default = ImageFont.load_default()
    return {'title': default, 'header': default, 'body': default, 'small': default, 'tag': default}


def draw_rounded_rect(draw, xy, radius, fill, outline=None):
    """Draw a rounded rectangle."""
    x1, y1, x2, y2 = xy[0][0], xy[0][1], xy[1][0], xy[1][1]
    draw.rounded_rectangle([x1, y1, x2, y2], radius=radius, fill=fill, outline=outline)


def generate_infographic(
    group_name="1-TGO和朋友们的AGI",
    date_str="2026-06-21",
    stats=None,
    highlight="",
    discussions=None,
    articles=None,
    videos=None,
    output_path="/data/data/com.termux/files/home/daily_digest.png",
    width=800
):
    """Generate a daily digest infographic."""
    
    if stats is None:
        stats = {"messages": 0, "articles": 0, "videos": 0, "participants": 0}
    if discussions is None:
        discussions = []
    if articles is None:
        articles = []
    if videos is None:
        videos = []
    
    fonts = get_fonts()
    
    # Colors
    primary_color = (102, 126, 234)
    secondary_color = (118, 75, 162)
    text_color = (51, 51, 51)
    light_text = (102, 102, 102)
    white = (255, 255, 255)
    bg_light = (248, 249, 250)
    border_color = (102, 126, 234)
    
    # Calculate height dynamically
    height = 140  # header
    height += 100  # stats
    height += 110  # highlight
    height += 35  # discussion title
    height += len(discussions) * 90  # discussions
    height += 35  # articles title
    height += len(articles) * 120  # articles
    height += 35  # videos title
    height += len(videos) * 70  # videos
    height += 70  # footer + padding
    
    # Create image
    img = Image.new('RGB', (width, height), color='white')
    draw = ImageDraw.Draw(img)
    
    # Header
    header_height = 140
    draw.rectangle([0, 0, width, header_height], fill=primary_color)
    draw.text((width//2, 30), "📋 群消息日报", fill=white, font=fonts['title'], anchor="mm")
    draw.text((width//2, 65), date_str, fill=(230, 230, 230), font=fonts['header'], anchor="mm")
    
    # Group name badge
    bbox = draw.textbbox((0, 0), group_name, font=fonts['small'])
    text_w = bbox[2] - bbox[0]
    draw_rounded_rect(draw, [(width//2 - text_w//2 - 15, 85), (width//2 + text_w//2 + 15, 115)], 15, (255, 255, 255, 50))
    draw.text((width//2, 100), group_name, fill=white, font=fonts['small'], anchor="mm")
    
    y = header_height + 20
    
    # Stats section
    stats_bg = (240, 242, 255)
    draw_rounded_rect(draw, [(20, y), (width-20, y+80)], 12, stats_bg)
    
    stat_items = [
        (str(stats.get('messages', 0)), "总消息"),
        (str(stats.get('articles', 0)), "公众号"),
        (str(stats.get('videos', 0)), "视频号"),
        (str(stats.get('participants', 0)), "参与讨论")
    ]
    stat_width = (width - 40) // len(stat_items)
    for i, (num, label) in enumerate(stat_items):
        x = 30 + i * stat_width + stat_width//2
        draw.text((x, y+20), num, fill=primary_color, font=fonts['title'], anchor="mm")
        draw.text((x, y+50), label, fill=light_text, font=fonts['small'], anchor="mm")
    
    y += 100
    
    # Highlight box
    highlight_bg = (240, 242, 255)
    draw_rounded_rect(draw, [(20, y), (width-20, y+90)], 12, highlight_bg, outline=(102, 126, 234, 50))
    draw.text((40, y+15), "💡 今日亮点", fill=primary_color, font=fonts['header'])
    
    wrapped = textwrap.fill(highlight, width=50)
    lines = wrapped.split('\n')
    for i, line in enumerate(lines[:3]):
        draw.text((40, y+45 + i*18), line, fill=text_color, font=fonts['small'])
    
    y += 110
    
    # Discussion section
    if discussions:
        draw.text((30, y), "💬 热门讨论", fill=text_color, font=fonts['header'])
        y += 35
        
        for sender, text in discussions:
            draw_rounded_rect(draw, [(20, y), (width-20, y+80)], 10, bg_light)
            draw.rectangle([20, y, 24, y+80], fill=border_color)
            
            draw.text((35, y+10), sender, fill=primary_color, font=fonts['small'])
            
            wrapped = textwrap.fill(text, width=55)
            lines = wrapped.split('\n')
            for i, line in enumerate(lines[:3]):
                draw.text((35, y+30 + i*16), line, fill=text_color, font=fonts['small'])
            
            y += 90
    
    # Articles section
    if articles:
        y += 10
        draw.text((30, y), "📰 公众号文章精选", fill=text_color, font=fonts['header'])
        y += 35
        
        for title, summary, tag in articles:
            draw_rounded_rect(draw, [(20, y), (width-20, y+110)], 10, bg_light)
            draw.rectangle([20, y, 24, y+110], fill=secondary_color)
            
            wrapped_title = textwrap.fill(title, width=50)
            title_lines = wrapped_title.split('\n')
            for i, line in enumerate(title_lines[:2]):
                draw.text((35, y+8 + i*18), line, fill=text_color, font=fonts['small'])
            
            wrapped_summary = textwrap.fill(summary, width=55)
            summary_lines = wrapped_summary.split('\n')
            for i, line in enumerate(summary_lines[:2]):
                draw.text((35, y+45 + i*16), line, fill=light_text, font=fonts['small'])
            
            bbox = draw.textbbox((0, 0), tag, font=fonts['tag'])
            tag_width = bbox[2] - bbox[0] + 16
            draw_rounded_rect(draw, [(35, y+82), (35+tag_width, y+102)], 8, primary_color)
            draw.text((35+tag_width//2, y+92), tag, fill=white, font=fonts['tag'], anchor="mm")
            
            y += 120
    
    # Video section
    if videos:
        y += 10
        draw.text((30, y), "🎬 视频号内容", fill=text_color, font=fonts['header'])
        y += 35
        
        for title, desc in videos:
            draw_rounded_rect(draw, [(20, y), (width-20, y+60)], 10, bg_light)
            draw.rectangle([20, y, 24, y+60], fill=(200, 200, 200))
            
            draw.text((35, y+8), title, fill=text_color, font=fonts['small'])
            draw.text((35, y+30), desc, fill=light_text, font=fonts['small'])
            
            y += 70
    
    # Footer
    y += 20
    footer_bg = (248, 249, 250)
    draw.rectangle([0, y, width, y+50], fill=footer_bg)
    draw.text((width//2, y+25), "🤖 由 AI 自动生成 · 每日0点更新 · " + group_name, fill=(153, 153, 153), font=fonts['small'], anchor="mm")
    
    # Crop to actual content
    final_height = y + 60
    img = img.crop((0, 0, width, final_height))
    
    # Save
    img.save(output_path, 'PNG')
    print(f"Image saved to: {output_path}")
    print(f"Size: {width}x{final_height}")
    return output_path


if __name__ == "__main__":
    # Example usage
    generate_infographic(
        group_name="1-TGO和朋友们的AGI",
        date_str="2026-06-21",
        stats={"messages": 25, "articles": 5, "videos": 3, "participants": 8},
        highlight="DeepMind CEO Hassabis 在斯坦福给出AGI时间表:3-5年内到来,误差不超过一年。日本应届生就业率98%引发热议。",
        discussions=[
            ("yibooo", "Boris 给出AI编码四阶段路径:部署期→成本管控→回报放大→解锁下游瓶颈。"),
            ("ding-xue", "热议日本就业形势:应届生就业率接近98%,企业疯狂抢人。"),
        ],
        articles=[
            ("DeepMind CEO斯坦福演讲", "AGI 3-5年内到来", "AGI预测"),
            ("阶跃星辰冲港股", "终端布局已落", "AI公司上市"),
        ],
        videos=[
            ("Boris AI编码路径", "视频号: latentverse"),
        ],
        output_path="/data/data/com.termux/files/home/daily_digest_test.png"
    )