Skip to content

About

Генератор PowerPoint-презентаций из Excel для автоматизации редакционного дайджеста книжных новинок.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

Repository files navigation

deck-builder

Note

Небольшой личный проект, который я самостоятельно сделал, чтобы автоматизировать генерацию презентаций для редакции, в которой работаю. Не является официальным проектом моего работодателя.

Генератор PowerPoint-презентаций с дайджестом книжных новинок из Excel.

Создает титульный слайд и отдельный слайд для каждой книги с обложкой, авторами, названием, аннотацией, причинами купить, тегами и характеристиками издания.

Требования

  • Python 3.14 или новее.
  • uv для установки зависимостей и запуска проекта.
  • Интернет для загрузки обложек с CDN АСТ.
  • Шрифт IBM Plex Sans на компьютере, где открывается презентация, если требуется отображение в соответствии с настройками шаблона. Шрифт не встраивается в PPTX.

Установка

Для установки зависимостей приложения выполните в корне проекта:

uv sync --locked

Для разработки вместе с dev-зависимостями:

uv sync --locked --group dev

Основные зависимости проекта:

  • polars и fastexcel — чтение Excel;
  • Pillow — работа с изображениями;
  • python-pptx — создание PowerPoint-презентаций;
  • Typer — командный интерфейс.

Версии для воспроизводимой установки зафиксированы в uv.lock.

Командная строка

После установки проекта доступна команда deck-builder.

Минимальный вызов:

uv run deck-builder data/input.xlsx output/output.pptx

Команда принимает два обязательных аргумента:

INPUT_FILE
OUTPUT_FILE

Входной файл должен существовать, быть обычным файлом и быть доступен для чтения.

Входной и выходной пути не могут указывать на один и тот же файл.

Пример

В каталоге examples/ находятся пример исходной таблицы и сгенерированной из нее презентации:

Презентацию можно воспроизвести командой:

uv run deck-builder \
  examples/input.xlsx \
  examples/output.pptx \
  --title "Книжные новинки" \
  --suptitle "Пример презентации" \
  --attribution "deck-builder"

Данные в примере являются демонстрационными и не содержат рабочую издательскую информацию.

Параметры титульного слайда

По умолчанию используются:

Заголовок:    ДАЙДЖЕСТ НОВИНОК
Подзаголовок: РЕДАКЦИИ «ЛИНГВА»
Подпись:      МЕСЯЦ ГОД

Их можно переопределить:

uv run deck-builder data/books.xlsx output/digest.pptx \
  --title "Книжные новинки" \
  --suptitle "Сентябрь 2026" \
  --attribution "Редакция «Лингва»"

Справка:

uv run deck-builder --help

Пути с пробелами заключайте в кавычки:

uv run deck-builder \
  "data/books september.xlsx" \
  "output/september digest.pptx"

После успешной сборки CLI выводит путь к созданному файлу:

Сохранено: output/digest.pptx

Коды завершения

Команда завершается:

  • с кодом 0 при успешной генерации;
  • с кодом 1, если Excel не соответствует схеме либо произошла ошибка чтения, сборки, загрузки изображения или сохранения;
  • с кодом 2 при ошибке аргументов CLI, например если входной файл отсутствует или входной и выходной файлы совпадают.

Ошибки приложения перехватываются на границе CLI и выводятся без traceback.

Формат Excel

Одна строка соответствует одной книге.

Названия обязательных столбцов должны совпадать со схемой, включая пробелы и знаки препинания.

Порядок столбцов не важен. Дополнительные столбцы допускаются и при сборке игнорируются.

TitlesDigest.from_excel() использует polars.read_excel() с настройками по умолчанию. Выбор конкретного листа через публичный API TitlesDigest сейчас не предусмотрен.

Обязательный столбец Тип после загрузки Пример
Код ном-ры Строка 00123
ISBN Строка 9781234567890
Наименование на обложку Строка Книга о языке
Авторы на обложку Строка Иван Иванов
Аннотация Строка Описание книги
Пять причин купить Строка Причины и теги, см. ниже
Серия Строка Изучаем языки
Формат Строка 60x90/16
Продукция.Тип переплета Строка 3
Кол-во стр Int16 128
Красочность блока текста Строка 1+1
Тираж Int64 1000
Продукция.Возрастное ограничение Строка 12+

Код номенклатуры и ISBN

Код ном-ры и ISBN приводятся к строковому типу, но если Excel изначально хранит значение как число, уже потерянные ведущие нули восстановить невозможно.

Поэтому значения вроде:

00123

следует хранить в Excel именно как текст.

Тип переплета

Значение:

3

при выводе преобразуется в:

мягкая обложка

Остальные значения выводятся без преобразования.

Причины и теги

Поле Пять причин купить содержит одновременно текст причин и теги.

Каждый элемент размещается на отдельной строке:

1. Понятные объяснения
2. Практические задания
3. Ответы для самопроверки
4. Удобный формат
5. Подходит для самостоятельного изучения
#английский_язык
#самоучитель

Строки, которые после удаления начальных пробелов начинаются с #, считаются тегами.

При обработке:

#английский_язык

превращается в:

английский язык

То есть:

  • символ # удаляется;
  • пробелы по краям удаляются;
  • _ заменяются пробелами;
  • строки с тегами удаляются из текста причин.

Минимальное количество тегов

Для непустого значения Пять причин купить генератор обеспечивает наличие как минимум двух тегов.

Если тегов меньше двух, список дополняется:

<не заполнен тег>

Например, при одном теге:

#самоучитель

будут сформированы:

самоучитель
<не заполнен тег>

Если поле Пять причин купить пустое, причины и список тегов остаются пустыми.

Количество самих причин не проверяется. Заголовок блока на слайде всегда:

5 причин купить

Проверка Excel

При загрузке выполняются две проверки:

  1. наличие всех обязательных столбцов;
  2. возможность строго привести каждый обязательный столбец к типу из схемы.

При отсутствии столбцов в stdout выводится диагностика вида:

Отсутствуют обязательные столбцы: [...]

Если значения нельзя привести к нужным типам:

Эти столбцы нельзя привести к нужному типу: ...

В обоих случаях load_from_excel() возвращает None, поэтому:

digest = TitlesDigest.from_excel(...)

создает объект с:

digest.df is None

При использовании Python API это состояние следует проверять до вызова build():

if digest.df is None:
    raise ValueError("Некорректная таблица")

Иначе build() все еще сможет создать титульный слайд без книжных слайдов.

Пустая таблица с корректной схемой также создает только титульный слайд.

Ошибки открытия или чтения Excel не преобразуются в None, а передаются вызывающему коду как исключения.

Валидация проверяет структуру и типы данных, но не их предметный смысл и не обязательность заполнения каждой ячейки.

Например, отсутствие значения там, где дальнейший код ожидает число, может привести к ошибке уже на этапе сборки презентации.

Обложки

Обложка загружается по значению Код ном-ры с фиксированного CDN АСТ:

https://cdn.ast.ru/v2/{item_code}/COVER/cover1.jpg

Например:

https://cdn.ast.ru/v2/00123/COVER/cover1.jpg

По умолчанию сетевой тайм-аут составляет 10 секунд.

Результаты скачивания кешируются в памяти процесса через LRU-кеш максимум на 256 значений. Кешируется в том числе отсутствие изображения.

При обработанной сетевой ошибке вместо обложки создается заглушка:

НЕТ ИЗОБРАЖЕНИЯ

К обрабатываемым ошибкам относятся сетевые ошибки, timeout и некорректный URL.

Если CDN вернул данные, которые не являются корректным изображением, Pillow может выбросить исключение при определении его размеров. Такое исключение не заменяется заглушкой автоматически и передается вызывающему коду.

Размер обложки

Изображение вписывается в заданную область с сохранением исходных пропорций.

Растягивания изображения до полного размера контейнера не происходит.

Построение презентации

TitlesDigest создается методом:

TitlesDigest.from_excel("data/books.xlsx")

Объект содержит:

digest.config
digest.deck
digest.df

Метод:

digest.build(...)

изменяет внутренний объект python-pptx Presentation и возвращает тот же TitlesDigest, поэтому можно использовать цепочку вызовов:

TitlesDigest.from_excel("data/books.xlsx").build(
    title_text="Книжные новинки",
    suptitle_text="Сентябрь 2026",
    attribution_text="Редакция «Лингва»",
).to_pptx("output/digest.pptx")

Однако при таком варианте невозможно предварительно проверить df is None, поэтому для данных из внешнего источника предпочтительнее отдельная проверка.

Повторный вызов build() для того же объекта не очищает презентацию, а добавляет еще один титульный слайд и еще один набор книжных слайдов.

Для новой презентации создавайте новый объект через from_excel().

Экспорт PPTX

Метод:

digest.to_pptx("output/digest.pptx")

использует общий экспортер deck_builder.io.export.to_pptx().

Экспортер:

  • преобразует расширение результата в .pptx;
  • создает отсутствующие родительские каталоги;
  • сохраняет презентацию через python-pptx;
  • перезаписывает существующий файл по тому же пути.

Метод принимает как строку, так и pathlib.Path:

from pathlib import Path

digest.to_pptx(Path("output/digest.pptx"))

Настройка оформления

Основной конфиг дайджеста находится в:

src/deck_builder/configs/titles_digest.py

По умолчанию используются:

Размер слайда: 254 × 143 мм
Шрифт:        IBM Plex Sans

PresentationConfig объединяет:

  • ресурсы презентации;
  • размер слайда;
  • поля;
  • типографику;
  • цвета;
  • позиции элементов титульного слайда;
  • позиции элементов книжного слайда.

Логотип и иконка находятся в:

src/deck_builder/assets/

Пути к ним определяются относительно установленного Python-пакета, поэтому запуск проекта не зависит от текущей рабочей директории.

Создание измененной конфигурации

Конфигурационные dataclass-объекты неизменяемы (frozen=True).

Например, для замены шрифта:

from dataclasses import replace

from deck_builder.builders.titles_digest import TitlesDigest

digest = TitlesDigest.from_excel("data/books.xlsx")

if digest.df is None:
    raise ValueError("Некорректная таблица")

config = replace(
    digest.config,
    typography=replace(
        digest.config.typography,
        font_family="Arial",
    ),
)

digest = replace(
    digest,
    config=config,
)

digest.build(
    title_text="Книжные новинки",
    suptitle_text="Сентябрь 2026",
    attribution_text="Редакция «Лингва»",
).to_pptx("output/titles_digest.pptx")

Ограничения шаблона

Позиции основных элементов слайда фиксированы конфигурацией.

Текстовые блоки используют автоматическое увеличение высоты, но координаты следующих элементов при этом автоматически не пересчитываются.

Поэтому длинные:

  • названия,
  • аннотации,
  • списки причин,
  • теги

могут привести к пересечению элементов или выходу за предполагаемую область.

После генерации презентации рекомендуется проверить результат в PowerPoint или совместимом приложении.

Структура проекта

src/deck_builder/
├── __init__.py
├── cli.py                     # Typer CLI
├── assets/
│   ├── icon_lingua.png
│   └── logo_lingua.png
├── builders/
│   └── titles_digest.py       # TitlesDigest и сборка презентации
├── configs/
│   ├── base.py                # Базовые конфиги оформления
│   ├── schemas.py             # Схема Excel и BookInfo
│   └── titles_digest.py       # Оформление дайджеста
├── core/
│   ├── images.py              # Загрузка и размещение изображений
│   ├── slides.py              # Компоновка слайдов
│   └── texts.py               # Текстовые блоки, теги и нормализация
└── io/
    ├── export.py              # Экспорт PPTX
    ├── load.py                # Чтение Excel
    └── validate.py            # Проверка схемы и типов

tests/
├── conftest.py
├── test_builder.py
├── test_cli.py
├── test_images.py
├── test_io.py
├── test_production_regressions.py
├── test_slides.py
└── test_texts.py

Разработка

Установите dev-зависимости:

uv sync --locked --group dev

Тесты

uv run pytest

Ruff

Проверка:

uv run ruff check .

Проверка форматирования:

uv run ruff format --check .

Применение форматирования:

uv run ruff format .

Проверка типов

uv run basedpyright src

Bandit

uv run bandit -c pyproject.toml -r src

Конфигурация Bandit исключает каталог tests и виртуальное окружение .venv; проверка B101 отключена.

Команды проверок независимы друг от друга: успешный pytest не означает, что ruff, basedpyright или bandit также завершатся без замечаний.

Тесты

Тесты покрывают:

  • создание TitlesDigest;
  • загрузку Excel;
  • проверку обязательных столбцов и типов;
  • CLI;
  • обработку причин и тегов;
  • нормализацию типа переплета;
  • загрузку и геометрию обложек;
  • построение титульного и книжного слайдов;
  • экспорт PPTX;
  • регрессионные контракты production-кода.

Сетевые обращения и чтение Excel в соответствующих тестах заменяются моками.

Для части проверок используются реальные объекты python-pptx и временно создаваемые PPTX-файлы.

Регрессионные тесты находятся в:

tests/test_production_regressions.py

Лицензия

Исходный код проекта распространяется на условиях PolyForm Noncommercial License 1.0.0.

Вы можете использовать, изменять и распространять программное обеспечение в пределах, разрешенных этой лицензией. Коммерческое использование не разрешено.

Логотип и графические материалы

Файлы src/deck_builder/assets/logo_lingua.png и src/deck_builder/assets/icon_lingua.png не являются частью лицензируемых материалов проекта.

Логотип и иконка редакции «Лингва» принадлежат ООО «Издательство АСТ». Они включены в репозиторий исключительно для использования в предусмотренном проектом шаблоне презентации.

PolyForm Noncommercial License не распространяется на эти файлы. Настоящий репозиторий не предоставляет никаких прав или лицензий на их использование, копирование, изменение или распространение. Для любого использования этих материалов необходимо отдельное разрешение их правообладателя.

Copyright © 2026 Kirill Ignatyev — исходный код проекта, за исключением материалов третьих лиц, указанных выше.

About

Генератор PowerPoint-презентаций из Excel для автоматизации редакционного дайджеста книжных новинок.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages