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

shapes.add_movie doesn't allow video to automatically start playing

Open Lileso opened this issue 6 years ago • 16 comments

~~And if possible there is a tag called play in full screen that would be awesome to be able to enable as well~~ Edit: Worked out how to do the full screen part

if this helps here is the difference between a slide with a video set to on click and here is the same slide with a video set to automatically play

https://www.diffchecker.com/aSGgBiOS

Lileso avatar Aug 23 '18 00:08 Lileso

@Lileso did you make any progress of auto play ? could you share it please ?

UAEpro avatar Feb 17 '19 07:02 UAEpro

I'm afraid not. I couldn't seem to get it to work no matter what I changed in the xml

Lileso avatar Feb 17 '19 08:02 Lileso

Here is a more up to date diff https://www.diffchecker.com/IuCdI8Rd The left handside is the on click and right handside is the auto run.

Looking through the source code I can't find how it edits the XML when it adds the movie but it should be just adding an event

<p:cond evt="onBegin" delay="0">
   <p:tn val="2"/>
</p:cond>

And then adjusting <p:cTn id="5" presetID="1" presetClass="mediacall" presetSubtype="0" fill="hold" nodeType="clickEffect"> to

<p:cTn id="5" presetID="1" presetClass="mediacall" presetSubtype="0" fill="hold" nodeType="afterEffect">

When manually editing the XML this works in auto playing video

Lileso avatar Feb 20 '19 16:02 Lileso

--------------Please ignore my previous post as it doesn't stick the to OOXML Standard.----------------- @UAEpro I've written some changes to the Scanny's python-pptx. This allows for autoplay but does have a small bug at the moment which I am still working on in a different branch https://github.com/Lileso/python-pptx

Lileso avatar Feb 21 '19 19:02 Lileso

If anyone else is looking for a quick hack that doesn't require modifying the library code, here's what I did (it's ugly but it works)

from lxml import etree
movie = slide.shapes.add_movie(path, 0, 0, width, height, poster_frame_image=still, mime_type=mime)
tree = movie._element.getparent().getparent().getnext().getnext()
timing = [el for el in tree.iterdescendants() if etree.QName(el).localname == 'cond'][0]
timing.set('delay', '0')

I haven't tested it on a slide with any advanced timing or multiple shapes

iota-pi avatar Feb 21 '19 23:02 iota-pi

@iota-pi

This is what my code does so I'm afraid to say it suffers the same bug mine does. I'm currently working on adding the animation like PowerPoint does but am getting stuck in adding the <p:seq> to the XML parser library.

Lileso avatar Feb 22 '19 00:02 Lileso

to make the video loop I did that

repeat = [el for el in tree.iterdescendants() if etree.QName(el).localname == 'cTn'][1]
repeat.set('repeatCount', 'indefinite')
repeat.set('fill', 'hold')
repeat.set('display', '0')

Thanks to @iota-pi code the purpose is to replace gif with mp4 which have smaller size

UAEpro avatar Mar 06 '19 06:03 UAEpro

I have added a movie, but etree.QName(el).localname only shows

  1. Choice
  2. transition
  3. Fallback
  4. transition

I did not see cond

Do you have any help? Thanks.

yoonghm avatar Feb 19 '20 17:02 yoonghm

Ok, I managed to fix it now. The xml has changed. My code is

        tree = movie._element.getparent().getparent().getnext().getnext().getnext()
        timing = [el for el in tree.iterdescendants() if etree.QName(el).localname == 'cond'][0]
        print(timing)
        timing.set('delay', '0')

yoonghm avatar Feb 19 '20 17:02 yoonghm

After trying the code from @iota-pi I believe the bug @Lileso is referring to, is only being able to autoplay the first media item. I took a look at the xml and found a solution that seems to work for autoplaying specific media.

from lxml import etree

def autoplay_media(media):
    el = media._element
    id = xpath(el, './/p:cNvPr')[0].attrib['id']
    sld = el.getparent().getparent().getparent()
    cond = xpath(sld, './/p:timing//p:video//p:cTn[@id="%s"]//p:cond' % (id))[0]
    cond.set('delay', '0')

def xpath(el, query):
    nsmap = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main'}
    return etree.ElementBase.xpath(el, query, namespaces=nsmap)

Kayron013 avatar May 09 '20 05:05 Kayron013

I've tried @Kayron013 code, but got errors with some presentations. So I've found that video's timing can be described as in this example:

<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="171"/>
           </p:tgtEl>
     </p:cMediaNode>
</p:video>

I've modified autoplay_media method to this one:

def autoplay_media(media):
    el_id = xpath(media.element, './/p:cNvPr')[0].attrib['id']
    el_cnt = xpath(
        media.element.getparent().getparent().getparent(),
        './/p:timing//p:video//p:spTgt[@spid="%s"]' % el_id,
    )[0]
    cond = xpath(el_cnt.getparent().getparent(), './/p:cond')[0]
    cond.set('delay', '0')

It works in all my test cases.

monstarnn avatar Jun 08 '21 12:06 monstarnn

btw, this code can set time interval of a given slide (default 2s)

def auto_adjust_time(slide, ms=2000):
    p = "http://schemas.openxmlformats.org/presentationml/2006/main"
    ele = etree.Element(etree.QName(p, "transition"), attrib={'spd': 'med', 'advClick': '0', 'advTm': str(ms)})
    xpath(slide.element, './/p:clrMapOvr')[0].getparent().insert(-1, ele)

yc-cui avatar Jan 23 '22 18:01 yc-cui

@monstarnn monstarnn

I have tried your example, and it works great, but I also need to set "loop until stopped" , do you know how to set that?

zookz avatar Feb 23 '23 14:02 zookz

FYI .. I got it to work ... if anyone need to set the "loop until stopped" prop here is how.

def autoplay_media(media):
    el_id = xpath(media.element, './/p:cNvPr')[0].attrib['id']
    el_cnt = xpath(
        media.element.getparent().getparent().getparent(),
        './/p:timing//p:video//p:spTgt[@spid="%s"]' % el_id,
    )[0]

    ctn = xpath(el_cnt.getparent().getparent(), './/p:cTn')[0]
    ctn.set('repeatCount', 'indefinite')

    cond = xpath(el_cnt.getparent().getparent(), './/p:cond')[0]
    cond.set('delay', '0')

zookz avatar Feb 24 '23 09:02 zookz

If anyone else is looking for a quick hack that doesn't require modifying the library code, here's what I did (it's ugly but it works)

from lxml import etree
movie = slide.shapes.add_movie(path, 0, 0, width, height, poster_frame_image=still, mime_type=mime)
tree = movie._element.getparent().getparent().getnext().getnext()
timing = [el for el in tree.iterdescendants() if etree.QName(el).localname == 'cond'][0]
timing.set('delay', '0')

I haven't tested it on a slide with any advanced timing or multiple shapes

This worked great, thanks for sharing

zervyou avatar Aug 06 '23 01:08 zervyou

FYI .. I got it to work ... if anyone need to set the "loop until stopped" prop here is how.

def autoplay_media(media):
    el_id = xpath(media.element, './/p:cNvPr')[0].attrib['id']
    el_cnt = xpath(
        media.element.getparent().getparent().getparent(),
        './/p:timing//p:video//p:spTgt[@spid="%s"]' % el_id,
    )[0]

    ctn = xpath(el_cnt.getparent().getparent(), './/p:cTn')[0]
    ctn.set('repeatCount', 'indefinite')

    cond = xpath(el_cnt.getparent().getparent(), './/p:cond')[0]
    cond.set('delay', '0')

@monstarnn It only works when the slides go forward, still not works when go backward. Any updates on the issue. Thanks,

dinhnguyens avatar Feb 07 '24 12:02 dinhnguyens