A generic, brand-agnostic toolkit for migrating PowerPoint presentations to branded templates. Supports intelligent content-type detection, layout variety tracking, and brand compliance analysis.
This toolkit provides the engine for brand-specific presentation skills:
- drupal-brand-skill - Drupal brand guidelines with presentation migration support
Brand skills use this toolkit's generic capabilities with their specific configurations (colors, fonts, layouts, templates).
- Content Extraction: Extract text and images from PPTX/PDF files
- Intelligent Migration: Auto-detect content types (stats, quotes, bullets, etc.) and map to appropriate template layouts
- Layout Variety: Prevents consecutive layout repeats for visual interest
- Brand Compliance Analysis: Scan presentations for off-brand fonts and colors
- Configurable: All brand-specific values (colors, fonts, slide catalog) loaded from YAML config
- content.json Intermediate Format: Validated JSON representation between parsing and generation — inspect, edit, and feed content from external tools
- Template Diagnostics: Pre-flight checks catch missing placeholders, broken references, and slide count mismatches before migration starts
- Slide Layout Cookbook: Explicit positioning recipes with automatic fallback when template placeholders are missing
pip install -e .With optional JSON Schema validation:
pip install -e ".[schema]"Or use directly:
python -m presentation_toolkit.cli migrate input.pptx output.pptx --config brand.yamlSee examples/sample_brand_config.yaml for a complete example.
version: "1.0"
brand_name: "My Brand"
colors:
primary:
brand_blue: "0066CC"
secondary:
white: "FFFFFF"
black: "000000"
fonts:
brand: ["Brand Font", "Noto Sans"]
replace: ["Arial", "Calibri", "Helvetica"]# Using CLI
pptx-migrate input.pptx output.pptx --config my-brand.yaml --template template.pptx
# Save intermediate content.json for inspection
pptx-migrate input.pptx output.pptx --config my-brand.yaml --save-content content.json
# Migrate from a pre-built content.json (skip parsing)
pptx-migrate --from-content content.json output.pptx --config my-brand.yaml
# Force cookbook mode (absolute positioning, no template placeholders)
pptx-migrate input.pptx output.pptx --config my-brand.yaml --use-cookbook
# Using Python
from presentation_toolkit import migrate_presentation, load_config
config = load_config("my-brand.yaml")
migrate_presentation("input.pptx", "output.pptx", config, "template.pptx")pptx-analyze presentation.pptx --config my-brand.yamlpptx-extract input.pptx --output content.md --images# Human-readable report
pptx-diagnose template.pptx --config brand.yaml
# JSON output for CI/automation
pptx-diagnose template.pptx --config brand.yaml --json
# Strict mode (exit code 1 on blocking errors)
pptx-diagnose template.pptx --strictThe intermediate content.json format provides a validated, editable representation of presentation content between the parsing and generation phases.
{
"version": "1.0",
"metadata": {
"source_file": "input.pptx",
"source_format": "pptx",
"generated_at": "2025-01-15T12:00:00Z",
"generator": "ai-presentation-toolkit"
},
"slides": [
{
"number": 1,
"title": "Welcome",
"body": "Subtitle text",
"content_type": "title_opening",
"layout_hint": "title_opening",
"images": [],
"zones": null,
"extraction_notes": []
},
{
"number": 2,
"title": "Key Metrics",
"body": "",
"content_type": "stats_dashboard",
"zones": {
"type": "stats_dashboard",
"stats": [
{"number": "85%", "label": "Customer Satisfaction"},
{"number": "$2.5M", "label": "Revenue Growth"}
]
}
}
]
}auto, statistic, stats_dashboard, quote, numbered_step, bullet_list, comparison, section_header, case_study, case_study_full, statement, feature, detailed_content, title_opening, closing
from presentation_toolkit import (
slides_to_content_document,
content_document_to_slides,
save_content_document,
load_content_document,
)
# Convert legacy slides to content document
doc = slides_to_content_document(slides, "input.pptx", "pptx")
# Save to JSON
save_content_document(doc, "content.json")
# Load and modify
doc = load_content_document("content.json")
doc.slides[0].content_type = "title_opening"
# Convert back to slides for migration
slides = content_document_to_slides(doc)When template placeholders are missing, the toolkit automatically falls back to the layout cookbook — a set of 13 built-in recipes that position content using absolute coordinates.
Available recipes: title_opening, feature_default, content_image_left, content_image_right, statement_center, stat_default, stats_dashboard, quote_default, two_column, section_divider, case_study_full, closing_cta, hero_photo
from presentation_toolkit import get_recipe, list_recipes, COOKBOOK
# List all available recipes
print(list_recipes())
# Get a specific recipe
recipe = get_recipe("stats_dashboard")
print(recipe.description)See the Configuration Schema for full details.
brand_name: Name of the brandcolors: Color palette with hex values (without #)fonts.brand: List of approved brand fontsfonts.replace: List of fonts to flag for replacement
template.default: Path to default template PPTXslide_catalog: Mapping of content types to template slide indicestext_capacity: Character limits and font sizes per layout typecontent_patterns: Regex patterns for content type detection
Migrate a presentation to a branded template.
pptx-migrate input.pptx output.pptx --config brand.yaml [options]
Options:
--template PATH Path to template PPTX (overrides config)
--no-images Skip image extraction/insertion
--save-content PATH Save intermediate content.json before migration
--from-content PATH Skip parsing, use pre-built content.json
--use-cookbook Force cookbook mode (absolute positioning)
--verbose Show detailed progressAnalyze a presentation for brand compliance.
pptx-analyze presentation.pptx --config brand.yaml [options]
Options:
--json Output results as JSON
--strict Fail on any compliance issueExtract content from a presentation.
pptx-extract input.pptx [options]
Options:
--output PATH Output markdown file (default: input.md)
--images Also extract images to folderRun template diagnostics.
pptx-diagnose template.pptx [options]
Options:
--config PATH Brand configuration file (YAML/JSON)
--strict Exit with error if blocking issues found
--json Output results as JSONDiagnostic codes:
| Code | Severity | Description |
|---|---|---|
| TMPL-001 | ERROR | Template file not found |
| TMPL-002 | ERROR | Not a valid ZIP/PPTX |
| TMPL-003 | ERROR | Missing required OOXML parts |
| TMPL-004 | WARNING | Config references slide index not in template |
| TMPL-010 | WARNING | Slide missing TITLE placeholder |
| TMPL-011 | WARNING | Slide missing BODY placeholder |
| TMPL-012 | INFO | Slide has no PICTURE placeholder |
| TMPL-020 | WARNING | stats_dashboard slide missing named shapes |
| TMPL-021 | WARNING | case_study_full slide missing Quote/Attribution shapes |
| TMPL-030 | WARNING | Template has >3 slide masters |
| TMPL-040 | WARNING | Relationship references missing media file |
| TMPL-050 | INFO | Shape covers >80% of slide area |
from presentation_toolkit import (
load_config,
migrate_presentation,
migrate_from_content,
analyze_presentation,
extract_pptx_to_markdown,
diagnose_template,
# Content document
ContentDocument,
SlideContent,
ContentType,
slides_to_content_document,
content_document_to_slides,
load_content_document,
save_content_document,
# Cookbook
LayoutRecipe,
COOKBOOK,
get_recipe,
list_recipes,
# Diagnostics
DiagnosticReport,
)
# Load brand configuration
config = load_config("brand.yaml")
# Migrate a presentation
migrate_presentation(
input_path="source.pptx",
output_path="branded.pptx",
config=config,
template_path="template.pptx",
insert_images=True,
diagnose=True,
)
# Migrate from content.json
migrate_from_content("content.json", "output.pptx", config, "template.pptx")
# Analyze for brand compliance
issues = analyze_presentation("deck.pptx", config)
for slide in issues:
print(f"Slide {slide['num']}: {slide['issues']}")
# Extract content
slides = extract_pptx_to_markdown("source.pptx", "content.md", extract_images=True)
# Diagnose template
report = diagnose_template("template.pptx", config)
report.print_report()
if report.has_blocking_issues:
print("Fix template before migration!")When generating presentations as HTML slides (e.g., using Manus slide tools or similar HTML-based renderers), the following hard-won lessons apply:
- CSS transform scaling is essential. When embedding 1280x720 slide content in a responsive iframe, use
transform: scale()with aResizeObserverto dynamically scale the iframe to fit its container. Settingwidth=device-widthin the viewport meta tag alone is unreliable. - Set
transform-origin: top lefton the iframe and calculatescale = containerWidth / 1280. Wrap the iframe in anoverflow: hiddencontainer sized tocontainerWidth x (containerWidth * 9/16). - Use
height: 720px !importanton.slide-containerto override inlinemin-height: 720pxstyles that cause white-space gaps at the bottom of slides.
- Never use
background-size: 100% 100%on GUI block images. This stretches rounded-corner frames and decorative elements. Useobject-fit: containon<img>tags orbackground-size: containfor background images. - For text inside GUI blocks, use
position: relativeon the container with the GUI block as an<img>, then overlay text withposition: absoluteand percentage-based insets (e.g.,top: 10%; left: 8%; right: 8%; bottom: 12%). This keeps text within the visible rounded-rectangle area. - Never leave GUI blocks empty in side-by-side layouts. Always place a photo, text, or branded content inside the frame.
- Alternate color placement (left vs. right) across consecutive split-panel slides for visual variety.
- Use different brand colors for each split-panel slide rather than repeating the same color. Rotate through the full palette (e.g., cyan, navy, yellow, orange).
- Set
min-height: 720pxon the colored panel div to ensure it fills the full slide height.
- Use Playwright with a 1280x720 viewport and
device_scale_factor=2for high-resolution captures. - Wait for
networkidleplus a short delay (500ms) to ensure images and fonts load before screenshotting. - Compile screenshots into PDF using
fpdf2with landscape orientation.
See the drupal-brand-skill repository for an example 88-slide Drupal pitch deck PDF generated using these techniques.
MIT