Add a video to a slide in a presentation

Video in PresentationML follows the same package pattern as audio: a slide contains markup for a clickable visual object, and relationships connect that object to media data in the package.

Package shape

A slide can reference a video file from non-visual picture properties:

<p:pic>
  <p:nvPicPr>
    <p:cNvPr id="7" name="video">
      <a:hlinkClick r:id="" action="ppaction://media"/>
    </p:cNvPr>
    <p:nvPr>
      <a:videoFile r:link="rVideo1"/>
    </p:nvPr>
  </p:nvPicPr>
</p:pic>

The slide part relationship item resolves rVideo1 to the stored media data. A complete PowerPoint-compatible video insertion normally also includes a preview image, media reference relationship, timing data, and shape geometry.

videoFile is defined inside the non-visual properties of the picture or shape. The visible object sits on the slide like any other drawing object, while playback is described in the slide timing tree. The non-visual drawing ID is used by that timing data to refer back to the media object.

The video timing schema uses CT_TLMediaNodeVideo, whose common media node child is required and whose fullScrn attribute defaults to false.

The full insertion shape normally includes:

  • p:cNvPr, including an a:hlinkClick action of ppaction://media,
  • p:cNvPicPr and picture locks,
  • p:nvPr with a:videoFile,
  • a video relationship to the media data part,
  • a media reference relationship,
  • an image part plus a:blip for the preview frame,
  • shape properties such as a:off, a:stretch, and a:fillRect.

Rust workflow

Create an MP4 media data part, add video and media reference relationships from the target slide, and insert the video picture markup:

#![allow(unused)]
fn main() {
pub fn add_video_to_slide(
  path: &Path,
  slide_index: usize,
  video_bytes: &[u8],
) -> Result<(Vec<u8>, String, String), Box<dyn std::error::Error>> {
  let mut document = PresentationDocument::new_from_file_with_settings(path, lazy_settings())?;
  let presentation_part = document.presentation_part()?;
  let Some(slide_part) = presentation_part.slide_parts(&document).nth(slide_index) else {
    return Err("slide index out of range".into());
  };

  let media_part = document.create_media_data_part_by_type(MediaDataPartType::Mp4)?;
  media_part.set_data(&mut document, video_bytes.to_vec())?;
  let video_relationship_id =
    slide_part.add_video_reference_relationship(&mut document, &media_part)?;
  let media_relationship_id =
    slide_part.add_media_reference_relationship(&mut document, &media_part)?;
  let slide_xml = slide_part.data_as_str(&document)?.unwrap_or_default();
  let updated_xml =
    insert_video_picture(slide_xml, &video_relationship_id, &media_relationship_id)?;
  slide_part.set_data(&mut document, updated_xml.into_bytes())?;

  let mut buffer = Cursor::new(Vec::new());
  document.save(&mut buffer)?;
  Ok((
    buffer.into_inner(),
    video_relationship_id,
    media_relationship_id,
  ))
}
}

For production presentations, add a preview image and timing tree entries if the target application requires them. The example above covers the package media part, video relationship, media relationship, and slide-level a:videoFile markup.