python-pptx icon indicating copy to clipboard operation
python-pptx copied to clipboard

docs: add_video() can be used to add audio to a slide

Open embie27 opened this issue 6 years ago • 17 comments

A feature for adding audio to slides would be a great benefit!

embie27 avatar May 23 '19 10:05 embie27

After a bit of research I found out that the mechanism for audio and video is the same in powerpoint. So the add_video() method can be used to add audio. Maybe this should be documented.

embie27 avatar May 25 '19 12:05 embie27

So @embie27, in the interest of documentation, what are your inputs, what code do you use and what do you get as a result?

scanny avatar Jun 03 '19 17:06 scanny

I used the following line of code to add test.mp3 which is in the same directory as the python script to the first slide: prs.slides[0].shapes.add_movie("test.mp3", 0, 0, 1000, 1000) This worked very well. When starting the presentation the file is played back. There is nothing visible on the slide that indicates the sound because I did not add a picture in add_movie.

The relevant part in the slide1.xml looks very similar to that, created when adding a movie file:

  <p:nvPicPr>
    <p:cNvPr id="42" name="Boehlke_Jenny.mp3">
      <a:hlinkClick r:id="" action="ppaction://media"/>
    </p:cNvPr>
    <p:cNvPicPr>
      <a:picLocks noChangeAspect="1"/>
    </p:cNvPicPr>
    <p:nvPr>
      <a:videoFile r:link="rId5"/>
      <p:extLst>
        <p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">
          <p14:media r:embed="rId4"/>
        </p:ext>
      </p:extLst>
    </p:nvPr>
  </p:nvPicPr>
  <p:blipFill>
    <a:blip r:embed="rId6"/>
    <a:stretch>
      <a:fillRect/>
    </a:stretch>
  </p:blipFill>
  <p:spPr>
    <a:xfrm>
      <a:off x="0" y="0"/>
      <a:ext cx="1000" cy="1000"/>
    </a:xfrm>
    <a:prstGeom prst="rect">
      <a:avLst/>
    </a:prstGeom>
  </p:spPr>
</p:pic>

embie27 avatar Jun 04 '19 13:06 embie27

Actually, adding audio into ppt should turn into a <a:audioFile> tag and a <p:seq> tag tree, but not <a:videoFile>. I use add_movie to add a wav into the slide, and it can't be played automatically. After setting the Audio Style to No Style, it works fine. Here's the difference: python-pptx produced slide1.xml:

<p:nvPr>
    <a:videoFile r:link="rId5"/>
    <p:extLst>
        <p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">
            <p14:media xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" r:embed="rId4"/>
        </p:ext>
    </p:extLst>
</p:nvPr>
<p:childTnLst>
<p:video>
    <p:cMediaNode vol="80000">
        <p:cTn id="2" fill="hold" display="0">
            <p:stCondLst>
                <p:cond delay="indefinite"/>
            </p:stCondLst>
        </p:cTn>
        <p:tgtEl>
            <p:spTgt spid="5"/>
        </p:tgtEl>
    </p:cMediaNode>
</p:video>

MS Office PowerPoint produced slide1.xml:

<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: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="7960" fill="hold"/>
                                                            <p:tgtEl>
                                                                <p:spTgt spid="5"/>
                                                            </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="5"/>
            </p:tgtEl>
        </p:cMediaNode>
    </p:audio>
</p:childTnLst>

Kiritow avatar Jan 27 '21 00:01 Kiritow

This doesn't work for me if I open a file that's already had audio files added in this manner.

I have files orig.pptx, wavefile1.wav, wavefile2.wav, and image.png I open the orig.pptx with python-pptx, insert the first wav file to a slide, and then save to new.pptx Then, I open new.pptx, attempt inserting the second wav file to a slide, and it fails.

`

from pptx import Presentation from pptx.util import Inches prs = Presentation('orig.pptx') slide_num = 7 # inserting to slide 7 slide = prs.slides[slide_num-1] slide.shapes.add_movie('wavefile1.wav', left=Inches(4.17), top=Inches(6.74), width=Inches(1.67), height=Inches(0.76), poster_frame_image='image.png', mime_type='audio/x-wav') <pptx.shapes.picture.Movie object at 0x7fd31f742430> prs.save('new.pptx')`

This works and I have new.pptx with the object inserted on slide 7, albeit a wav inserted as a video.

But subsequent attempts to insert wav files to the newly generated file fail:

`

prs = Presentation('new.pptx') slide_num = 8 # try inserting to slide 8 slide = prs.slides[slide_num-1] slide.shapes.add_movie('wavefile2.wav', left=Inches(4.17), top=Inches(6.74), width=Inches(1.67), height=Inches(0.76), poster_frame_image='image.png', mime_type='audio/x-wav') Traceback (most recent call last): File "", line 1, in File "/usr/local/lib/python3.9/dist-packages/pptx/shapes/shapetree.py", line 524, in add_movie movie_pic = _MoviePicElementCreator.new_movie_pic( File "/usr/local/lib/python3.9/dist-packages/pptx/shapes/shapetree.py", line 901, in new_movie_pic return cls( File "/usr/local/lib/python3.9/dist-packages/pptx/util.py", line 215, in get value = self._fget(obj) File "/usr/local/lib/python3.9/dist-packages/pptx/shapes/shapetree.py", line 921, in _pic self._video_rId, File "/usr/local/lib/python3.9/dist-packages/pptx/shapes/shapetree.py", line 989, in _video_rId return self._video_part_rIds[1] File "/usr/local/lib/python3.9/dist-packages/pptx/util.py", line 215, in get value = self._fget(obj) File "/usr/local/lib/python3.9/dist-packages/pptx/shapes/shapetree.py", line 979, in _video_part_rIds media_rId, video_rId = self._slide_part.get_or_add_video_media_part(self._video) File "/usr/local/lib/python3.9/dist-packages/pptx/parts/slide.py", line 194, in get_or_add_video_media_part media_part = self._package.get_or_add_media_part(video) File "/usr/local/lib/python3.9/dist-packages/pptx/package.py", line 44, in get_or_add_media_part return self._media_parts.get_or_add_media_part(media) File "/usr/local/lib/python3.9/dist-packages/pptx/package.py", line 205, in get_or_add_media_part media_part = self._find_by_sha1(media.sha1) File "/usr/local/lib/python3.9/dist-packages/pptx/package.py", line 218, in _find_by_sha1 if media_part.sha1 == sha1: AttributeError: 'Part' object has no attribute 'sha1'`

slycordinator avatar Nov 13 '21 10:11 slycordinator

I don't know what it looks like if you just add a (eg) mp3 via add_video but would it make sense to have a wrapper method that adds some "press me to play" shape to the audio?

MartinPacker avatar Nov 14 '21 08:11 MartinPacker

Okay, I think I've identified the root cause here:
https://github.com/scanny/python-pptx/blob/71d1ca0b2b3b9178d64cdab565e8503a25a54e0b/pptx/init.py#L51-L60

WAV is not a registered content-type for MediaPart, so when the .pptx file (package) is reloaded, that WAV part is instantiated as Part instead of MediaPart. MediaPart has a .sha1 method (property) but Part doesn't, hence the error you're seeing.

This explains why the problem only happens when the .pptx is reloaded. I expect you can add as many audio files as you want, as long as you start from a file that doesn't have any yet (don't reopen the file to add more).

I believe the following monkey patch will remedy this, if a bit ugly:

from pptx import content_type_to_part_class_map
from pptx.parts.media import MediaPart

content_type_to_part_class_map["audio/x-wav"] = MediaPart

prs = Presentation(...

The movie feature is experimental and using it for audio files is off-label, so building-in support for audio files is something I would regard as a new feature. But hopefully this gives you enough to get the off-label use to work.

scanny avatar Nov 15 '21 19:11 scanny

Btw, it's also possible it will "just work" if you don't specify the MIME type parameter. That's probably worth a try.

scanny avatar Nov 15 '21 19:11 scanny

WAV is not a registered content-type for MediaPart, so when the .pptx file (package) is reloaded, that WAV part is instantiated as Part instead of MediaPart. MediaPart has a .sha1 method (property) but Part doesn't, hence the error you're seeing.

It's not just WAV. No audio files are registered in MediaPart (mp3, m4a, wav, midi, etc).

I got it to work instead using PartFactory.part_type_for.update

`from pptx.opc.package import PartFactory from pptx.parts.media import MediaPart for aud_type in [ 'audio/mp3', 'audio/mp4', 'audio/mid', 'audio/x-wav', 'audio/mpeg' ]: PartFactory.part_type_for.update( { aud_type: MediaPart } )

prs = Presentation(...`

slycordinator avatar Feb 21 '22 07:02 slycordinator

Hey guys,

I have a small side hustle converting powerpoints into narrated videos for small businesses. It involves recording an mp3 file for each slide, adding it to the slide, and exporting as mp4. I made a small script in vba to automate this, and have recently made it in python with python-pptx and put it up online, you can try it out here: ppt-narrator.com. It's still in a buggy alpha stage. You can find the source code on my profile.

I am inserting the audio using the add_movie() method, but unfortunately the audio won't play when I export it to mp4. I've tried adding the code that slycordinator posted, but no joy. Any idea what I can do to fix this?

harryob2 avatar Mar 16 '23 17:03 harryob2

One thing is that inserting the media with that function doesn't add it to the animation list; it's merely inserted into a slide.

Also, how are you exporting it to MP4? I believe that some sites that do conversion of PPTX to MP4 can't handle when a slide contains multiple simultaneous audio sources with the same codec (like two MP3 files playing concurrently). Though I think directly in PowerPoint's MP4 video export feature, it worked okay.

On Fri, Mar 17, 2023, 2:42 AM harryob2 @.***> wrote:

Hey guys,

I have a small side hustle converting powerpoints into narrated videos for small businesses. It involves recording an mp3 file for each slide, adding it to the slide, and exporting as mp4. I made a small script in vba to automate this, and have recently made it in python with python-pptx and put it up online, you can try it out here: ppt-narrator.com. It's still in a buggy alpha stage. You can find the source code on my profile.

I am inserting the audio using the add_movie() method, but unfortunately the audio won't play when I export it to mp4. I've tried adding the code that slycordinator posted, but no joy. Any idea what I can do to fix this?

— Reply to this email directly, view it on GitHub https://github.com/scanny/python-pptx/issues/502#issuecomment-1472431920, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQN7DTNZ5ESC2KB3ADZMGO3W4NGHZANCNFSM4HO5JMFA . You are receiving this because you commented.Message ID: @.***>

slycordinator avatar Mar 16 '23 21:03 slycordinator

I’m just using the ppt app to export to video; automatic mp4 maker is on the list of features to add, but first I need to make it work properly.

If the audio was in the animation list, would it play when converted to video?

harryob2 avatar Mar 17 '23 00:03 harryob2

For media to play, you would need to open the powerpoint file and add it to wherever you like in the animation list.

"add_video" just places a video on a slide at a certain position. And in this case, it can be cooerced into adding audio instead.

slycordinator avatar Mar 17 '23 03:03 slycordinator

Ok cool, I will try and find code to do that automatically and report back

harryob2 avatar Mar 17 '23 12:03 harryob2

OK, I've made some progress. I managed to get the audio to play when the PowerPoint is exported as a video, but only with a very basic PowerPoint. With fancier PowerPoints with lots of images, it doesn't work.

I messed around with the XML data for the slides and noticed that if I just changed one piece of code from id="4" to id="6", and replaced the code between the <p:video></p:video> tags with a different block of code, I could get it to work with the simple PowerPoint files. I found the big block of code from the XML file of a PowerPoint where the audio was added manually in the PowerPoint app.

When I was trying to figure out why it didn't work on bigger, more complex PowerPoints, I compared the XML files from where the audio was added normally vs where the audio was added with Python. You can check out the difference between the 2 sample slides here, the text on the left was the one where the audio was added normally.

You can see the experimental web app that adds the audio files with the add_movie() method and makes the changes I said above to include the audio in the video export here. If you upload the simple PowerPoint found in the repo to the web app and export the video, the video will play the audio on the slides. The same is not true if you upload the big complex PowerPoint.

The only differences between the code in the working app and the experimental version are the apply_changes_to_slide() and register_all_namespaces() functions.

I'll keep working on this, but I would appreciate any guidance you may have on what I should focus on @slycordinator @scanny

harryob2 avatar Mar 31 '23 21:03 harryob2

When you add audio using "add_video" function, it adds it with actual video tags in the xml and then in the content_types xml, there will usually be overrides to set the correct mime-type for it being an mp3/m4a/wav.

Though, if you open it and in PowerPoint and re-save the file, it's possible for it to automatically change the tags to actually be audio ones instead.

On Sat, Apr 1, 2023, 6:34 AM harryob2 @.***> wrote:

OK, I've made some progress. I managed to get the audio to play when the PowerPoint is exported as a video, but only with a very basic PowerPoint. With fancier PowerPoints with lots of images, it doesn't work.

I messed around with the XML data for the slides and noticed that if I just changed one piece of code from id="4" to id="6", and replaced the code between the <p:video></p:video> tags with a different block of code, I could get it to work with the simple PowerPoint files. I found the big block of code from the XML file of a PowerPoint where the audio was added manually in the PowerPoint app.

When I was trying to figure out why it didn't work on bigger, more complex PowerPoints, I compared the XML files from where the audio was added normally vs where the audio was added with Python. You can check out the difference between the 2 sample slides here https://www.diffchecker.com/oy5eVR6b/, the text on the left was the one where the audio was added normally.

You can see the experimental web app that adds the audio files with the add_movie() method and makes the changes I said above to include the audio in the video export here https://github.com/harryob2/ppt-narration-tool/tree/add_audio_test. If you upload the simple PowerPoint found in the repo to the web app and export the video, the video will play the audio on the slides. The same is not true if you upload the big complex PowerPoint.

The only differences between the code in the working app and the experimental version are the apply_changes_to_slide() and register_all_namespaces() functions.

I'll keep working on this, but I would appreciate any guidance you may have on what I should focus on @slycordinator https://github.com/slycordinator @scanny https://github.com/scanny

— Reply to this email directly, view it on GitHub https://github.com/scanny/python-pptx/issues/502#issuecomment-1492637513, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQN7DTPUOOHKMBPL3SYWCSLW65EWLANCNFSM4HO5JMFA . You are receiving this because you were mentioned.Message ID: @.***>

slycordinator avatar Apr 01 '23 02:04 slycordinator

it's a long thread... do we have a best practice to insert wav/mp3 with current python-pptx version? thanks @scanny @slycordinator

chrjxj avatar Jun 26 '24 06:06 chrjxj