scanny / python-pptx

Create Open XML PowerPoint documents in Python
MIT License
2.39k stars 518 forks source link

How to access and create media objects #977

Closed hros closed 4 months ago

hros commented 4 months ago

I want to add audio files to slides by inserting an audio object that plays an audio file image How can this be accomplished using the python-pptx package?

I added an audio object to a slide (named "Recorded Sound") and tried to find it from the slide object, but it wasn't part of the shapes list.

How can access existing audio objects, and add additional objects to a slide?

scanny commented 4 months ago

Hi believe some folks have had success using shapes.add_movie() for that. Have a search around on "python-pptx add mp3" or similar search terms and I think you'll find some ideas.

hros commented 4 months ago

thanks!

even if shapes.add_movie can add a new mp3 audio object (as described in this post), I still don't see how to:

  1. find an existing audio object from the slide object, and either delete it or update the link to the audio file
  2. set properties of audio object, enable: hide during playback and start automatically
MartinPacker commented 4 months ago

Yes, I've done it that way: Lines 2889 to 2896 in md2pptx show an example of doing that. Nowhere in md2pptx do I do anything different for media that turns out to be audio compared to video. (Yes, I parse HTML / markdown input that points at them separately, but that's not relevant.)

hros commented 4 months ago

Yes, I've done it that way: Lines 2889 to 2896 in md2pptx show an example of doing that. Nowhere in md2pptx do I do anything different for media that turns out to be audio compared to video. (Yes, I parse HTML / markdown input that points at them separately, but that's not relevant.)

thanks, but unfortunately the example can only create new media objects, while I would like to access and update existing media objects

hros commented 4 months ago

is there a "raw XML" API that lets me access everything in the slide, albeit in a non-friendly manner?

scanny commented 4 months ago

It's an implementation detail, so use at your own risk, but I think what you're talking about is what we call the "oxml" layer. Like Slide has ._element which is the lxml.etree._Element object for the <p:sld> element that is the root of the XML tree for the slide.

Using these is how all the proxy classes (e.g. Slide) operate on the XML and it's not uncommon for folks to do the same when they need to extend the behaviors somehow.

The basic strategy is to get as close to your target spot in the XML using the proxies, so maybe slide.shapes._grpSp and then use lxml.etree._Element methods from there.

hros commented 4 months ago

thanks! I inserted a Media/Audio object in one of the slides, and I parsed it with python-pptx, but I couldn't find it How do you suggest I traverse all the elements of the slide? Do I start with slide._element and work my way down the tree with .getchildren()?

By the way, I unzipped the presentation (the pptx file) and viewed the XML tree of the slide (slides/slide2.xml) which contains a blank slide with an audio element with an online XML viewer. The element is named "Recorded Sound." How would you access this element using either slide._element or slide.shapes.grpSp? I found that slide._element.getchildren()[0].getchildren()[0].getchildren()[2].getchildren()[0].getchildren()[0].name == 'Recorded Sound', is that the proper way to do it? How would I find it easily in general when there are other elements in the slide? Can you share a link to an example that changes the properties of the elements using the XML API?

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sld
  xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
  xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:cSld>
    <p:spTree>
      <p:nvGrpSpPr>
        <p:cNvPr id="1" name=""/>
        <p:cNvGrpSpPr/>
        <p:nvPr/>
      </p:nvGrpSpPr>
      <p:grpSpPr>
        <a:xfrm>
          <a:off x="0" y="0"/>
          <a:ext cx="0" cy="0"/>
          <a:chOff x="0" y="0"/>
          <a:chExt cx="0" cy="0"/>
        </a:xfrm>
      </p:grpSpPr>
      <p:pic>
        <p:nvPicPr>
          <p:cNvPr id="2" name="Recorded Sound">
            <a:hlinkClick r:id="" action="ppaction://media"/>
            <a:extLst>
              <a:ext uri="{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}">
                <a16:creationId
                  xmlns:a16="http://schemas.microsoft.com/office/drawing/2014/main" id="{42CC0928-1512-980B-37E2-51D82FC53C63}"/>
                </a:ext>
              </a:extLst>
            </p:cNvPr>
            <p:cNvPicPr>
              <a:picLocks noChangeAspect="1"/>
            </p:cNvPicPr>
            <p:nvPr>
              <a:audioFile r:link="rId2"/>
              <p:extLst>
                <p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">
                  <p14:media
                    xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" r:embed="rId1"/>
                  </p:ext>
                </p:extLst>
              </p:nvPr>
            </p:nvPicPr>
            <p:blipFill>
              <a:blip r:embed="rId4"/>
              <a:stretch>
                <a:fillRect/>
              </a:stretch>
            </p:blipFill>
            <p:spPr>
              <a:xfrm>
                <a:off x="5892800" y="3225800"/>
                <a:ext cx="406400" cy="406400"/>
              </a:xfrm>
              <a:prstGeom prst="rect">
                <a:avLst/>
              </a:prstGeom>
            </p:spPr>
          </p:pic>
        </p:spTree>
        <p:extLst>
          <p:ext uri="{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}">
            <p14:creationId
              xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="472980364"/>
            </p:ext>
          </p:extLst>
        </p:cSld>
        <p:clrMapOvr>
          <a:masterClrMapping/>
        </p:clrMapOvr>
        <p:timing>
          <p:tnLst>
            <p:par>
              <p:cTn id="1" dur="indefinite" restart="never" nodeType="tmRoot">
                <p:childTnLst>
                  <p:seq concurrent="1" nextAc="seek">
                    <p:cTn id="2" dur="indefinite" nodeType="mainSeq">
                      <p:childTnLst>
                        <p:par>
                          <p:cTn id="3" fill="hold">
                            <p:stCondLst>
                              <p:cond delay="indefinite"/>
                            </p:stCondLst>
                            <p:childTnLst>
                              <p:par>
                                <p:cTn id="4" fill="hold">
                                  <p:stCondLst>
                                    <p:cond delay="0"/>
                                  </p:stCondLst>
                                  <p:childTnLst>
                                    <p:par>
                                      <p:cTn id="5" presetID="1" presetClass="mediacall" presetSubtype="0" fill="hold" nodeType="clickEffect">
                                        <p:stCondLst>
                                          <p:cond delay="0"/>
                                        </p:stCondLst>
                                        <p:childTnLst>
                                          <p:cmd type="call" cmd="playFrom(0.0)">
                                            <p:cBhvr>
                                              <p:cTn id="6" dur="3797" fill="hold"/>
                                              <p:tgtEl>
                                                <p:spTgt spid="2"/>
                                              </p:tgtEl>
                                            </p:cBhvr>
                                          </p:cmd>
                                        </p:childTnLst>
                                      </p:cTn>
                                    </p:par>
                                  </p:childTnLst>
                                </p:cTn>
                              </p:par>
                            </p:childTnLst>
                          </p:cTn>
                        </p:par>
                      </p:childTnLst>
                    </p:cTn>
                    <p:prevCondLst>
                      <p:cond evt="onPrev" delay="0">
                        <p:tgtEl>
                          <p:sldTgt/>
                        </p:tgtEl>
                      </p:cond>
                    </p:prevCondLst>
                    <p:nextCondLst>
                      <p:cond evt="onNext" delay="0">
                        <p:tgtEl>
                          <p:sldTgt/>
                        </p:tgtEl>
                      </p:cond>
                    </p:nextCondLst>
                  </p:seq>
                  <p:audio>
                    <p:cMediaNode vol="80000">
                      <p:cTn id="7" fill="hold" display="0">
                        <p:stCondLst>
                          <p:cond delay="indefinite"/>
                        </p:stCondLst>
                        <p:endCondLst>
                          <p:cond evt="onStopAudio" delay="0">
                            <p:tgtEl>
                              <p:sldTgt/>
                            </p:tgtEl>
                          </p:cond>
                        </p:endCondLst>
                      </p:cTn>
                      <p:tgtEl>
                        <p:spTgt spid="2"/>
                      </p:tgtEl>
                    </p:cMediaNode>
                  </p:audio>
                </p:childTnLst>
              </p:cTn>
            </p:par>
          </p:tnLst>
        </p:timing>
      </p:sld>
hros commented 2 months ago

Using add_movie with the "mp3" mime adds the audio objects, but they do not play automatically I use the following VBA macro on the generated presentation:

Sub playAllAudio()
    Dim osld As Slide
    Dim oshp As Shape
    For Each osld In ActivePresentation.Slides
        For Each oshp In osld.Shapes
            If oshp.Type = msoMedia Then
                oshp.AnimationSettings.PlaySettings.PlayOnEntry = msoTrue
            End If
        Next oshp
    Next osld
End Sub

Currently, I have to follow up the python-pptx script with "manual labor" in PowerPoint.

Is there a way to do the same with python-pptx?

Additionally, I have to set the transition of each slide with the property: "After = 00:00.00". It is relatively easy to do directly from Powerpoint (selecting all slides), but is there a way to do that from python-pptx?