Create, read, edit, convert, repair, or inspect OpenDocument Presentation files (.odp).
An .odp file is an OpenDocument ZIP package for presentations. Important package files:
mimetype - should be the first ZIP entry and stored uncompressed as application/vnd.oasis.opendocument.presentationcontent.xml - slides, shapes, text boxes, images, speaker notesstyles.xml - presentation, drawing, and text stylesmeta.xml - document metadatasettings.xml - application settingsMETA-INF/manifest.xml - package manifestPictures/... - embedded media| Task | Preferred approach |
|---|---|
| Create branded deck | Start from an .odp template, clone slides/masters, edit XML |
| Create simple structured deck | Generate ODP package XML directly |
| Convert PPTX/HTML/SVG to ODP | Use LibreOffice only when the source already exists or interoperability requires it |
| Extract text | Use scripts/extract_text.py or parse content.xml |
| Preserve template layout | Edit the existing ODP package XML and media references |
| Visual QA | Convert to PDF or slide images and inspect every slide |
Before starting a real ODP task, check available tools:
which pandoc
Resolve the LibreOffice command as described in docs/soffice-resolver.md.
LibreOffice is the preferred engine for ODP conversion and rendering. Pandoc is not a full ODP authoring or inspection tool.
For raw inspection:
unzip -l input.odp
python -m zipfile -e input.odp unpacked_odp
Slide content is usually in content.xml under draw:page elements. Extract text with an XML parser, keeping namespace handling explicit.
Bundled scripts for common inspection tasks:
# Extract visible slide text and speaker notes.
python scripts/extract_text.py input.odp
python scripts/extract_text.py input.odp --json
# Inspect package files, media references, slide metadata, notes, and master pages.
python scripts/inspect_package.py input.odp
For the full script reference, see docs/script-reference.md.
Useful things to inspect:
draw:page elements for slide order and namesdraw:frame and draw:text-box for positioned contenttext:p and text:list for visible slide textpresentation:notes for speaker notesdraw:image references for embedded mediaAn ODP presentation normally stores slides in content.xml under this broad path:
office:document-content
office:body
office:presentation
draw:page
Each draw:page is one slide. Important attributes commonly include:
draw:name - slide name, often visible in Impress navigationdraw:style-name - slide/page style referencedraw:master-page-name - links the slide to a master pagepresentation:presentation-page-layout-name - placeholder/layout behaviorpresentation:use-header-name, presentation:use-footer-name, presentation:use-date-time-name - header/footer/date-time bindings when presentTypical slide children:
draw:frame - positioned container for text boxes, images, objects, charts, or pluginsdraw:text-box inside draw:frame - text box contenttext:p, text:span, text:list - visible text runs and listsdraw:image - embedded or linked image reference, usually via xlink:hrefdraw:custom-shape, draw:rect, draw:ellipse, draw:line, draw:connector, draw:path - vector shapespresentation:notes - speaker notes and notes-page content for that slideanim:par / animation elements - slide animations or transitions when presentSpeaker notes live inside the slide's draw:page, usually as a presentation:notes child. Notes may contain their own drawing page/frame structure, not just plain text:
draw:page
... visible slide shapes ...
presentation:notes
draw:page-thumbnail
draw:frame
draw:text-box
text:p
When extracting notes, gather text under presentation:notes separately from visible slide text. When editing visible slide text, avoid accidentally modifying notes text with broad text:p searches.
Master pages are usually defined in styles.xml, not content.xml, under:
office:document-styles
office:master-styles
style:master-page
Slides connect to masters through draw:page/@draw:master-page-name. A style:master-page can reference page layouts and include background shapes, logos, footer placeholders, and presentation placeholders. Common attributes and children include:
style:name - the value referenced by draw:master-page-namestyle:page-layout-name - page size and margins through a page layout styledraw:frame, draw:rect, draw:image, draw:text-box - master-level visible/background objectspresentation:placeholder or presentation classes on frames - title/body/footer/date placeholdersWhen changing a repeated background, footer, logo, or title placeholder, inspect the matching style:master-page first. Editing each slide separately is only appropriate when the content is slide-specific.
ODF models slides as a flat two-layer reference, not a PowerPoint-style master
hierarchy: each draw:page independently names a master page (the chrome —
background, header/footer) and a slide layout (style:presentation-page-layout
— the placeholder zone arrangement). create_minimal_odp.py ships six standard
layouts: title-slide, title-content (default), two-content,
section-header, title-only, and blank.
# Generate a deck with per-slide layouts and an extra master:
python scripts/create_minimal_odp.py deck.json deck.odp
# Reassign layout/master on existing slides (placeholder frames are repositioned):
python scripts/set_layout.py deck.odp --slide 2 --layout two-content -o deck.odp
python scripts/set_layout.py deck.odp --slide all --master Brand -o deck.odp
python scripts/list_masters.py deck.odp # masters + layouts + per-slide usage
Spec shape — a layout key per slide selects the layout; content keys fill its
zones (title, subtitle, body, body_left, body_right); a top-level
masters array adds extra master pages, and a slide's master key picks one:
{
"masters": [{"name": "Brand", "background_color": "#02416C"}],
"slides": [
{"layout": "title-slide", "master": "Brand", "title": "Q1 Review", "subtitle": "2026"},
{"layout": "two-content", "title": "Regions", "body_left": ["North"], "body_right": ["South"]}
]
}
Specs without a layout key behave exactly as before — title/body fill the
title-content layout. Generator masters carry a background_color; for dark
master backgrounds pair them with branded text styles via
inject_styles_from_file. validate_refs.py flags slides whose master or
slide-layout reference does not resolve.
ODP content uses many name-based references across content.xml and styles.xml:
draw:style-name points to drawing/graphic stylestext:style-name points to paragraph or text stylesdraw:text-style-name can control text inside drawing objectspresentation:style-name and presentation layout names can control placeholder behaviordraw:master-page-name points from a slide to a master pageBefore deleting or renaming a style or master page, search both content.xml and styles.xml for the name. Broken style references often render as layout shifts rather than obvious validation errors.
ODP is an XML package and can be generated directly. Do not default to PPTX as an intermediate just because PPTX tooling exists.
Choose the creation path by fidelity needs:
| Scenario | Use |
|---|---|
| Branded deck, recurring layouts, exact master/footer/logo behavior | Template-first ODP |
| Simple generated deck with title/body/image/diagram slides | Direct ODP XML generation |
| Existing PPTX/HTML/SVG source or explicit cross-format conversion | LibreOffice conversion fallback |
Use this when visual consistency matters or a user provides an .odp template.
styles.xml master pages and content.xml slide structures.draw:page for each needed layout.Pictures/ and update META-INF/manifest.xml.This is usually the safest path for branded or institutional decks because it preserves master pages, placeholders, fonts, footers, and page settings.
Use this for simple or highly structured generated decks. Generate a minimal package with:
mimetypecontent.xmlstyles.xmlmeta.xmlsettings.xmlMETA-INF/manifest.xmlPictures/...Minimum slide structure:
office:body
office:presentation
draw:page draw:name="Slide 1" draw:master-page-name="Default"
draw:frame svg:x="1cm" svg:y="1cm" svg:width="24cm" svg:height="2cm"
draw:text-box
text:p text:style-name="Title"
When generating directly, keep the first version deliberately small: title/body/image slides, a small style set, one or two masters, and no animations until the PDF/PNG render is stable.
Use LibreOffice conversion when the source already exists in another format or when interoperability is the actual task:
# Resolve SOFFICE as shown in Tool Checks.
"$SOFFICE" --headless --convert-to odp source.pptx --outdir out
"$SOFFICE" --headless --convert-to pdf out/source.odp --outdir qa
Treat conversion as lossy until QA proves otherwise. Check fonts, images, connectors, masters, notes, and placeholder behavior after every conversion.
create_minimal_odp.py emits a designed default theme, not raw frames:
drawing-page style (dp-default) sets the slide background and is
referenced by the master page through draw:style-name.draw:frame references a graphic-family style (gr-title,
gr-body, gr-notes, gr-image) with draw:fill="none" and
draw:stroke="none". Without a graphic style a frame inherits
LibreOffice's default fill and renders as a blue box — always give
generated frames an explicit draw:style-name.Title/Body/Notes carry an explicit fo:color.Two ways to brand a deck:
Whole-theme swap — write a curated styles.xml that redefines the
same named styles (dp-default, gr-title, gr-body, Title, Body,
master Default, layout Screen) and inject it:
from odp_common import embed_pictures, inject_styles_from_file
inject_styles_from_file("base.odp", "branded-styles.xml", "styled.odp")
embed_pictures("styled.odp", {"Pictures/logo.png": "logo.png"}, "deck.odp")
Because content references styles by name, no edit to content.xml is
needed. See examples/deck/ for a complete
branded-deck build.
Per-master tweak — use customize_master.py for a background colour,
header/footer, page numbers, or a logo on one master page. The background
colour is written into the drawing-page style the master references
(setting it on the master element itself does not render).
create_minimal_odp.py accepts --theme NAME — a curated colour palette and
font pairing applied to the whole deck. Five themes:
| Theme | Feel |
|---|---|
corporate-blue |
clean corporate blue |
warm-editorial |
cream background, terracotta serif — reports, essays |
high-contrast |
black on white, bold — accessibility, print |
slate-mono |
slate palette, monospaced headings — technical decks |
forest |
deep green, sans heading + serif body |
python scripts/create_minimal_odp.py deck.json deck.odp --theme slate-mono
Without --theme the output is unchanged. A theme sets the default slide
background; a per-master background_color in the masters array still
overrides it. Themes name fonts as stacks with a Liberation fallback, so a
themed deck renders even where the first-choice font is absent. For branding
beyond the five themes, use a template (next section).
A template is a complete branded design (styles + master pages + slide layouts + assets like a logo) packaged as a directory. The skill ships three templates and three tools to work with them — themes are quick palette+font tweaks; templates are full branded designs.
skills/odp/templates/:
| Template | Look |
|---|---|
dao-conference |
deep-blue background (#02416C), Nunito Sans, logo bottom-right. Conference / grant decks. |
academic-blue |
cream background, navy Lato headings, Source Serif body. Long academic talks. |
minimalist-mono |
near-white background, JetBrains Mono headings, Inter body. Technical talks, post-mortems. |
apply_template.py is the one-call wrapper for injection + asset embedding +
validation. The agent's standard branding workflow:
# Build a base deck
python scripts/create_minimal_odp.py spec.json deck.odp
# Apply a shipped template by name
python scripts/apply_template.py deck.odp \
--template-name dao-conference -o branded.odp
# Or apply by path (e.g. a user-supplied template directory)
python scripts/apply_template.py deck.odp \
--template /path/to/my-template -o branded.odp
Before picking a layout per slide, ask what the template offers:
python scripts/inspect_template.py \
skills/odp/templates/dao-conference/styles.xml --json
Output is JSON with master_pages (each with background colour, frames,
placeholder classes), presentation_page_layouts (each with placeholder
zones and their geometry), paragraph_styles, graphic_styles, and
font_face_decls. Use this to drive per-slide layout and master
choices in your generation spec.
To turn an existing .odp/.otp/.pptx into a reusable template:
python scripts/extract_template.py investor-pitch.pptx \
--name pitch-warm --outdir skills/odp/templates/ \
--license CC-BY-4.0 --source "https://example.com/template"
The extractor filters office:automatic-styles to keep only what master
pages reference (and parent-style chains), copies master-page-referenced
images from Pictures/, and writes LICENSE.txt/PROVENANCE.md/README.md
metadata. PPTX inputs are auto-converted via the v1.11 OOXML bridge.
Each template directory has:
styles.xml — the branded theme.Pictures/ (optional) — master-page assets like logo.png.LICENSE.txt, PROVENANCE.md, README.md — metadata.Templates live inside skills/odp/ so install_skills.py bundles them
into every installation (Smithery, skills.sh, Claude Code plugin).
For creation and editing scripts, see docs/script-reference.md. All scripts use the Python standard library and are invoked as:
python scripts/<script_name>.py [args]
Keep direct generation deliberately small unless a task needs more: title/body/image slides, simple notes, a small style set, and QA-rendered output.
Use this checklist when creating or materially redesigning ODP slides. ODP decks should be designed for LibreOffice Impress rendering first, then checked for export stability.
draw:master-page-name points to the intended master after conversions or cloning.content.xml.META-INF/manifest.xml after adding images manually.For content/layout-preserving edits:
content.xml with an XML library.Pictures/ and update META-INF/manifest.xml.mimetype first and uncompressed.Repack pattern:
cd unpacked_odp
zip -0 -X ../output.odp mimetype
zip -r -X ../output.odp . -x mimetype
Assume generated or edited ODP files have problems until proven otherwise. ODP round-trips through LibreOffice can change fonts, connector routing, image sizing, placeholder behavior, and master-page inheritance.
Extract visible text and notes:
python scripts/extract_text.py output.odp
python scripts/extract_text.py output.odp --json > qa/text.json
Check:
Lorem, TODO, XXXX, or template instructionsInspect the package before visual review:
python scripts/inspect_package.py output.odp > qa/package.json
Check:
mimetype is firstcontent.xml, styles.xml, meta.xml, and META-INF/manifest.xml existdraw:image/@xlink:href exists in the packagedraw:master-page-namehas_notes: trueIf you changed masters, backgrounds, logos, footers, or placeholders, inspect both content.xml and styles.xml for broken style/master references before rendering.
Rendering is a design step, not only a final check. Render an early draft, look at it, fix what is wrong, then continue — do not build the whole deck blind and render once at the end.
python scripts/render.py output.odp --outdir qa --contact-sheet # all slides in one image
python scripts/render.py output.odp --outdir qa --png # one PNG per slide
python scripts/render.py output.odp --outdir qa # PDF only
python scripts/render.py output.odp --outdir qa --notes # + slides+notes PDF
python scripts/render.py output.odp --outdir qa --notes-only # + notes-only PDF
--notes / --notes-only use LibreOffice's impress_pdf_Export
filter (ExportNotesPages / ExportOnlyNotesPages) and write
<stem>-with-notes.pdf / <stem>-notes.pdf alongside the default
slide-only PDF — useful for reviewing speaker notes or producing a
handout for the presenter.
The contact sheet composes every slide into a single labelled grid image — the best way to judge cross-slide consistency (alignment, colours, repeated master elements) at a glance. Open it and actually look at it.
Inspect every rendered slide. Look for:
The final pass of a loop you should already be running while building the deck:
For template-based decks, always verify at least one slide using each master page/layout that was touched.
For one-shot conversion between ODP and Microsoft PowerPoint formats,
convert.py wraps soffice --headless --convert-to with an isolated temp
profile:
# ODP → PPTX:
python scripts/convert.py deck.odp --to pptx --outdir qa
# PPTX → ODP (the bridge: edit a PowerPoint deck with our skills, then export back):
python scripts/convert.py source.pptx --to odp --outdir qa
python scripts/add_animation.py qa/source.odp --slide 1 --shape "title" \
--effect "entrance:fade-in" -o edited.odp
python scripts/convert.py edited.odp --to pptx --outdir qa
# Legacy MS PowerPoint 97-2003 (.ppt):
python scripts/convert.py deck.odp --to ppt --outdir qa
python scripts/convert.py legacy.ppt --to odp --outdir qa
Fidelity caveat: soffice handles text, basic shapes, images, and master
pages well. Slide layouts (the ODF presentation-page-layout introduced in
v1.8) map approximately to PowerPoint slide layouts but are not perfectly
equivalent. Animations and transitions usually round-trip; some advanced
SMIL motion paths or custom-path effects may simplify or be dropped. Fonts
in particular substitute very differently across PowerPoint, LibreOffice
Impress, and Keynote — always render the output in the target application
before delivering.
For text documents, use the odt skill's convert.py (ODT ↔ DOCX/DOC);
for spreadsheets, the ods skill's convert.py (ODS ↔ XLSX/XLS). The
script enforces format families — cross-family conversions are rejected
with a clear hint.
Part of the open-document-skills suite: