#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 天恩 Word(笛卡尔背单词)项目 · 小学生演讲 PPT 生成 汇报人:二年级4班 施吴佶 | 12页 | 小学生口吻 | 含流程图、插图、讲解 风格:仿天恩乖乖,暖色、圆角、图标、童趣;每页有图或流程图。 """ from pathlib import Path import sys # 输出目录:优先天恩项目下,便于用户使用 TIANEN_ROOT = Path("/Users/karuo/Library/Mobile Documents/com~apple~CloudDocs/Documents/婼瑄/天恩") OUT_DIR = TIANEN_ROOT / "天恩Word演讲PPT" KARUO_IMG = Path("/Users/karuo/Documents/卡若Ai的文件夹/图片") # 确保可导入 pptx try: from pptx import Presentation from pptx.util import Inches, Pt from pptx.dml.color import RGBColor from pptx.enum.shapes import MSO_SHAPE from pptx.enum.text import PP_ALIGN except ImportError: print("请先安装: pip install python-pptx") sys.exit(1) # ---------- 配色(小学绘本/童趣风 + 苹果毛玻璃感)---------- BG_CREAM = RGBColor(255, 250, 230) # 暖黄 BG_PINK = RGBColor(255, 245, 248) # 浅粉 BG_GLASS = RGBColor(248, 248, 252) # 毛玻璃浅白 TITLE_BROWN = RGBColor(139, 90, 43) # 标题棕 TEXT_DARK = RGBColor(60, 45, 30) # 正文深棕 ACCENT = RGBColor(218, 165, 32) # 金黄强调 BORDER = RGBColor(180, 140, 100) # 边框暖棕 GLASS_BORDER = RGBColor(220, 220, 230) # 毛玻璃边框 LIGHT_PINK = RGBColor(255, 228, 225) # 封面渐变用 def set_slide_background(slide, color=BG_CREAM): try: background = slide.background fill = background.fill fill.solid() fill.fore_color.rgb = color except Exception: pass def add_title(slide, text, top=Inches(0.4), left=Inches(0.6), width=Inches(12), font_size=28, center=False): """添加标题;center=True 时文字居中""" tb = slide.shapes.add_textbox(left, top, width, Inches(0.8)) tf = tb.text_frame p = tf.paragraphs[0] p.text = text p.font.size = Pt(font_size) p.font.bold = True p.font.color.rgb = TITLE_BROWN if center: p.alignment = PP_ALIGN.CENTER return tb def add_body(slide, lines, top, left=Inches(0.6), width=Inches(7), font_size=20, center=False): tb = slide.shapes.add_textbox(left, top, width, Inches(3.5)) tf = tb.text_frame tf.word_wrap = True for i, line in enumerate(lines): if i == 0: p = tf.paragraphs[0] else: p = tf.add_paragraph() p.text = line p.font.size = Pt(font_size) p.font.color.rgb = TEXT_DARK p.space_after = Pt(8) if center: p.alignment = PP_ALIGN.CENTER return tb def add_flowchart(slide, top=Inches(2), left=Inches(0.8), glass_style=True): """绘制流程图(毛玻璃风格):选后缀 → 加前缀 → 生成单词 → 听句子看图 → 标记已背""" box_w, box_h = Inches(1.85), Inches(0.65) steps = ["1. 选后缀", "2. 加前缀", "3. 生成单词", "4. 听句看图", "5. 标记已背"] fill_rgb = BG_GLASS if glass_style else RGBColor(255, 255, 255) line_rgb = GLASS_BORDER if glass_style else BORDER for i, label in enumerate(steps): x = left + Inches(i * 2.15) shape = slide.shapes.add_shape( MSO_SHAPE.ROUNDED_RECTANGLE, x, top, box_w, box_h ) shape.fill.solid() shape.fill.fore_color.rgb = fill_rgb shape.line.color.rgb = line_rgb shape.line.width = Pt(1.5) tf = shape.text_frame tf.paragraphs[0].text = label tf.paragraphs[0].font.size = Pt(13) tf.paragraphs[0].font.color.rgb = TEXT_DARK tf.paragraphs[0].alignment = PP_ALIGN.CENTER if i < len(steps) - 1: arrow_x = x + box_w + Inches(0.04) arrow = slide.shapes.add_shape( MSO_SHAPE.RIGHT_ARROW, arrow_x, top + Inches(0.18), Inches(0.3), Inches(0.28) ) arrow.fill.solid() arrow.fill.fore_color.rgb = ACCENT arrow.line.color.rgb = ACCENT SLIDE_W = Inches(13.333) def add_picture_glass(slide, path, left, top, width, border_rgb=GLASS_BORDER, center=False): """插入图片并加毛玻璃感边框。center=True 时水平居中。""" path = Path(path) if not path.exists(): return None try: if center: left = (SLIDE_W - width) / 2 pic = slide.shapes.add_picture(str(path), left, top, width=width) pic.line.color.rgb = border_rgb pic.line.width = Pt(2) return pic except Exception: return None def add_cartesian_diagram(slide, top=Inches(2.2), left=Inches(0.8), glass_style=True): """笛卡尔坐标简易示意图(毛玻璃风格):X轴后缀、Y轴前缀、交叉得单词""" fill_rgb = BG_GLASS if glass_style else RGBColor(255, 255, 255) line_rgb = GLASS_BORDER if glass_style else BORDER box = slide.shapes.add_shape( MSO_SHAPE.ROUNDED_RECTANGLE, left, top, Inches(8), Inches(2.8) ) box.fill.solid() box.fill.fore_color.rgb = fill_rgb box.line.color.rgb = line_rgb box.line.width = Pt(1.5) # X轴标签 tb_x = slide.shapes.add_textbox(left + Inches(0.3), top + Inches(0.2), Inches(2), Inches(0.4)) tb_x.text_frame.paragraphs[0].text = "X轴:后缀 (DAY, BOOK...)" tb_x.text_frame.paragraphs[0].font.size = Pt(16) tb_x.text_frame.paragraphs[0].font.bold = True tb_x.text_frame.paragraphs[0].font.color.rgb = TITLE_BROWN # Y轴标签 tb_y = slide.shapes.add_textbox(left + Inches(0.3), top + Inches(1), Inches(2), Inches(0.4)) tb_y.text_frame.paragraphs[0].text = "Y轴:前缀 (SUN, NOTE...)" tb_y.text_frame.paragraphs[0].font.size = Pt(16) tb_y.text_frame.paragraphs[0].font.bold = True tb_y.text_frame.paragraphs[0].font.color.rgb = TITLE_BROWN # 示例单词 tb_w = slide.shapes.add_textbox(left + Inches(0.3), top + Inches(1.8), Inches(7), Inches(0.8)) tb_w.text_frame.word_wrap = True p = tb_w.text_frame.paragraphs[0] p.text = "组合起来 → SUNDAY, MONDAY, NOTEBOOK, BIRTHDAY ... 一次记住好多词!" p.font.size = Pt(18) p.font.color.rgb = TEXT_DARK def add_icon_text(slide, icon, text, left, top, font_size=22): tb = slide.shapes.add_textbox(left, top, Inches(10), Inches(0.5)) p = tb.text_frame.paragraphs[0] p.text = f"{icon} {text}" p.font.size = Pt(font_size) p.font.color.rgb = TEXT_DARK return tb def create_placeholder_photo(path, name="施吴佶", size=400): """生成一张占位图:渐变底 + 圆形 + 姓名,用于演讲人照片位""" try: from PIL import Image, ImageDraw except ImportError: return None path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) img = Image.new("RGB", (size, size), (255, 240, 230)) draw = ImageDraw.Draw(img) # 渐变 for y in range(size): t = y / size r = int(255) g = int(240 + (220 - 240) * t) b = int(230 + (200 - 230) * t) draw.line([(0, y), (size, y)], fill=(r, g, b)) # 圆 margin = 60 draw.ellipse([margin, margin, size - margin, size - margin], outline=(180, 140, 100), width=4) # 姓名 try: from PIL import ImageFont font = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 36) except Exception: font = ImageFont.load_default() bbox = draw.textbbox((0, 0), name, font=font) tw = bbox[2] - bbox[0] draw.text(((size - tw) // 2, size // 2 - 25), name, font=font, fill=(139, 90, 43)) img.save(path, "PNG") return path def build_presentation(photo_path=None, extra_images=None): prs = Presentation() prs.slide_width = Inches(13.333) prs.slide_height = Inches(7.5) blank = prs.slide_layouts[6] # 准备演讲人照片与插图资源 OUT_DIR.mkdir(parents=True, exist_ok=True) assets_dir = OUT_DIR / "assets" speaker1 = assets_dir / "speaker1.png" # 小女孩剪刀手 speaker2 = assets_dir / "speaker2.png" # 户外照 coordinate_img = assets_dir / "coordinate.png" flow_words_img = assets_dir / "flow_words.png" scale_img = assets_dir / "scale.png" sunflower_book_img = assets_dir / "sunflower_book.png" day_table_img = assets_dir / "day_table.png" speaker_img = photo_path if not speaker_img or not Path(speaker_img).exists(): if speaker1.exists(): speaker_img = str(speaker1) else: placeholder_photo = OUT_DIR / "placeholder_speaker.png" created = create_placeholder_photo(placeholder_photo) speaker_img = str(placeholder_photo) if (created and placeholder_photo.exists()) else None else: speaker_img = str(speaker_img) speaker2_path = str(speaker2) if speaker2.exists() else speaker_img def add_slide(): return prs.slides.add_slide(blank) # ---------- 第1页:封面(标题居中、图片居中放大、汇报人一行)---------- s1 = add_slide() set_slide_background(s1, BG_CREAM) add_title(s1, "天恩 Word · 智能单词记忆小助手", top=Inches(0.7), left=Inches(0.5), width=Inches(12.3), font_size=40, center=True) add_body(s1, ["向全班、全校、全国的小朋友介绍我们的项目"], top=Inches(1.4), left=Inches(1.5), width=Inches(10.3), font_size=22, center=True) if speaker_img and Path(speaker_img).exists(): add_picture_glass(s1, speaker_img, 0, Inches(2.0), Inches(3.2), BORDER, center=True) add_title(s1, "汇报人:二年四班 4号 施吴佶", top=Inches(5.6), left=Inches(0.5), width=Inches(12.3), font_size=24, center=True) # ---------- 第2页:大家好,我是施吴佶(双图可用 speaker2)---------- s2 = add_slide() set_slide_background(s2, BG_CREAM) add_title(s2, "大家好,我是施吴佶!", font_size=32, center=True) add_icon_text(s2, "👋", "我是二年级4班的一名小学生。", Inches(0.6), Inches(1.4)) add_icon_text(s2, "📚", "我和小伙伴们一起做了一个「记单词」的小项目,", Inches(0.6), Inches(2.0)) add_icon_text(s2, "✨", "今天想跟大家说一说它是怎么用的!", Inches(0.6), Inches(2.6)) if speaker2_path and Path(speaker2_path).exists(): add_picture_glass(s2, speaker2_path, Inches(7.8), Inches(1.6), Inches(2.6), BORDER) # ---------- 第3页:今天讲什么 ---------- s3 = add_slide() set_slide_background(s3, BG_CREAM) add_title(s3, "今天我要讲什么?", font_size=30, center=True) add_body(s3, [ "我们要讲的是一个「智能单词记忆小助手」!", "它有一个很厉害的方法,叫「笛卡尔坐标记忆法」。", "用这个方法,可以一次记住好多相关的单词,又好玩又不容易忘。", "后面我会告诉大家怎么用、我们班有什么收获。" ], top=Inches(1.5), font_size=20) # ---------- 第4页:为什么做这个(可配情绪/改进示意图)---------- s4 = add_slide() set_slide_background(s4, BG_PINK) add_title(s4, "我们为什么做这个?", font_size=30, center=True) add_icon_text(s4, "😣", "记单词太难了!", Inches(0.6), Inches(1.3), 24) add_body(s4, [ "以前记单词总是死记硬背,很容易忘。", "而且一个一个记,又慢又没意思。", "我们就想:能不能把有关的词放在一起记?", "所以做了这个小助手,让记单词变得更有趣、更快。" ], top=Inches(1.9), left=Inches(0.6), width=Inches(6.2), font_size=20) if scale_img.exists(): add_picture_glass(s4, scale_img, Inches(7.5), Inches(1.8), Inches(4.8), BORDER) # ---------- 第5页:我们的办法 — 笛卡尔坐标 + 坐标系插图 ---------- s5 = add_slide() set_slide_background(s5, BG_CREAM) add_title(s5, "我们的办法:笛卡尔坐标记忆法", font_size=28, center=True) add_body(s5, [ "把单词拆成「前缀」和「后缀」:", "X 轴放后缀(比如 DAY、BOOK),Y 轴放前缀(比如 SUN、NOTE)。", "它们一组合,就变成 SUNDAY、NOTEBOOK 这些词,一次能记一串!" ], top=Inches(1.0), left=Inches(0.6), width=Inches(5.8), font_size=19) if coordinate_img.exists(): add_picture_glass(s5, coordinate_img, Inches(6.8), Inches(2.0), Inches(5.5), GLASS_BORDER) else: add_cartesian_diagram(s5, top=Inches(2.9), left=Inches(0.6)) # ---------- 第6页:什么是笛卡尔坐标 + DAY 单词表示例图 ---------- s6 = add_slide() set_slide_background(s6, BG_CREAM) add_title(s6, "什么是笛卡尔坐标?", font_size=30, center=True) add_body(s6, [ "就像画一个「十字」,横着的是后缀,竖着的是前缀。", "它们交叉的地方,就是一个新单词!", "这样记,单词和单词之间就有联系,不会乱。" ], top=Inches(1.2), left=Inches(0.6), width=Inches(6), font_size=20) if day_table_img.exists(): add_picture_glass(s6, day_table_img, Inches(7.0), Inches(1.8), Inches(5.5), GLASS_BORDER) else: add_cartesian_diagram(s6, top=Inches(3.2), left=Inches(1.5)) # ---------- 第7页:怎么用 — 流程图(毛玻璃)+ 步骤示意图 ---------- s7 = add_slide() set_slide_background(s7, BG_PINK) add_title(s7, "怎么用?五步就会!", font_size=30, center=True) add_flowchart(s7, top=Inches(1.85), left=Inches(0.5), glass_style=True) if flow_words_img.exists(): add_picture_glass(s7, flow_words_img, Inches(7.2), Inches(2.0), Inches(5.3), GLASS_BORDER) add_body(s7, [ "选一个后缀 → 加上几个前缀 → 点「生成」→ 听句子、看配图 → 会了就标记「已背」!" ], top=Inches(3.5), left=Inches(0.6), width=Inches(12), font_size=18) # ---------- 第8页:记忆句和配图 + 联想示意图 ---------- s8 = add_slide() set_slide_background(s8, BG_CREAM) add_title(s8, "记忆句和配图", font_size=30, center=True) add_icon_text(s8, "📝", "一句子里藏着这一组的所有单词,读一句就复习一遍。", Inches(0.6), Inches(1.3)) add_icon_text(s8, "🖼️", "还有 AI 画的配图,看图就能想起这句话和单词。", Inches(0.6), Inches(1.9)) add_body(s8, [ "这样记单词又轻松又牢,我们班同学都很喜欢!" ], top=Inches(2.5), left=Inches(0.6), width=Inches(5.8), font_size=20) if sunflower_book_img.exists(): add_picture_glass(s8, sunflower_book_img, Inches(6.6), Inches(1.5), Inches(6), GLASS_BORDER) # ---------- 第9页:界面长什么样 + 单词表示例 ---------- s9 = add_slide() set_slide_background(s9, BG_CREAM) add_title(s9, "我们做的界面长什么样?", font_size=28, center=True) add_body(s9, [ "打开网页就能用,不用装软件。", "上面是「坐标系」:左边是前缀,上边是后缀,中间是单词和意思。", "下面有一句「记忆句」和一张图,点一下还能朗读。", "会了的词点一下就能标成「已背」,特别方便!" ], top=Inches(1.2), left=Inches(0.6), width=Inches(6), font_size=19) if day_table_img.exists(): add_picture_glass(s9, day_table_img, Inches(6.8), Inches(1.8), Inches(5.5), GLASS_BORDER) # ---------- 第10页:我们班的收获 ---------- s10 = add_slide() set_slide_background(s10, BG_PINK) add_title(s10, "我们班的收获", font_size=30, center=True) add_icon_text(s10, "🌟", "记单词变快了,而且不容易忘。", Inches(0.6), Inches(1.4)) add_icon_text(s10, "😊", "大家觉得好玩,都愿意多背几组。", Inches(0.6), Inches(2.0)) add_icon_text(s10, "🤝", "我们一起想点子、一起用,更像一个小组了。", Inches(0.6), Inches(2.6)) add_body(s10, ["希望更多班级、更多小朋友也能用上,一起轻松记单词!"], top=Inches(3.2), font_size=20) # ---------- 第11页:谢谢大家 ---------- s11 = add_slide() set_slide_background(s11, BG_CREAM) add_title(s11, "谢谢大家!", top=Inches(2.2), font_size=44, center=True) add_body(s11, ["欢迎大家一起用「天恩 Word」记单词~"], top=Inches(3.0), font_size=24) add_body(s11, ["汇报人:二年四班 4号 施吴佶"], top=Inches(4.0), font_size=20, center=True) # ---------- 第12页:感谢 ---------- s12 = add_slide() set_slide_background(s12, LIGHT_PINK) add_title(s12, "感谢聆听", top=Inches(2.8), font_size=38, center=True) add_body(s12, ["感谢全班、全校、全国的老师和小伙伴们!"], top=Inches(3.6), font_size=22) out_ppt = OUT_DIR / "天恩Word项目介绍_二年级4班施吴佶_12页.pptx" prs.save(out_ppt) print("已生成:", out_ppt) return out_ppt def main(): import argparse ap = argparse.ArgumentParser(description="天恩 Word 小学生演讲 PPT(12页)") ap.add_argument("--photo", default="", help="施吴佶照片路径,无则用占位图") ap.add_argument("--output-dir", default="", help="输出目录,默认天恩项目下") args = ap.parse_args() if args.output_dir: global OUT_DIR OUT_DIR = Path(args.output_dir) build_presentation(photo_path=args.photo or None) if __name__ == "__main__": main()