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/ находятся пример исходной таблицы и сгенерированной из нее презентации:
titles_digest.xlsx— пример входных данных;titles_digest.pptx— результат генерации.
Презентацию можно воспроизвести командой:
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.
Одна строка соответствует одной книге.
Названия обязательных столбцов должны совпадать со схемой, включая пробелы и знаки препинания.
Порядок столбцов не важен. Дополнительные столбцы допускаются и при сборке игнорируются.
TitlesDigest.from_excel() использует polars.read_excel() с настройками по умолчанию. Выбор конкретного листа через публичный API TitlesDigest сейчас не предусмотрен.
| Обязательный столбец | Тип после загрузки | Пример |
|---|---|---|
Код ном-ры |
Строка | 00123 |
ISBN |
Строка | 9781234567890 |
Наименование на обложку |
Строка | Книга о языке |
Авторы на обложку |
Строка | Иван Иванов |
Аннотация |
Строка | Описание книги |
Пять причин купить |
Строка | Причины и теги, см. ниже |
Серия |
Строка | Изучаем языки |
Формат |
Строка | 60x90/16 |
Продукция.Тип переплета |
Строка | 3 |
Кол-во стр |
Int16 |
128 |
Красочность блока текста |
Строка | 1+1 |
Тираж |
Int64 |
1000 |
Продукция.Возрастное ограничение |
Строка | 12+ |
Код ном-ры и ISBN приводятся к строковому типу, но если Excel изначально хранит значение как число, уже потерянные ведущие нули восстановить невозможно.
Поэтому значения вроде:
00123
следует хранить в Excel именно как текст.
Значение:
3
при выводе преобразуется в:
мягкая обложка
Остальные значения выводятся без преобразования.
Поле Пять причин купить содержит одновременно текст причин и теги.
Каждый элемент размещается на отдельной строке:
1. Понятные объяснения
2. Практические задания
3. Ответы для самопроверки
4. Удобный формат
5. Подходит для самостоятельного изучения
#английский_язык
#самоучитель
Строки, которые после удаления начальных пробелов начинаются с #, считаются тегами.
При обработке:
#английский_язык
превращается в:
английский язык
То есть:
- символ
#удаляется; - пробелы по краям удаляются;
_заменяются пробелами;- строки с тегами удаляются из текста причин.
Для непустого значения Пять причин купить генератор обеспечивает наличие как минимум двух тегов.
Если тегов меньше двух, список дополняется:
<не заполнен тег>
Например, при одном теге:
#самоучитель
будут сформированы:
самоучитель
<не заполнен тег>
Если поле Пять причин купить пустое, причины и список тегов остаются пустыми.
Количество самих причин не проверяется. Заголовок блока на слайде всегда:
5 причин купить
При загрузке выполняются две проверки:
- наличие всех обязательных столбцов;
- возможность строго привести каждый обязательный столбец к типу из схемы.
При отсутствии столбцов в 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().
Метод:
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 devuv run pytestПроверка:
uv run ruff check .Проверка форматирования:
uv run ruff format --check .Применение форматирования:
uv run ruff format .uv run basedpyright srcuv 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 — исходный код проекта, за исключением материалов третьих лиц, указанных выше.