"""Генерация 10-слайдовой презентации сервиса «ИИ Пресс-секретарь» по ТЗ.""" from pathlib import Path from pptx import Presentation from pptx.util import Inches, Pt, Emu from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.shapes import MSO_SHAPE # === Палитра (тёмная тема из ТЗ) === BG = RGBColor(0x0F, 0x17, 0x2A) # #0f172a SURFACE = RGBColor(0x1E, 0x29, 0x3B) # #1e293b SURFACE_2 = RGBColor(0x27, 0x34, 0x49) # #273449 TEXT = RGBColor(0xE2, 0xE8, 0xF0) # #e2e8f0 MUTED = RGBColor(0x94, 0xA3, 0xB8) # #94a3b8 ACCENT = RGBColor(0x60, 0xA5, 0xFA) # #60a5fa ACCENT_DARK = RGBColor(0x25, 0x63, 0xEB) # #2563eb WHITE = RGBColor(0xFF, 0xFF, 0xFF) BORDER = RGBColor(0x33, 0x41, 0x55) # #334155 FONT = "Inter" FONT_FALLBACK = "Arial" SLIDE_W = Inches(13.333) SLIDE_H = Inches(7.5) def set_font(run, size, color=TEXT, bold=False, name=FONT): run.font.size = Pt(size) run.font.color.rgb = color run.font.bold = bold run.font.name = name def add_bg(slide, color=BG): """Заливает весь слайд цветом.""" slide.background.fill.solid() slide.background.fill.fore_color.rgb = color def add_rect(slide, x, y, w, h, fill=SURFACE, line=None, radius=False): """Добавляет прямоугольник (карточку).""" shape_type = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE shape = slide.shapes.add_shape(shape_type, x, y, w, h) if radius: try: shape.adjustments[0] = 0.06 except Exception: pass if fill is None: shape.fill.background() else: shape.fill.solid() shape.fill.fore_color.rgb = fill if line is None: shape.line.fill.background() else: shape.line.color.rgb = line shape.line.width = Pt(1) shape.shadow.inherit = False return shape def add_text(slide, x, y, w, h, text, size=18, color=TEXT, bold=False, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, name=FONT): """Добавляет текстовое поле.""" box = slide.shapes.add_textbox(x, y, w, h) tf = box.text_frame tf.word_wrap = True tf.vertical_anchor = anchor p = tf.paragraphs[0] p.alignment = align run = p.add_run() run.text = text set_font(run, size, color, bold, name) return box def add_paragraph(tf, text, size=18, color=TEXT, bold=False, align=PP_ALIGN.LEFT, space_after=8, bullet=False, name=FONT): """Добавляет абзац в существующий text_frame.""" p = tf.add_paragraph() p.alignment = align p.space_after = Pt(space_after) run = p.add_run() run.text = text set_font(run, size, color, bold, name) return p def add_bullets(slide, x, y, w, h, items, size=18, color=TEXT, gap=10): """Добавляет маркированный список.""" box = slide.shapes.add_textbox(x, y, w, h) tf = box.text_frame tf.word_wrap = True for i, item in enumerate(items): if i == 0: p = tf.paragraphs[0] else: p = tf.add_paragraph() p.space_after = Pt(gap) # Маркер run = p.add_run() run.text = "• " set_font(run, size, ACCENT, True) # Текст run2 = p.add_run() run2.text = item set_font(run2, size, color) return box def add_slide_number(slide, num, total=10): """Добавляет номер слайда в правый нижний угол.""" add_text(slide, SLIDE_W - Inches(1.2), SLIDE_H - Inches(0.6), Inches(0.9), Inches(0.4), f"{num} / {total}", size=12, color=MUTED, align=PP_ALIGN.RIGHT) def add_header(slide, title, subtitle=None): """Добавляет заголовок слайда.""" add_text(slide, Inches(0.8), Inches(0.5), Inches(11.7), Inches(0.9), title, size=34, color=TEXT, bold=True) if subtitle: add_text(slide, Inches(0.8), Inches(1.35), Inches(11.7), Inches(0.6), subtitle, size=16, color=MUTED) def add_footer_bar(slide): """Добавляет нижнюю полосу с брендом.""" add_rect(slide, Inches(0), SLIDE_H - Inches(0.35), SLIDE_W, Inches(0.35), fill=SURFACE_2) add_text(slide, Inches(0.8), SLIDE_H - Inches(0.32), Inches(6), Inches(0.3), "ИИ Пресс-секретарь · press.my-dpr.ru", size=11, color=MUTED) def add_icon_circle(slide, x, y, size, emoji, bg=ACCENT): """Добавляет круг с эмодзи.""" shape = slide.shapes.add_shape(MSO_SHAPE.OVAL, x, y, size, size) shape.fill.solid() shape.fill.fore_color.rgb = bg shape.line.fill.background() shape.shadow.inherit = False tf = shape.text_frame tf.word_wrap = False tf.vertical_anchor = MSO_ANCHOR.MIDDLE p = tf.paragraphs[0] p.alignment = PP_ALIGN.CENTER run = p.add_run() run.text = emoji run.font.size = Pt(int(int(size) / 12700 * 0.5)) return shape def add_image(slide, img_path, x, y, w=None, h=None): """Добавляет изображение на слайд с сохранением пропорций.""" from PIL import Image img = Image.open(img_path) iw, ih = img.size if w is None and h is None: w = Inches(4) if w is not None and h is None: w_emu = int(w) h = Emu(int(w_emu * ih / iw)) elif h is not None and w is None: h_emu = int(h) w = Emu(int(h_emu * iw / ih)) slide.shapes.add_picture(str(img_path), x, y, width=w, height=h) def build(): prs = Presentation() prs.slide_width = SLIDE_W prs.slide_height = SLIDE_H blank = prs.slide_layouts[6] # пустой макет # Пути к сгенерированным изображениям img_dir = Path(__file__).resolve().parent / "presentation_images" img_title = img_dir / "title_robot.png" img_solution = img_dir / "solution.png" img_media = img_dir / "media_brand.png" # ============ СЛАЙД 1: Титульный ============ s = prs.slides.add_slide(blank) add_bg(s) # Изображение робота (сгенерировано через PiAPI) if img_title.exists(): add_image(s, img_title, Inches(4.67), Inches(0.7), w=Inches(4.0)) else: add_icon_circle(s, Inches(5.92), Inches(1.2), Inches(1.5), "🤖", ACCENT) # Название add_text(s, Inches(1.5), Inches(4.6), Inches(10.3), Inches(1.2), "ИИ Пресс-секретарь", size=54, color=TEXT, bold=True, align=PP_ALIGN.CENTER) # Слоган add_text(s, Inches(1.5), Inches(5.7), Inches(10.3), Inches(0.8), "Умный контент для вашего бизнеса", size=26, color=ACCENT, align=PP_ALIGN.CENTER) # Описание add_text(s, Inches(2.0), Inches(6.4), Inches(9.3), Inches(0.8), "Сервис, который создаёт контент-план, генерирует посты и картинки " "для вашего бизнеса и публикует их в Telegram — всё в одном месте.", size=15, color=MUTED, align=PP_ALIGN.CENTER) add_text(s, Inches(0.8), SLIDE_H - Inches(0.5), Inches(6), Inches(0.3), "press.my-dpr.ru", size=12, color=MUTED) # ============ СЛАЙД 2: Проблема ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "Ведение соцсетей отнимает слишком много времени") add_footer_bar(s) add_slide_number(s, 2) problems = [ "Нужно регулярно публиковать контент, но нет времени и идей", "Сложно придумывать посты каждый день", "Дорого нанимать копирайтера и дизайнера", "Трудно поддерживать единый стиль и медиа-образ", "Публикация в Telegram вручную — рутина", ] add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0), problems, size=22, gap=18) # ============ СЛАЙД 3: Решение ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "ИИ Пресс-секретарь решает эту задачу") add_footer_bar(s) add_slide_number(s, 3) solutions = [ "ИИ берёт на себя генерацию контента: посты, картинки, контент-план", "Всё в одном личном кабинете — без копирайтера и дизайнера", "Контент подстраивается под ваш медиа-образ и стиль", "Публикация в Telegram в один клик", "Экономия времени и денег", ] add_bullets(s, Inches(0.8), Inches(2.2), Inches(6.5), Inches(4.0), solutions, size=20, gap=16) if img_solution.exists(): add_image(s, img_solution, Inches(7.6), Inches(2.0), w=Inches(5.0)) # ============ СЛАЙД 4: Ключевые возможности ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "Всё, что нужно для контента — в одном сервисе") add_footer_bar(s) add_slide_number(s, 4) features = [ ("✍️", "Генерация постов", "ИИ пишет готовые тексты для вашего бизнеса с учётом вашего медиа-образа и стиля."), ("🎨", "Генерация картинок", "Создавайте иллюстрации к постам автоматически — без дизайнера и фотостока."), ("📅", "Контент-план", "Планируйте публикации на неделю вперёд и следите за выполнением плана."), ("📣", "Публикация в Telegram", "Публикуйте посты в свои каналы прямо из личного кабинета."), ] card_w = Inches(2.85) card_h = Inches(3.6) gap_x = Inches(0.25) start_x = Inches(0.8) y = Inches(2.2) for i, (emoji, title, desc) in enumerate(features): x = start_x + i * (card_w + gap_x) card = add_rect(s, x, y, card_w, card_h, fill=SURFACE, line=BORDER, radius=True) # Иконка add_icon_circle(s, x + Inches(0.9), y + Inches(0.4), Inches(1.0), emoji, ACCENT) # Заголовок add_text(s, x + Inches(0.3), y + Inches(1.6), card_w - Inches(0.6), Inches(0.7), title, size=18, color=TEXT, bold=True, align=PP_ALIGN.CENTER) # Описание add_text(s, x + Inches(0.3), y + Inches(2.3), card_w - Inches(0.6), Inches(1.2), desc, size=13, color=MUTED, align=PP_ALIGN.CENTER) # ============ СЛАЙД 5: Медиа-образ ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "Уникальный медиа-образ вашего бренда") add_footer_bar(s) add_slide_number(s, 5) media_items = [ "Заполните опрос о своём бизнесе — ИИ изучит ваш стиль, миссию, ценности и целевую аудиторию", "Сервис формирует медиа-образ (бренд-бук) вашей организации", "Все посты генерируются в едином стиле, соответствующем вашему бренду", "Поддержка 9 видов бизнеса и различных форматов", "Импорт данных из существующих каналов: Telegram, VK, Tenchat, Instagram", ] add_bullets(s, Inches(0.8), Inches(2.2), Inches(6.5), Inches(4.0), media_items, size=18, gap=14) if img_media.exists(): add_image(s, img_media, Inches(7.6), Inches(2.0), w=Inches(5.0)) # ============ СЛАЙД 6: Как это работает ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "Просто начните — 4 шага") add_footer_bar(s) add_slide_number(s, 6) steps = [ ("1", "Зарегистрируйтесь", "Создайте аккаунт в личном кабинете за пару минут."), ("2", "Опишите медиа-образ", "Расскажите о своём бизнесе — ИИ подстроится под ваш стиль."), ("3", "Генерируйте контент", "Создавайте посты, картинки и контент-план автоматически."), ("4", "Публикуйте", "Отправляйте готовые посты в свои Telegram-каналы."), ] step_w = Inches(2.85) step_h = Inches(3.6) gap_x = Inches(0.25) start_x = Inches(0.8) y = Inches(2.2) for i, (num, title, desc) in enumerate(steps): x = start_x + i * (step_w + gap_x) card = add_rect(s, x, y, step_w, step_h, fill=SURFACE, line=BORDER, radius=True) # Номер circle = add_rect(s, x + Inches(1.1), y + Inches(0.4), Inches(0.7), Inches(0.7), fill=ACCENT, radius=True) tf = circle.text_frame tf.vertical_anchor = MSO_ANCHOR.MIDDLE p = tf.paragraphs[0] p.alignment = PP_ALIGN.CENTER run = p.add_run() run.text = num set_font(run, 22, WHITE, True) # Заголовок add_text(s, x + Inches(0.3), y + Inches(1.4), step_w - Inches(0.6), Inches(0.7), title, size=18, color=TEXT, bold=True, align=PP_ALIGN.CENTER) # Описание add_text(s, x + Inches(0.3), y + Inches(2.1), step_w - Inches(0.6), Inches(1.3), desc, size=13, color=MUTED, align=PP_ALIGN.CENTER) # ============ СЛАЙД 7: Дополнительные возможности ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "Больше, чем генерация постов") add_footer_bar(s) add_slide_number(s, 7) extra = [ "🤖 Чат с ИИ для редактирования и доработки постов", "📊 Статистика и аналитика публикаций", "🔗 Подключение нескольких Telegram-каналов", "🖼️ Медиа-библиотека и история постов", "📱 Адаптивный дизайн — работайте с телефона, планшета или компьютера", "📄 Экспорт бренд-бука в документ (DOCX)", ] add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0), extra, size=20, gap=14) # ============ СЛАЙД 8: Для кого ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "Кому подойдёт сервис") add_footer_bar(s) add_slide_number(s, 8) audience = [ "Предпринимателям и владельцам бизнеса", "Руководителям организаций и сообществ", "Маркетологам и SMM-специалистам", "Блогерам и экспертам, ведущим Telegram-каналы", "Тем, кто хочет экономить время на контенте", ] add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0), audience, size=22, gap=18) # ============ СЛАЙД 9: Преимущества ============ s = prs.slides.add_slide(blank) add_bg(s) add_header(s, "Почему выбирают ИИ Пресс-секретарь") add_footer_bar(s) add_slide_number(s, 9) advantages = [ "⏱️ Экономия времени — контент создаётся за минуты", "💰 Экономия бюджета — не нужны копирайтер и дизайнер", "🎯 Единый стиль — контент соответствует вашему бренду", "📈 Регулярность — контент-план помогает публиковать системно", "🔒 Безопасность — данные и аккаунт защищены", ] add_bullets(s, Inches(1.2), Inches(2.2), Inches(10.9), Inches(4.0), advantages, size=22, gap=18) # ============ СЛАЙД 10: Призыв к действию ============ s = prs.slides.add_slide(blank) add_bg(s) add_icon_circle(s, Inches(6.17), Inches(1.2), Inches(1.0), "🚀", ACCENT) add_text(s, Inches(1.5), Inches(2.6), Inches(10.3), Inches(1.0), "Готовы начать?", size=44, color=TEXT, bold=True, align=PP_ALIGN.CENTER) add_text(s, Inches(1.5), Inches(3.6), Inches(10.3), Inches(0.7), "Зарегистрируйтесь и получите доступ к личному кабинету.", size=18, color=MUTED, align=PP_ALIGN.CENTER) # Кнопка регистрации btn1 = add_rect(s, Inches(4.17), Inches(4.6), Inches(2.5), Inches(0.65), fill=ACCENT, radius=True) tf = btn1.text_frame tf.vertical_anchor = MSO_ANCHOR.MIDDLE p = tf.paragraphs[0] p.alignment = PP_ALIGN.CENTER run = p.add_run() run.text = "Зарегистрироваться" set_font(run, 16, WHITE, True) # Кнопка входа btn2 = add_rect(s, Inches(6.87), Inches(4.6), Inches(2.3), Inches(0.65), fill=SURFACE, line=ACCENT, radius=True) tf = btn2.text_frame tf.vertical_anchor = MSO_ANCHOR.MIDDLE p = tf.paragraphs[0] p.alignment = PP_ALIGN.CENTER run = p.add_run() run.text = "Войти в кабинет" set_font(run, 16, ACCENT, True) # Ссылка на сайт add_text(s, Inches(1.5), Inches(5.6), Inches(10.3), Inches(0.5), "press.my-dpr.ru", size=16, color=ACCENT, align=PP_ALIGN.CENTER) # Сохранение out_path = Path(__file__).resolve().parent.parent / "ИИ_Пресс-секретарь_презентация.pptx" prs.save(out_path) print(f"✅ Презентация сохранена: {out_path}") if __name__ == "__main__": build()