Skip to content

Studio: import slide decks (.pptx/.ppt/.odp) as PDF via a LibreOffice container - #98

Merged
leorfer23 merged 1 commit into
mainfrom
feat/pptx-to-pdf
Oct 14, 2026
Merged

leorfer23 merged 1 commit into
mainfrom
feat/pptx-to-pdf

Conversation

@leorfer23

Copy link
Copy Markdown
Contributor

What

A customer drops a .pptx (or .ppt / .odp) on the Studio Home and gets a private pdf artifact (viewer from #88). The original deck is kept at original/<file>, recorded in type_metadata.source, and downloadable, the same way #93 handles docx/xlsx.

Conversion runs in LibreOffice inside a Cloudflare Container, behind an optional SLIDES_CONVERTER Durable Object binding.

How

  • shareout-app/containers/slides/: Dockerfile (pinned debian:trixie-20260918-slim, libreoffice-impress, metric-compatible fonts Carlito/Caladea/Liberation) and server.py (stdlib HTTP on :8080, POST /convert, non-root user, per-request LibreOffice profile, at most 2 conversions at once, kills the process group after 75 s).
  • src/pages/home/slides-converter-do.ts: SlidesConverter extends Container (@cloudflare/containers pinned 0.3.7), enableInternet = false, sleepAfter = '2m'. It's exported from src/index.ts. Nothing routes public traffic to it. Only import-slides.ts calls it through the binding.
  • src/pages/home/import-slides.ts: calls the converter (90 s worker timeout), checks the output is really a PDF, and returns { pdf, original } + source.
  • import.ts / import-rules.ts: IMPORT_SLIDES_EXTS → artifact type pdf. Decks must be sent base64, with the same 25 MB cap as the other imports.
  • Client (drop-import.ts): decks go to import only when window.WSX_SLIDES is set (!!env.SLIDES_CONVERTER). Otherwise they still go to Recursos, so instances without the binding behave exactly as before.
  • Studio inspector: a PDF has no viewer shell, so the inspector gets a "Download original" button. GET /v1/artifacts/:id now returns original when the artifact kept one.

Errors (nothing is published, rate limit not consumed)

Case Status Code
No binding 422 CONVERT_UNAVAILABLE (en/es copy: "export it as PDF")
> 25 MB (worker, and again in the container) 413 FILE_TOO_LARGE
Timeout (worker 90 s / container 75 s) 422 CONVERT_TIMEOUT
No container instance free 503 CONVERT_BUSY
LibreOffice fails or output isn't a PDF 422 CONVERT_FAILED

Import-rules note (#93)

#93 merged before this PR, so this PR includes the .pptx route itself. No follow-up needed. IMPORT_ARTIFACT_EXTS is unchanged (decks aren't unconditionally artifacts). The binding gates them via IMPORT_SLIDES_EXTS + WSX_SLIDES.

Checks (local, no Actions)

  • tsc --noEmit clean
  • npm run test:affected: all green after fixes (new tests in home-import.test.ts, home-drop-import.dom.test.ts, get-artifact.test.ts)
  • pre-push fast checks (ci-check-fast.sh) passed on push, no --no-verify
  • Docker: built the image (linux/amd64) and converted a real 2-slide .pptx → 2-page PDF with text intact ("Revisión Q3…"). Chunked bodies → 200, bad ext → 415, junk bytes → 422, 27 MB → 413. Temp dirs are cleaned up and the process runs as uid 10001.
  • wrangler deploy --dry-run with the container blocks uncommented: config valid, env.SLIDES_CONVERTER bound, container shareout-slidesconverter detected. A dry-run of the stock config also passes.

Prod deploy needs (not done here; no prod config touched)

  1. Add to the prod overlay (shareout-private / wrangler.prod.toml) the three blocks commented in shareout-app/wrangler.toml: [[containers]] (class_name = "SlidesConverter", image = "./containers/slides/Dockerfile", instance_type = "standard-1", max_instances = 1), the SLIDES_CONVERTER DO binding, and a new_sqlite_classes = ["SlidesConverter"] migration with the next free tag in prod's migration list (the OSS template uses v9).
  2. Docker must be running where wrangler deploy runs, because it builds and pushes the image to the Cloudflare registry (compressed image is about 190 MB). The first deploy provisions the container, and it can take a few minutes before conversions answer instead of CONVERT_BUSY.
  3. Workers Paid plan (Containers is beta).

Cost notes (from the pricing page, 2026-10): billed only while awake, and it sleeps 2 min after the last conversion. standard-1 = ½ vCPU / 4 GiB. A cold conversion keeps it up about 2–3 min, roughly $0.003 above the included usage. Included monthly: 25 GiB-h memory (≈6 h awake at 4 GiB) and 375 vCPU-min. max_instances = 1 bounds spend, and concurrent imports beyond 2 queue or get CONVERT_BUSY. Raise max_instances and spread names if volume grows.

Not verified

  • On Cloudflare itself: the container cold start, startAndWaitForPorts against this image, and the DO→container request path. Tested only with a local docker run and stubbed binding unit tests.
  • wrangler dev with the container (local container dev).
  • UI by eye (per repo rule): the drop flow and the inspector "Download original" button.
  • .ppt / .odp real-file conversion (only .pptx converted for real; the other two share the code path).
  • Rendering fidelity for decks with embedded fonts, CJK text (no Noto CJK in the image), or remote-linked media (blocked by design: no internet).

🤖 Generated with Claude Code

@leorfer23
leorfer23 force-pushed the feat/pptx-to-pdf branch 3 times, most recently from 6ba37d2 to 4fd66ce Compare October 14, 2026 19:42
… container

- New optional SLIDES_CONVERTER binding: a Cloudflare Container (Debian
  trixie + LibreOffice Impress, containers/slides/) behind the SlidesConverter
  Durable Object (@cloudflare/containers 0.3.7, no outbound internet, sleeps
  after 2m). No public route reaches it; only POST /v1/home/import calls it.
- Import: a deck becomes a private pdf artifact; the original is kept at
  original/<file> and recorded in type_metadata.source, like Word/Excel (#93).
- Guards: 25 MB import cap (413 FILE_TOO_LARGE, also enforced in the
  container), 90 s worker / 75 s LibreOffice timeout (422 CONVERT_TIMEOUT),
  busy container 503 CONVERT_BUSY, bad output 422 CONVERT_FAILED. Nothing is
  published on any failure.
- Without the binding: the client keeps routing decks to Recursos (no
  behavior change; gated by window.WSX_SLIDES), and the API answers 422
  CONVERT_UNAVAILABLE. Copy in en/es.
- Studio inspector: "Download original" for pdf artifacts that kept one
  (GET /v1/artifacts/:id now returns `original`).
- wrangler.toml: commented [[containers]] + binding + migration v9 block;
  deploy doc lists the optional binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leorfer23
leorfer23 merged commit dc59114 into main Oct 14, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant