Webinar-to-PDF Pipeline
Convert webinar materials (HTML slide presentations, summaries, instructions, transcripts) into professional PDF documents using Playwright.
When to Use
- Converting interactive HTML slide presentations to PDF (1 slide = 1 page)
- Generating styled PDF documents (summaries, instructions, guides)
- Formatting raw transcripts into readable, sectioned PDFs
- Full webinar materials pipeline: audio -> transcript -> 4 PDFs -> Диск
- Пакет по спикерам: запись мастермайнда/конференции → отдельные видео+транскрипт+саммари на каждый доклад → Яндекс.Диск
Пайплайн: запись встречи → пакет по докладчикам → Я.Диск
Когда из ОДНОЙ записи (мастермайнд, конференция) нужно сделать отдельные дельиверблы по каждому докладу (видео + PDF-транскрипт + PDF-саммари) и выложить публичными ссылками:
- Скачать запись. Zoom share-ссылка + passcode → skill
zoom→ «Download a SHARE recording» (Playwright sniff, yt-dlp не умеет). Своя запись → Zoom API recordings. - Полный транскрипт + диаризация.
ffmpeg -i in.mp4 -vn -ac 1 -ar 16000 -b:a 64k a.mp3→ Deepgram RESTdiarize=true&utterances=true(skilldeepgram, модельnova-3, НЕ старыйPrerecordedOptions). - Границы докладов. По
utterancesнайти, где модератор передаёт слово (смена доминирующего спикера на длинном отрезке) → таймкоды start/end каждого доклада (segments.json). - Нарезать видео. На каждый доклад
ffmpeg -ss START -to END -i full.mp4 -c copy talk_N.mp4(copy = без перекодирования, быстро). - PDF транскрипт + саммари на каждый доклад — этим скиллом (
build_transcript_html.py→generate_document_pdf.py); саммари пишет Claude по транскрипту доклада. - Залить на Я.Диск папками
NN_Тема_Спикер/(video.mp4 + transcript.pdf + summary.pdf), собрать публичные ссылки (_file_urls.json).
Разовые скрипты пайплайна держи рядом с материалами, а не внутри навыка — они
привязаны к конкретной записи (таймкоды, имена докладов) и в переиспользование не идут.
Рабочая раскладка папки встречи: _download.py, _transcribe.py, _slice_videos.py,
_build_all_pdfs.py, _upload.py, _file_urls.json + сам материал. Навык даёт шаги и
генераторы PDF; склейка под конкретную запись — одноразовая.
Если контент идёт ещё и в свой канал — посты и карточки см. tg-post + cards-creator.
Для чужих встреч свой канал не упоминать вовсе: пакет отдаётся заказчику, и реклама
в нём выглядит как злоупотребление доступом к записи.
Architecture
Source materials (audio, HTML presentation) -> transcription (Deepgram or another ASR)
-> HTML documents (presentation, summary, instructions, transcript) -> Playwright
(headless Chromium, file:// or a local 127.0.0.1 server) -> PDF files -> Диск.
Что тебе понадобится
| Что | Зачем | Платно? |
|---|---|---|
| Playwright + Chromium | вся генерация PDF | нет |
Pillow, img2pdf, PyMuPDF |
слайды → JPEG → PDF, проверка результата без poppler | нет |
| ffmpeg | извлечь звук из записи и нарезать видео по докладам | нет, ставится отдельно от pip |
Deepgram API key (DEEPGRAM_API_KEY) |
транскрипт с диаризацией; ~$0.0043/мин, час записи ≈ $0.26 | да, но есть стартовый грант |
YANDEX_OAUTH_TOKEN или Google OAuth |
выложить пакет и получить публичные ссылки | нет |
Ветка «у меня уже есть текст транскрипта» бесплатна целиком: Deepgram нужен только чтобы получить текст из аудио.
pip install playwright Pillow img2pdf PyMuPDF requests
playwright install chromium
# ffmpeg: winget install Gyan.FFmpeg | brew install ffmpeg | apt install ffmpeg
Two PDF Generation Methods
Base headless-Chromium mechanics — launching, page.pdf(), per-slide screenshots, font
waits, "PDF came out blank" — live in export-pdf / export-png and are not repeated
here. Below is only what webinar materials add on top.
Method 1: Screenshot Approach (slide presentations)
export-png drives <deck-stage> + goToSlide(i); webinar decks use a different
contract (.slide.active toggling), which plain page.pdf() cannot separate — hence
a local script: screenshot each slide -> JPEG (quality=90) -> img2pdf.
python scripts/generate_presentation_pdf.py --url http://127.0.0.1:8889/presentation.html --output presentation.pdf
- Viewport 1920x1080; each slide MUST fit — on overflow reduce font sizes/paddings
- PNG -> JPEG drops ~40MB to ~7MB for 55 slides;
img2pdf.get_fixed_dpi_layout_fun((96, 96))fixes page size - CRITICAL: kill CSS animations before screenshots.
animation: slideInoropacity: 0+transitionrender as empty/transparent slides — inject CSS that disables animations and forces opacity (pattern in the script; symptom row inreferences/gotchas.md)
Method 2: page.pdf() (text documents)
Summaries, instructions, transcripts — normal flow layout, mechanics per export-pdf.
Wrapper: python scripts/generate_document_pdf.py --url http://127.0.0.1:8889/document.html --output document.pdf
— A4, margins 10mm, print_background=True, @media print for page breaks.
Local files: file:// works — the server is for fetch/modules only
Playwright does not block file://: both neighbour skills open local HTML exactly
that way (pathToFileURL(path.resolve(file)).href). What breaks under file:// is the
browser's CORS policy on resources the page fetches itself — fetch/XHR to local JSON
and <script type="module">. Plain markup, inline CSS and <img> load fine. So serve
over HTTP only when the page pulls its own data: python scripts/start_server.py --dir . --port 8889.
Critical: 127.0.0.1, NOT localhost — Playwright sometimes fails to resolve localhost.
HTML contracts
Slides (what generate_presentation_pdf.py drives): .slide absolutely positioned
and display:none, the visible one marked .slide.active, body pinned to 1920x1080,
overflow:hidden everywhere. Navigation: ArrowRight/Space = next, ArrowLeft = prev.
Готовый CSS — references/styling-guide.md (Slide Layout + Viewport Constraints).
Documents (summaries, instructions) — общая библиотека компонентов: .header .step
.prompt-box .tip-box .warning-box .webinar-quote .table .grid-2+.card,
плюс TOC, timestamp-badge и stats-bar для транскриптов; HTML/CSS каждого —
references/html-components.md. Не пересобирать по описанию: классы завязаны на
print-CSS оттуда же, свой вариант ломает разрывы страниц.
Transcript Formatting
To format a raw transcript (format: [MM:SS] Speaker N: text) into styled HTML:
- Parse entries with regex:
\[(\d+:\d+)\]\s*Speaker\s*\d+:\s*(.*) - Group consecutive entries into paragraphs (gap < 25s AND total < 600 chars)
- Define topic sections manually by timestamp boundaries
- Generate HTML with TOC, section headers, timestamped paragraphs
Run scripts/build_transcript_html.py — edit the sections list for topic boundaries.
Reference-style package (gradient cover + методичка) ⭐
Эталонный стиль пакета материалов вебинара. Спецификация ниже самодостаточна:
генератор _build_package.py пишется по ней под свой бренд за один заход и живёт
рядом с материалами встречи, а не внутри навыка.
Обложка (cover()): полностраничная градиентная секция в цвете бренда
height:262mm; margin:-18mm, eyebrow «ЗАКАЗЧИК × ИМЯ СПИКЕРА» (крупный letter-spacing),
огромный заголовок ТРАНСКРИПТ / САММАРИ (Manrope 900, 74pt), белая черта,
название вебинара, дата. Рендер — HTML + Playwright page.pdf() A4,
print_background=True, margins 0.
Транскрипт: cover → meta-strip → Содержание (TOC: раздел + таймкод справа) → разделы по таймкодам (h2 с левой синей чертой; границы задаются вручную списком (start_sec, title)) → блоки [таймкод] **Спикер.** текст (таймкод слева жирным синим, группировка ~90с / смена спикера).
Саммари = ОБРАЗОВАТЕЛЬНАЯ МЕТОДИЧКА, а не пересказ «о чём говорили». Обязательные блоки: metabox «О вебинаре», формула промпта (.formula), таблица инструментов с колонкой «доступен ли из твоей страны» (для аудитории под региональными ограничениями это первый вопрос к любому инструменту), по каждой задаче — тёмный промпт-бокс (.prompt) для копирования, чек-лист «с чего начать» (.chk с ☐), глоссарий, контакты. CSS-компоненты: .prompt .formula .callout .hl .chk .gloss .metabox.
Склейка 2+ записей (два файла с диктофона) в один документ: части идут по порядку;
таймкоды второй части смещаются на длительность первой (OFFSET = part1_end + ~60с)
→ сквозное время. Спикеров нормализовать: диаризация регулярно дробит ОДНОГО человека
на Speaker 1 и Speaker 3 — сверить по содержанию реплик и слить руками
(Speaker 1 + Speaker 3 → «Имя спикера», остальные → «Слушатель»). Автоматике тут верить
нельзя: через паузу между файлами сшивка по голосу не работает.
НЕ сваливать сырой markdown из диктофонных сервисов в саммари: в нём остаются
плейсхолдеры вида [Insert Question], служебные блоки «Задания» / «План действий» с
чекбоксами, картинки-водяные знаки сервиса и систематические опечатки в названиях
продуктов — ASR слышит бренд неправильно и пишет уверенно (Loveable вместо Lovable).
Всё чистить и переписывать редакторски. Названия инструментов проверять отдельным
проходом: в методичке неверное название означает, что читатель просто не найдёт продукт.
Голос для устных материалов (спикерноутсы, скрипт): ~/.claude/voice-sample.md
(шаблон — ~/.claude/templates/voice-sample.md).
Color Scheme by Topic
Пример раскладки «тема → цвет»: назначь свои темы, палитра переносимая.
| Topic | Border color | Step circle class |
|---|---|---|
| AI-инструмент №1 | #8b5cf6 |
.purple |
| AI-инструмент №2 | #10b981 |
.green |
| Gemini | #4285F4 |
.blue |
| Telegram | #0088cc |
.tg |
| Agents | #f97316 |
.orange |
| n8n | #ea4c89 |
.pink |
| Prompts | #2D2FE8 |
.navy |
| Tips | #14b8a6 |
.teal |
Google Drive Upload
Готовым клиентом python ~/.claude/tools/gdrive_client.py (/gdrive, хаб —
google-workspace): папки, публичный доступ и токен там уже обработаны. Своего кода на
googleapiclient не писать — дубль расходится с клиентом при первой смене скоупов.
Yandex Disk Upload ⭐
Русскоязычной аудитории пакет удобнее отдавать на Яндекс.Диск, а не на Google Drive: у части зрителей Drive не открывается без VPN, и половина ссылок «не работает» ещё до того, как человек дошёл до материала.
REST-рецепт (mkdir → upload сырым телом → publish → public_url → удаление навсегда)
вынесен в references/yandex-disk-rest.md — он нужен не только вебинарам; доступы и
полная таблица эндпоинтов живут в скилле yandex. Здесь — только политика пакета:
- На Диске — ТОЛЬКО PDF для пользователей. Редактируемые исходники (docx/xlsx/pptx) НЕ заливать — держать локально. Если по ошибке залил папку
Исходники_*— удалитьpermanently=true. - Структура пакета:
Презентация/,Транскрипт_и_саммари/,Гайд_инструменты/. - В пакет добавляй гайд-таблицу инструментов (что делает / цена / доступен ли из твоей страны / сайт) отдельным landscape-PDF. Это самый частый вопрос после вебинара — без таблицы он приходит в личку по одному.
Verify PDFs without poppler/LibreOffice
pdftoppm часто отсутствует на Windows. Рендер страниц через PyMuPDF: fitz.open(p)[i].get_pixmap(dpi=80).tobytes('png') → собрать в сетку PIL и Read. Презентацию из картинок-слайдов в PDF собирать через PIL append_images (без LibreOffice).
Pre-Flight Checklist
- Every slide fits within 1920x1080 (no overflow)
- Promo codes and contacts are correct across ALL HTML files
- If the page fetches its own data — server is up on 127.0.0.1 (otherwise
file://is fine) - PDF page count matches slide count (for presentations)
-
@media printCSS rules prevent broken page breaks (for documents) - Uploaded folder is published (Я.Диск
public_url/ Drive "anyone can view")
Additional Resources
Reference Files
references/html-components.md— Full HTML/CSS code for all document components (header, steps, prompt-box, tip-box, tables, cards, footer)references/gotchas.md— Common problems and solutions (file locking, encoding, parallel Playwright, emoji rendering)references/styling-guide.md— Dark theme CSS for presentations, light theme CSS for documents, print CSSreferences/yandex-disk-rest.md— Я.Диск REST: mkdir / upload сырым телом / publish / public_url / удаление навсегда
Neighbour skills (не дублировать здесь)
export-pdf — печать HTML в PDF headless-Chromium · export-png — скриншоты слайдов ·
yandex — доступы и полная таблица эндпоинтов Диска · google-workspace — Drive-клиент.
Scripts
scripts/generate_presentation_pdf.py— Screenshot approach: slides -> JPEG -> PDFscripts/generate_document_pdf.py— page.pdf() approach for text documentsscripts/build_transcript_html.py— Parse and format transcript into styled HTMLscripts/start_server.py— HTTP server for pages that fetch their own local data
Reference Implementation
Готового демо-пакета в навыке нет намеренно: он весит десятки мегабайт и устаревает
вместе с контентом. Первый свой пакет собирается по шагам выше; контракты HTML
(.slide.active, компоненты документов) и готовый CSS лежат в references/ —
их достаточно, чтобы получить рабочий PDF с первого прогона.