A one-slide presentation embedding a media payload (audio WAV, audio MPEG, or video MP4) via SlideShapes.add_movie with the appropriate mime_type.
Library verdicts: python-pptx: — pptxjs: —
| Feature id | pptx/media-embed--mp4 |
|---|---|
| Format | pptx |
| Category | embedded-parts |
| Family | pptx/media-embed |
| Axis values | media=mp4 |
| Spec | ecma-376-5-part-1 § 19.3.1.32 p:pic |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
media-part-content-type-mp4[Content_Types].xml | equal = video/mp4A <Default> content-type mapping must register the media file extension. | //ct:Default[@Extension='mp4']/@ContentType | — | — |
slide-media-relationship-mp4ppt/slides/_rels/slide1.xml.rels | match = .*/media/media1\.mp4The slide must carry a media (raw-payload) relationship — emitted under the schemas.microsoft.com/office/2007 namespace — pointing at /ppt/media/media1.<ext>. | //r:Relationship[@Type='http://schemas.microsoft.com/office/2007/relationships/media']/@Target | — | — |
slide-playback-relationship-mp4ppt/slides/_rels/slide1.xml.rels | existThe slide must declare a /video playback relationship. python-pptx emits this rel-type for both audio and video payloads; the <a:audioFile> vs <a:videoFile> shape child is what distinguishes the two. | //r:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/video']/@Target | — | — |
slide-media-shape-child-mp4ppt/slides/slide1.xml | existThe slide's <p:pic> for the media shape must carry an <a:audioFile> (audio) or <a:videoFile> (video) child matching the declared media kind. | //p:pic/p:nvPicPr/p:nvPr/a:videoFile | — | — |
No render assertions declared.
scripts/gen_media_embed.py
#!/usr/bin/env python3
"""Generate a ``fixtures/pptx/media-embed--<media>.pptx`` fixture.
Parameterised generator for the ``pptx/media-embed`` family. One invocation
embeds a single media payload on slide 1 via ``SlideShapes.add_movie`` with
the appropriate ``mime_type``. python-pptx dispatches on the MIME prefix:
``audio/*`` -> ``<a:audioFile>``; everything else -> ``<a:videoFile>``.
Media payloads are synthesised as stub bytes — real WAV/MP3/MP4 binaries
aren't needed for XML-shape assertions. python-pptx does not validate the
blob (aside from length), so a short hand-built byte sequence suffices.
"""
from __future__ import annotations
import argparse
import datetime as _dt
import io
from pathlib import Path
from pptx import Presentation
from pptx.util import Inches
_REPO_ROOT = Path(__file__).resolve().parent.parent
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)
# Minimal 1x1 RGBA PNG — reused as the poster-frame image required by
# add_movie. See corpus scripts/gen_image_on_slide.py for the decoding.
_POSTER_PNG = bytes.fromhex(
"89504E470D0A1A0A"
"0000000D49484452000000010000000108060000001F15C489"
"0000000D49444154789C63000100000500010D0A2DB4"
"0000000049454E44AE426082"
)
# Minimal WAV: RIFF header + fmt chunk + 2-byte data. 44-byte PCM-ish
# skeleton that passes python-pptx's zero-interrogation handling.
_STUB_WAV = bytes.fromhex(
"52494646" # "RIFF"
"24000000" # chunk size = 36
"57415645" # "WAVE"
"666D7420" # "fmt "
"10000000" # fmt chunk size = 16
"01000100" # PCM, 1 channel
"44AC0000" # 44100 Hz
"88580100" # byte rate
"02001000" # block align / bits-per-sample
"64617461" # "data"
"00000000" # data size = 0
)
# Opaque payloads for MP3 / MP4 — python-pptx does not parse the bytes.
_STUB_MP3 = b"ID3\x04\x00\x00\x00\x00\x00\x00" + b"\x00" * 64
_STUB_MP4 = (
b"\x00\x00\x00\x20ftypisom\x00\x00\x02\x00"
b"isomiso2avc1mp41"
+ b"\x00" * 32
)
_STUB = {
"wav": _STUB_WAV,
"mp3": _STUB_MP3,
"mp4": _STUB_MP4,
}
def _write(kind: str, ext: str, mime_type: str, out: Path) -> None:
payload = _STUB[ext]
prs = Presentation()
# slide_layouts[5] = "Title Only" layout — keeps the slide minimal
# so the media shape stands alone in the assertions.
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.shapes.add_movie(
io.BytesIO(payload),
Inches(1), Inches(1), Inches(2), Inches(2),
poster_frame_image=io.BytesIO(_POSTER_PNG),
mime_type=mime_type,
)
out.parent.mkdir(parents=True, exist_ok=True)
prs.save(out, zip_date_time=_REPRODUCIBLE_DT)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--kind", required=True, choices=("audio", "video"))
parser.add_argument("--ext", required=True)
parser.add_argument("--mime-type", required=True)
parser.add_argument("--out", required=True)
ns = parser.parse_args()
out = Path(ns.out)
if not out.is_absolute():
out = _REPO_ROOT / out
_write(ns.kind, ns.ext, ns.mime_type, out)
print(out)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "pptx/media-embed--mp4",
"kind": "literal",
"title": "Embedded media — audio / video (parameterised)",
"format": "pptx",
"category": "embedded-parts",
"roles": [
"authoring"
],
"summary": "A one-slide presentation embedding a media payload (audio WAV, audio MPEG, or video MP4) via SlideShapes.add_movie with the appropriate mime_type.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "19.3.1.32",
"element": "p:pic",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/PresentationML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/pml.xsd",
"notes": "PresentationML embeds media (audio/video) through a <p:pic> shape whose non-visual properties carry an <a:audioFile> (audio) or <a:videoFile> (video) child. The media binary lives in /ppt/media/mediaN.<ext>, wired through two relationships on the slide: (1) a media relationship under the schemas.microsoft.com/office/2007/relationships/media namespace — a Microsoft extension, not the officeDocument/… namespace, matching what PowerPoint writes in practice; and (2) an officeDocument/…/video or /audio playback relationship. python-pptx always emits the /video playback rel for both audio and video payloads (documented quirk — the a:videoFile/a:audioFile shape child is the discriminator, not the playback rel type). This family pins the three media MIME types that Word-family tools handle natively: audio/wav, audio/mpeg, video/mp4."
},
"fixtures": {
"machine": "pptx/media-embed--mp4"
},
"generator": {
"python": "scripts/gen_media_embed.py",
"arg_template": "--kind {media.kind} --ext {media.ext} --mime-type {media.mime_type} --out fixtures/pptx/media-embed--{media.id}.pptx"
},
"_expansion": {
"parent_id": "pptx/media-embed",
"bindings": {
"media": "mp4"
}
},
"assertions": [
{
"id": "media-part-content-type-mp4",
"part": "[Content_Types].xml",
"namespaces": {
"ct": "http://schemas.openxmlformats.org/package/2006/content-types"
},
"xpath": "//ct:Default[@Extension='mp4']/@ContentType",
"must": "equal",
"value": "video/mp4",
"description": "A <Default> content-type mapping must register the media file extension."
},
{
"id": "slide-media-relationship-mp4",
"part": "ppt/slides/_rels/slide1.xml.rels",
"namespaces": {
"r": "http://schemas.openxmlformats.org/package/2006/relationships"
},
"xpath": "//r:Relationship[@Type='http://schemas.microsoft.com/office/2007/relationships/media']/@Target",
"must": "match",
"value": ".*/media/media1\\.mp4",
"description": "The slide must carry a media (raw-payload) relationship — emitted under the schemas.microsoft.com/office/2007 namespace — pointing at /ppt/media/media1.<ext>."
},
{
"id": "slide-playback-relationship-mp4",
"part": "ppt/slides/_rels/slide1.xml.rels",
"namespaces": {
"r": "http://schemas.openxmlformats.org/package/2006/relationships"
},
"xpath": "//r:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/video']/@Target",
"must": "exist",
"description": "The slide must declare a /video playback relationship. python-pptx emits this rel-type for both audio and video payloads; the <a:audioFile> vs <a:videoFile> shape child is what distinguishes the two."
},
{
"id": "slide-media-shape-child-mp4",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"a": "http://schemas.openxmlformats.org/drawingml/2006/main",
"p": "http://schemas.openxmlformats.org/presentationml/2006/main"
},
"xpath": "//p:pic/p:nvPicPr/p:nvPr/a:videoFile",
"must": "exist",
"description": "The slide's <p:pic> for the media shape must carry an <a:audioFile> (audio) or <a:videoFile> (video) child matching the declared media kind."
}
]
}
No rendered reference is available for this case.