Сервис для конвертирования презентаций из pptx и других форматов в pdf (при необходимости) и потом в webp/png в архиве
  • Go 93.9%
  • Python 3%
  • Shell 2.6%
  • Dockerfile 0.5%
Find a file
fedoryuk_au d8aa5421a3
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
fix: Учет слайдов без заметок.
2026-07-08 15:13:37 +03:00
cmd/conference-convert fix: Логирование 2026-07-03 14:13:17 +03:00
docs fix: talkID 2026-07-06 08:40:06 +03:00
internal fix: Учет слайдов без заметок. 2026-07-08 15:13:37 +03:00
scripts feat: WebHooks и метрики 2026-06-30 12:03:39 +03:00
testdata feat: WebHooks и метрики 2026-06-30 12:03:39 +03:00
.dockerignore feat: WebHooks и метрики 2026-06-30 12:03:39 +03:00
.gitignore feat: Добавление тестов 2026-07-02 16:20:22 +03:00
.woodpecker.yml fix: CI 2026-07-03 13:54:36 +03:00
docker-compose.yml fix: Некоторые недочеты и ошибки, которые могут быть критичными 2026-06-30 12:18:51 +03:00
Dockerfile feat: Добавление тестов 2026-07-03 11:32:24 +03:00
Dockerfile.ci feat: CI 2026-07-03 12:06:18 +03:00
FontSubstTable.xcu first commit 2026-06-30 10:21:16 +03:00
go.mod feat: Транслитерация имен файлов 2026-07-06 10:00:41 +03:00
go.sum feat: Транслитерация имен файлов 2026-07-06 10:00:41 +03:00
install-deps.sh first commit 2026-06-30 10:21:16 +03:00
LICENSE first commit 2026-06-30 10:21:16 +03:00
README.md feat: Транслитерация имен файлов 2026-07-06 10:00:41 +03:00

Converter Service

Микросервис конвертации презентаций в набор изображений слайдов (PNG/WebP), упакованных в ZIP-архив, с генерацией PDF. Асинхронная обработка с webhook-уведомлениями.

Технологии: Go 1.26 (net/http, archive/zip, channels), LibreOffice Headless, Poppler-utils (pdftoppm), pdfcpu, gowebp (pure Go WebP), Prometheus.

Входные и выходные форматы

Вход origin pdf zip
PDF загруженный PDF тот же PDF pdftoppm → PNG/WebP → ZIP
PPT / PPTX / PPS / PPSX / PPTM / POTX / POT / ODP / FODP / SXI / KEY копия оригинала LibreOffice → PDF LO → PDF → pdftoppm → PNG/WebP → ZIP
ZIP (с metadata.json + слайдами) загруженный ZIP pdfcpu → PDF из слайдов тот же ZIP

Возможности

  • 12 входных форматов: PPT, PPTX, PPS, PPSX, PPTM, POTX, POT, ODP, FODP, SXI, KEY, PDF + готовый ZIP
  • Конвертация в PDF через LibreOffice Headless или pdfcpu (для ZIP со слайдами)
  • Извлечение слайдов в PNG (96 DPI, масштабирование до 1920px)
  • Сжатие в WebP (pure Go, без CGO/libwebp)
  • Автоматическая транслитерация имён файлов (кириллица → ASCII) через go-unidecode
  • ZIP-архив с metadata.json и слайдами
  • Webhook-уведомления с HMAC-SHA256 подписью
  • In-memory очередь с Worker Pool (ограничение параллелизма)
  • Rate limiting per-IP (скользящее окно)
  • Prometheus метрики (/api/metrics)
  • Graceful shutdown + Cleanup по TTL
  • Docker (Ubuntu 26.04, LibreOffice 25.2.3, Poppler-utils 25.03.0)

Конфигурация

Все параметры задаются через переменные окружения или флаги командной строки.

Переменная Флаг Тип По умолчанию Описание
PORT --port int 8080 Порт HTTP-сервера
API_KEY --api-key string required API-ключ аутентификации
STORAGE_PATH --storage-path string /data/converted Директория готовых файлов
UPLOAD_PATH --upload-path string /data/uploads Временная директория загрузок
MAX_CONCURRENT_TASKS --max-tasks int 3 Макс. параллельных задач
MAX_FILE_SIZE --max-file-size int 524288000 Макс. размер файла (байт)
TASK_TIMEOUT_SLIDE --timeout-slide int 60 Таймаут на слайд (сек)
LIBREOFFICE_TIMEOUT --timeout-libre int 300 Таймаут LibreOffice (сек)
OUTPUT_WIDTH --output-width int 1920 Ширина слайда (px, 0=исходная)
OUTPUT_HEIGHT --output-height int 0 Высота (0 = пропорционально ширине)
WEBP_QUALITY --webp-quality int 90 Качество WebP (1100)
WEBHOOK_MAX_RETRIES --webhook-retries int 10 Попыток webhook-доставки
WEBHOOK_BACKOFF_BASE --webhook-backoff int 1 Начальная задержка retry (сек)
CALLBACK_URL --callback-url string "" URL webhook по умолчанию
LOG_LEVEL --log-level string off Уровень логов: off (нет логов, по умолч.), debug, info, warn, error
CLEANUP_TTL --cleanup-ttl int 24 Время жизни архива (часы, 0=откл)
CLEANUP_INTERVAL --cleanup-interval int 60 Интервал проверки cleanup (мин)
RATE_LIMIT_REQUESTS --rate-limit-reqs int 100 Запросов в окно rate limit
RATE_LIMIT_WINDOW --rate-limit-window int 60 Окно rate limit (сек)
CONVERTER_RETRIES --converter-retries int 3 Retry LibreOffice/pdftoppm (0=без retry)

Быстрый старт

# Сборка бинарника
CGO_ENABLED=0 go build -o converter-service ./cmd/conference-convert/

# Docker
docker build -t converter-service:latest .
docker run -p 8081:8080 \
  -e API_KEY=my-secret-key \
  converter-service:latest

# Проверка
curl -s http://localhost:8081/api/health
# {"status":"ok"}

API (кратко)

Метод Путь Описание
POST /api/convert Создать задачу (multipart, 202 Accepted)
GET /api/result/{task_id} Статус задачи
GET /api/result/{task_id}/origin Скачать оригинальный файл
GET /api/result/{task_id}/pdf Скачать PDF
GET /api/result/{task_id}/zip Скачать ZIP со слайдами
DELETE /api/result/{task_id} Удалить все файлы задачи
POST /api/callback/retry/{task_id} Повторная отправка webhook
GET /api/health Health check
GET /api/readyz Readiness probe
GET /api/metrics Prometheus метрики

Полная спецификация: docs/api.md

Протокол интеграции

Жизненный цикл задачи

1. POST /api/convert  (multipart: file + talk_id)
       ↓
2. 202 Accepted       {task_id: "conv_xxx", status: "processing"}
       ↓
3. Асинхронная конвертация (секунды–минуты)
       ↓
4. Один из двух вариантов:

   A) Polling                         B) Webhook
   ────────────                       ────────────
   GET /api/result/{task_id}          POST <callback_url>
   до status = "completed"            {event, task_id, status, ...}
        или "failed"

Поток данных

sequenceDiagram
    participant Client as Внешний сервис
    participant API as Converter API
    participant Store as Хранилище

    Client->>API: POST /api/convert (file + talk_id)
    API-->>Client: 202 {task_id, status: "processing"}
    Note over API: Асинхронная обработка...

    Client->>API: GET /api/result/{task_id}
    API-->>Client: 200 {status: "processing"}
    Note over API: ...процесс завершён...
    Client->>API: GET /api/result/{task_id}
    API-->>Client: 200 {status: "completed", total_slides, ...}

    Client->>API: GET /api/result/{task_id}/origin
    API-->>Client: 200 original file

    Client->>API: GET /api/result/{task_id}/pdf
    API-->>Client: 200 application/pdf

    Client->>API: GET /api/result/{task_id}/zip
    API-->>Client: 200 application/zip

    Client->>API: DELETE /api/result/{task_id}
    API-->>Client: 200 {status: "deleted"}

Стратегии получения результата

A. Polling — клиент периодически опрашивает статус:

# Создать задачу
curl -s -X POST http://localhost:8080/api/convert \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@presentation.pptx" -F "talk_id=42"

# {"task_id":"conv_abc123","status":"processing"}

# Опрашивать, пока status не станет completed/failed
curl -s http://localhost:8080/api/result/conv_abc123 \
  -H "Authorization: Bearer $API_KEY"

# {"status":"completed","total_slides":10,"format":"webp",...}

Рекомендуемый интервал: 12 секунды.

B. Webhook — сервис сам уведомит клиента:

curl -s -X POST http://localhost:8080/api/convert \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@presentation.pptx" -F "talk_id=42" \
  -F "callback_url=https://my-service.example/webhook"

После завершения конвертации Converter Service отправляет POST на callback_url:

{
  "event": "conversion.completed",
  "task_id": "conv_abc123",
  "talk_id": 42,
  "status": "completed",
  "result": {
    "origin_url": "/api/result/conv_abc123/origin",
    "pdf_url": "/api/result/conv_abc123/pdf",
    "zip_url": "/api/result/conv_abc123/zip",
    "format": "webp",
    "total_slides": 10,
    "size_bytes": 1048576
  }
}

При ошибке:

{
  "event": "conversion.failed",
  "task_id": "conv_abc123",
  "talk_id": 42,
  "status": "failed",
  "error": {
    "code": "LIBREOFFICE_TIMEOUT",
    "message": "conversion to PDF failed",
    "details": "LIBREOFFICE_TIMEOUT: libreoffice timed out after 300s"
  }
}

Контракт webhook

Параметр Значение
HTTP метод POST
Content-Type application/json
Таймаут на ответ 10 секунд
Retry до WEBHOOK_MAX_RETRIES раз, экспоненциальная задержка
Подпись X-Webhook-Signature: sha256=<hex> (HMAC-SHA256, если указан callback_secret)
Код успеха 2xx
Код неудачи не-2xx → retry
Исчерпание retry статус задачи → failed_to_notify

Сервис не переотправляет webhook автоматически после исчерпания retry. Для повторной отправки используйте POST /api/callback/retry/{task_id}.

Коды ошибок конвертации

Код Причина Что делать
INVALID_FILE Файл повреждён Проверить/перезалить файл
UNSUPPORTED_FORMAT Формат не из списка Проверить расширение
LIBREOFFICE_TIMEOUT LibreOffice не уложился в таймаут Увеличить LIBREOFFICE_TIMEOUT
LIBREOFFICE_ERROR LibreOffice упал Проверить файл, логи
PDFTOPP_TIMEOUT pdftoppm на одном слайде завис Увеличить TASK_TIMEOUT_SLIDE
PDFTOPP_ERROR pdftoppm не смог обработать PDF Проверить PDF
PDF_PASSWORD_REQUIRED PDF защищён паролем Снять пароль
EMPTY_PRESENTATION 0 слайдов после конвертации Проверить файл
CWEBP_ERROR Ошибка кодирования WebP Редкая ошибка, retry
STORAGE_ERROR Ошибка записи на диск Проверить STORAGE_PATH
INVALID_ZIP ZIP не соответствует формату Проверить структуру архива

Тайм-ауты и TTL

Тайм-аут Где задаётся Дефолт Описание
На один слайд TASK_TIMEOUT_SLIDE 60 с pdftoppm на извлечение одного слайда
LibreOffice LIBREOFFICE_TIMEOUT 300 с Конвертация файла в PDF
Webhook жёстко 10 с Ожидание ответа от callback_url
Retry pdftoppm/LO CONVERTER_RETRIES 3 Число попыток (с задержкой)
Retry webhook WEBHOOK_MAX_RETRIES 10 Число попыток доставки
TTL архива CLEANUP_TTL 24 ч Через CLEANUP_TTL часов файлы удаляются

Ограничения

  • Максимальный размер файла: MAX_FILE_SIZE (дефолт 500 MB)
  • Параллельные задачи: MAX_CONCURRENT_TASKS (дефолт 3), остальные ждут в очереди
  • Rate limit на HTTP API: RATE_LIMIT_REQUESTS запросов за RATE_LIMIT_WINDOW секунд на IP
  • Все данные хранятся локально, внешнее S3/блочное хранилище не используется
  • Нет переотправки webhook после исчерпания retry — используйте POST /api/callback/retry/{task_id}

Зависимости

  • Go 1.26.4 — основная логика, ZIP, HTTP, JSON
  • LibreOffice 25.2.3 — конвертация PPT/ODP/KEY → PDF (установлен в Docker)
  • Poppler-utils 25.03.0pdftoppm: PDF → PNG
  • pdfcpu — генерация PDF из изображений (pure Go)
  • gowebp — сжатие PNG → WebP (pure Go, без CGO)

Мониторинг

Метрика Тип Описание
conversion_tasks_total Counter Всего задач (теги: status)
conversion_duration_seconds Histogram Длительность конвертации
conversion_tasks_active Gauge Текущие активные задачи
webhook_delivery_attempts_total Counter Попытки отправки webhook

Архитектура

HTTP API → in-memory Queue → Worker Pool → Pipeline
                                            ├─ LibreOffice (презентация → PDF)
                                            ├─ pdftoppm (PDF → PNG)
                                            ├─ gowebp (PNG → WebP)
                                            ├─ pdfcpu (слайды → PDF)
                                            ├─ NotesExtractor (аннотации)
                                            └─ ZIP (слайды + metadata.json)
                                              → Локальное хранилище
                                                → Webhook-уведомление

Подробнее: docs/architecture.md

Права доступа

  • /api/health, /api/readyz, /api/metrics — без аутентификации
  • Все остальные эндпоинты — Authorization: Bearer <API_KEY>