Skip to content

fix(pptx): conversion aborts when a slide's title placeholder holds a picture #78

Description

@lsimons

What happens

Converting an old (2013-era) .pptx deck raises AttributeError and the whole file is dropped:

ERROR   mdd.converters.pptx: error converting <REDACTED>/<REDACTED>.pptx
Traceback (most recent call last):
  File "src/mdd/converters/pptx.py", line 32, in convert
    convert_pptx(src, dest)
  File "src/mdd/convert/pptx.py", line 421, in convert_pptx
    _render_slide(
    ...
  File "src/mdd/convert/pptx.py", line 304, in _render_slide
    str(title_shape.text).strip()
        ^^^^^^^^^^^^^^^^
AttributeError: 'PlaceholderPicture' object has no attribute 'text'

Why

_render_slide assumes slide.shapes.title is a text placeholder:

https://github.com/schubergphilis/mdd/blob/main/src/mdd/convert/pptx.py#L302-L307

title_shape: Any = slide.shapes.title
slide_title: str = (
    str(title_shape.text).strip()
    if title_shape is not None
    else ""
)

python-pptx's shapes.title returns whatever placeholder sits at idx == 0, not
necessarily a title text placeholder. In this deck that slot holds a picture
placeholder, so python-pptx hands back a PlaceholderPicture, which has no
.text — only text-frame shapes do. Older decks (and decks built from custom
layouts) hit this regularly; the layout puts a logo or screenshot in the idx-0
placeholder.

Note this is not only about the title: a PlaceholderPicture title also means
the picture is silently lost today even if the AttributeError were swallowed,
because _render_shape skips any shape matching title_shape before it gets a
chance to be extracted as an image.

Expected

A picture in the title placeholder should not abort conversion of the deck. The
slide should fall back to the Slide N heading (the existing slide_title or f"Slide {slide_num}" path), and the picture should still be extracted as an
attachment like any other picture.

Suggested fix

Guard the title read on the shape actually having a text frame, and only treat
the title shape as "already rendered" when it contributed the heading:

title_shape: Any = slide.shapes.title
if title_shape is not None and getattr(title_shape, "has_text_frame", False):
    slide_title = str(title_shape.text).strip()
else:
    title_shape = None   # not a text title; let the shape loop render it normally
    slide_title = ""

Setting title_shape = None is what keeps the picture: _render_shape /
_render_text_frame both skip via _same_shape(shape, title_shape), and
_same_shape already tolerates None.

Test coverage

Add a fixture deck whose slide layout places a picture in the idx == 0
placeholder and assert:

  1. conversion succeeds,
  2. the heading is ## Slide 1,
  3. the picture is written to the attachments dir and linked.

Environment

  • mdd main (at time of report: 3f7f017)
  • python-pptx as pinned in uv.lock
  • source deck: a 2013 .pptx synced from SharePoint (path and deck title redacted)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions