• Tech Dev NotesTech Dev Notes
Apps
  • App lookup
  • App compare
Market movement
  • App charts
  • App rankings
Visual proof
  • App screens
  • App listing screenshots
  • App icons
Build intelligence
  • App tech stacks
  • Tool releases
  • Developers
More
  • X feature flags
  • Grokipedia
  • Blog
  • Follow on X
Skip to content
All content/ filesChangelog

grok-build/latest/content · 0.2.69

skills/pptx/scripts/thumbnail.py

Skill·8.5 KB·190 lines

content/

  • .

    • README.md
  • docs/user-guide

    • 01-getting-started.md
    • 02-authentication.md
    • 03-keyboard-shortcuts.md
    • 04-slash-commands.md
    • 05-configuration.md
    • 06-theming.md
    • 07-mcp-servers.md
    • 08-skills.md
    • 09-plugins.md
    • 10-hooks.md
    • 11-custom-models.md
    • 12-project-rules.md
    • 13-memory.md
    • 14-headless-mode.md
    • 15-agent-mode.md
    • 16-subagents.md
    • 17-sessions.md
    • 18-sandbox.md
    • 19-plan-mode.md
    • 20-background-tasks.md
    • 21-terminal-support.md
    • 22-permissions-and-safety.md
  • skills/check-work

    • SKILL.md
  • skills/code-review

    • SKILL.md
  • skills/create-skill

    • SKILL.md
  • skills/docx

    • SKILL.md
  • skills/docx/scripts

    • __init__.py
    • accept_changes.py
    • comment.py
  • skills/docx/scripts/office

    • pack.py
    • soffice.py
    • unpack.py
    • validate.py
  • skills/docx/scripts/office/helpers

    • __init__.py
    • merge_runs.py
    • simplify_redlines.py
  • skills/docx/scripts/office/schemas/ecma/fouth-edition

    • opc-contentTypes.xsd
    • opc-coreProperties.xsd
    • opc-digSig.xsd
    • opc-relationships.xsd
  • skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016

    • dml-chart.xsd
    • dml-chartDrawing.xsd
    • dml-diagram.xsd
    • dml-lockedCanvas.xsd
    • dml-main.xsd
    • dml-picture.xsd
    • dml-spreadsheetDrawing.xsd
    • dml-wordprocessingDrawing.xsd
    • pml.xsd
    • shared-additionalCharacteristics.xsd
    • shared-bibliography.xsd
    • shared-commonSimpleTypes.xsd
    • shared-customXmlDataProperties.xsd
    • shared-customXmlSchemaProperties.xsd
    • shared-documentPropertiesCustom.xsd
    • shared-documentPropertiesExtended.xsd
    • shared-documentPropertiesVariantTypes.xsd
    • shared-math.xsd
    • shared-relationshipReference.xsd
    • sml.xsd
    • vml-main.xsd
    • vml-officeDrawing.xsd
    • vml-presentationDrawing.xsd
    • vml-spreadsheetDrawing.xsd
    • vml-wordprocessingDrawing.xsd
    • wml.xsd
    • xml.xsd
  • skills/docx/scripts/office/schemas/mce

    • mc.xsd
  • skills/docx/scripts/office/schemas/microsoft

    • wml-2010.xsd
    • wml-2012.xsd
    • wml-2018.xsd
    • wml-cex-2018.xsd
    • wml-cid-2016.xsd
    • wml-sdtdatahash-2020.xsd
    • wml-symex-2015.xsd
  • skills/docx/scripts/office/validators

    • __init__.py
    • base.py
    • docx.py
    • pptx.py
    • redlining.py
  • skills/docx/scripts/templates

    • comments.xml
    • commentsExtended.xml
    • commentsExtensible.xml
    • commentsIds.xml
    • people.xml
  • skills/help

    • SKILL.md
  • skills/imagine

    • SKILL.md
  • skills/pptx

    • editing.md
    • pptxgenjs.md
    • SKILL.md
  • skills/pptx/scripts

    • __init__.py
    • add_slide.py
    • clean.py
    • thumbnail.py
  • skills/pptx/scripts/office

    • pack.py
    • soffice.py
    • unpack.py
    • validate.py
  • skills/pptx/scripts/office/helpers

    • __init__.py
    • merge_runs.py
    • simplify_redlines.py
  • skills/pptx/scripts/office/schemas/ecma/fouth-edition

    • opc-contentTypes.xsd
    • opc-coreProperties.xsd
    • opc-digSig.xsd
    • opc-relationships.xsd
  • skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016

    • dml-chart.xsd
    • dml-chartDrawing.xsd
    • dml-diagram.xsd
    • dml-lockedCanvas.xsd
    • dml-main.xsd
    • dml-picture.xsd
    • dml-spreadsheetDrawing.xsd
    • dml-wordprocessingDrawing.xsd
    • pml.xsd
    • shared-additionalCharacteristics.xsd
    • shared-bibliography.xsd
    • shared-commonSimpleTypes.xsd
    • shared-customXmlDataProperties.xsd
    • shared-customXmlSchemaProperties.xsd
    • shared-documentPropertiesCustom.xsd
    • shared-documentPropertiesExtended.xsd
    • shared-documentPropertiesVariantTypes.xsd
    • shared-math.xsd
    • shared-relationshipReference.xsd
    • sml.xsd
    • vml-main.xsd
    • vml-officeDrawing.xsd
    • vml-presentationDrawing.xsd
    • vml-spreadsheetDrawing.xsd
    • vml-wordprocessingDrawing.xsd
    • wml.xsd
    • xml.xsd
  • skills/pptx/scripts/office/schemas/mce

    • mc.xsd
  • skills/pptx/scripts/office/schemas/microsoft

    • wml-2010.xsd
    • wml-2012.xsd
    • wml-2018.xsd
    • wml-cex-2018.xsd
    • wml-cid-2016.xsd
    • wml-sdtdatahash-2020.xsd
    • wml-symex-2015.xsd
  • skills/pptx/scripts/office/validators

    • __init__.py
    • base.py
    • docx.py
    • pptx.py
    • redlining.py
  • skills/xlsx/scripts

    • recalc.py
  • skills/xlsx/scripts/office

    • pack.py
    • soffice.py
    • unpack.py
    • validate.py
  • skills/xlsx/scripts/office/helpers

    • __init__.py
    • merge_runs.py
    • simplify_redlines.py
  • skills/xlsx/scripts/office/schemas/ecma/fouth-edition

    • opc-contentTypes.xsd
  • skills/xlsx/scripts/office/validators

    • __init__.py
    • base.py
    • docx.py
    • pptx.py
    • redlining.py
"""Create thumbnail grids from PowerPoint presentation slides.

Creates a grid layout of slide thumbnails for quick visual analysis.
Labels each thumbnail with its XML filename (e.g., slide1.xml).
Hidden slides are shown with a placeholder pattern.

Usage:
    python thumbnail.py input.pptx [output_prefix] [--cols N]

Examples:
    python thumbnail.py presentation.pptx
    # Creates: thumbnails.jpg

    python thumbnail.py template.pptx grid --cols 4
    # Creates: grid.jpg (or grid-1.jpg, grid-2.jpg for large decks)
"""

import argparse
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path

import defusedxml.minidom
from office.soffice import get_soffice_env
from PIL import Image, ImageDraw, ImageFont

THUMBNAIL_WIDTH = 300
CONVERSION_DPI = 100
MAX_COLS = 6
DEFAULT_COLS = 3
JPEG_QUALITY = 95
GRID_PADDING = 20
BORDER_WIDTH = 2
FONT_SIZE_RATIO = 0.10
LABEL_PADDING_RATIO = 0.4


def main():
    parser = argparse.ArgumentParser(description="Create thumbnail grids from PowerPoint slides.")
    parser.add_argument("input", help="Input PowerPoint file (.pptx)")
    parser.add_argument(
        "output_prefix",
        nargs="?",
        default="thumbnails",
        help="Output prefix for image files (default: thumbnails)",
    )
    parser.add_argument(
        "--cols",
        type=int,
        default=DEFAULT_COLS,
        help=f"Number of columns (default: {DEFAULT_COLS}, max: {MAX_COLS})",
    )

    args = parser.parse_args()

    cols = min(args.cols, MAX_COLS)
    if args.cols > MAX_COLS:
        print(f"Warning: Columns limited to {MAX_COLS}")

    input_path = Path(args.input)
    if not input_path.exists() or input_path.suffix.lower() != ".pptx":
        print(f"Error: Invalid PowerPoint file: {args.input}", file=sys.stderr)
        sys.exit(1)

    output_path = Path(f"{args.output_prefix}.jpg")

    try:
        slide_info = get_slide_info(input_path)

        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            visible_images = convert_to_images(input_path, temp_path)

            if not visible_images and not any(s["hidden"] for s in slide_info):
                print("Error: No slides found", file=sys.stderr)
                sys.exit(1)

            slides = build_slide_list(slide_info, visible_images, temp_path)

            grid_files = create_grids(slides, cols, THUMBNAIL_WIDTH, output_path)

            print(f"Created {len(grid_files)} grid(s):")
            for grid_file in grid_files:
                print(f"  {grid_file}")

    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


def get_slide_info(pptx_path: Path) -> list[dict]:
    with zipfile.ZipFile(pptx_path, "r") as zf:
        rels_content = zf.read("ppt/_rels/presentation.xml.rels").decode("utf-8")
        rels_dom = defusedxml.minidom.parseString(rels_content)

        rid_to_slide = {}
        for rel in rels_dom.getElementsByTagName("Relationship"):
            rid = rel.getAttribute("Id")
            target = rel.getAttribute("Target")
            rel_type = rel.getAttribute("Type")
            if "slide" in rel_type and target.startswith("slides/"):
                rid_to_slide[rid] = target.replace("slides/", "")

        pres_content = zf.read("ppt/presentation.xml").decode("utf-8")
        pres_dom = defusedxml.minidom.parseString(pres_content)

        slides = []
        for sld_id in pres_dom.getElementsByTagName("p:sldId"):
            rid = sld_id.getAttribute("r:id")
            if rid in rid_to_slide:
                hidden = sld_id.getAttribute("show") == "0"
                slides.append({"name": rid_to_slide[rid], "hidden": hidden})

        return slides


def build_slide_list(
    slide_info: list[dict],
    visible_images: list[Path],
    temp_dir: Path,
) -> list[tuple[Path, str]]:
    if visible_images:
        with Image.open(visible_images[0]) as img:
            placeholder_size = img.size
    else:
        placeholder_size = (1920, 1080)

    slides = []
    visible_idx = 0

    for info in slide_info:
        if info["hidden"]:
            placeholder_path = temp_dir / f"hidden-{info['name']}.jpg"
            placeholder_img = create_hidden_placeholder(placeholder_size)
            placeholder_img.save(placeholder_path, "JPEG")
            slides.append((placeholder_path, f"{info['name']} (hidden)"))
        else:
            if visible_idx < len(visible_images):
                slides.append((visible_images[visible_idx], info["name"]))
                visible_idx += 1

    return slides


def create_hidden_placeholder(size: tuple[int, int]) -> Image.Image:
    img = Image.new("RGB", size, color="#F0F0F0")
    draw = ImageDraw.Draw(img)
    line_width = max(5, min(size) // 100)
    draw.line([(0, 0), size], fill="#CCCCCC", width=line_width)
    draw.line([(size[0], 0), (0, size[1])], fill="#CCCCCC", width=line_width)
    return img


def convert_to_images(pptx_path: Path, temp_dir: Path) -> list[Path]:
    pdf_path = temp_dir / f"{pptx_path.stem}.pdf"

    result = subprocess.run(
        [
            "soffice",
            "--headless",
            "--convert-to",
            "pdf",
            "--outdir",
            str(temp_dir),
            str(pptx_path),
        ],
        capture_output=True,
        text=True,
        env=get_soffice_env(),
    )
    if result.returncode != 0 or not pdf_path.exists():
        raise RuntimeError("PDF conversion failed")

    result = subprocess.run(
        [
            "pdftoppm",
            "-jpeg",
            "-r",
            str(CONVERSION_DPI),
            str(pdf_path),
            str(temp_dir / "slide"),
        ],
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        raise RuntimeError("Image conversion f
…
Previousskills/pptx/scripts/office/validators/redlining.pyNextskills/xlsx/scripts/office/helpers/__init__.py

© 2026 Tech Dev Notes

RSSAboutAPIPrivacyTermsSitemap@techdevnotes