sd/qa/filter/eppt/data/video-loop.pptx |binary sd/qa/filter/eppt/eppt.cxx | 26 ++++++++++++++++ sd/source/filter/eppt/pptx-animations.cxx | 47 ++++++++++++++++++++++++------ 3 files changed, 65 insertions(+), 8 deletions(-)
New commits: commit 38671e21d7dbcd5019912b9468305018de0c922e Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri Sep 2 14:19:41 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Sep 2 15:41:56 2022 +0200 Related: tdf#149969 PPTX export: add loop from the animation of a media shape The PPTX import maps media nodes to XAudio nodes, but the export side in PPTXAnimationExport::WriteAnimationNodeAudio() only handled audio, not video. This is fine, that code was added for audio narration purposes, but the same animation handles looping for videos, so this needs extending. Fix the problem by exporting <p:audio> conditionally and write video markup (especially info about repeat count) when the content of the media shape is video, not audio. Change-Id: Iba6bb4901b984c4363023f05232efc06ff069022 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139261 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sd/qa/filter/eppt/data/video-loop.pptx b/sd/qa/filter/eppt/data/video-loop.pptx new file mode 100644 index 000000000000..4cb7e20b7428 Binary files /dev/null and b/sd/qa/filter/eppt/data/video-loop.pptx differ diff --git a/sd/qa/filter/eppt/eppt.cxx b/sd/qa/filter/eppt/eppt.cxx index 1e8e2c7e1491..151b9cfce27a 100644 --- a/sd/qa/filter/eppt/eppt.cxx +++ b/sd/qa/filter/eppt/eppt.cxx @@ -125,6 +125,32 @@ CPPUNIT_TEST_FIXTURE(Test, testThemeExport) // i.e. the RGB color was lost on export. xComponent->dispose(); } + +CPPUNIT_TEST_FIXTURE(Test, testLoopingFromAnimation) +{ + // Given a media shape that has an animation that specifies looping for the video: + OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "video-loop.pptx"; + getComponent() = loadFromDesktop(aURL); + + // When exporting that to PPTX: + utl::TempFile aTempFile; + uno::Reference<frame::XStorable> xStorable(getComponent(), uno::UNO_QUERY); + utl::MediaDescriptor aMediaDescriptor; + aMediaDescriptor["FilterName"] <<= OUString("Impress Office Open XML"); + aTempFile.EnableKillingFile(); + xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); + validate(aTempFile.GetFileName(), test::OOXML); + + // Then make sure that the "infinite" repeat count is written: + std::unique_ptr<SvStream> pStream = parseExportStream(aTempFile, "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // Without the fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 0 + // - In <>, XPath '//p:cMediaNode/p:cTn' number of nodes is incorrect + // i.e. the media node was lost on export, the video no longer looped. + assertXPath(pXmlDoc, "//p:cMediaNode/p:cTn", "repeatCount", "indefinite"); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sd/source/filter/eppt/pptx-animations.cxx b/sd/source/filter/eppt/pptx-animations.cxx index 54dab17d1972..1c901573c00a 100644 --- a/sd/source/filter/eppt/pptx-animations.cxx +++ b/sd/source/filter/eppt/pptx-animations.cxx @@ -659,6 +659,9 @@ bool IsAudioURL(const OUString& rURL) { return rURL.endsWithIgnoreAsciiCase(".wav") || rURL.endsWithIgnoreAsciiCase(".m4a"); } + +/// Returns if rURL has an extension which is a video format. +bool IsVideoURL(const OUString& rURL) { return rURL.endsWithIgnoreAsciiCase(".mp4"); } } namespace oox::core @@ -1235,6 +1238,7 @@ void PPTXAnimationExport::WriteAnimationNodeAudio() bValid = true; } + bool bVideo = false; if (!bValid) { if (xAudio->getSource() >>= xShape) @@ -1243,7 +1247,8 @@ void PPTXAnimationExport::WriteAnimationNodeAudio() bool bHasMediaURL = xShapeProps->getPropertySetInfo()->hasPropertyByName("MediaURL"); if (bHasMediaURL && (xShapeProps->getPropertyValue("MediaURL") >>= sUrl)) { - bValid = IsAudioURL(sUrl); + bVideo = IsVideoURL(sUrl); + bValid = IsAudioURL(sUrl) || bVideo; } } } @@ -1256,12 +1261,31 @@ void PPTXAnimationExport::WriteAnimationNodeAudio() mrPowerPointExport.embedEffectAudio(mpFS, sUrl, sRelId, sName); } - bool bNarration = xAudio->getNarration(); - mpFS->startElementNS(XML_p, XML_audio, XML_isNarration, bNarration ? "1" : "0"); - bool bHideDuringShow = xAudio->getHideDuringShow(); - mpFS->startElementNS(XML_p, XML_cMediaNode, XML_showWhenStopped, bHideDuringShow ? "0" : "1"); + if (bVideo) + { + mpFS->startElementNS(XML_p, XML_video); + mpFS->startElementNS(XML_p, XML_cMediaNode); + } + else + { + bool bNarration = xAudio->getNarration(); + mpFS->startElementNS(XML_p, XML_audio, XML_isNarration, bNarration ? "1" : "0"); + bool bHideDuringShow = xAudio->getHideDuringShow(); + mpFS->startElementNS(XML_p, XML_cMediaNode, XML_showWhenStopped, + bHideDuringShow ? "0" : "1"); + } - mpFS->startElementNS(XML_p, XML_cTn); + animations::Timing eTiming{}; + bool bLooping + = (xAudio->getRepeatCount() >>= eTiming) && eTiming == animations::Timing_INDEFINITE; + if (bVideo && bLooping) + { + mpFS->startElementNS(XML_p, XML_cTn, XML_repeatCount, "indefinite"); + } + else + { + mpFS->startElementNS(XML_p, XML_cTn); + } WriteAnimationCondList(mpContext->getCondition(true), XML_stCondLst); WriteAnimationCondList(mpContext->getCondition(false), XML_endCondLst); mpFS->endElementNS(XML_p, XML_cTn); @@ -1281,7 +1305,14 @@ void PPTXAnimationExport::WriteAnimationNodeAudio() mpFS->endElementNS(XML_p, XML_tgtEl); mpFS->endElementNS(XML_p, XML_cMediaNode); - mpFS->endElementNS(XML_p, XML_audio); + if (bVideo) + { + mpFS->endElementNS(XML_p, XML_video); + } + else + { + mpFS->endElementNS(XML_p, XML_audio); + } } void PPTXAnimationExport::WriteAnimationNode(const NodeContextPtr& pContext) @@ -1456,7 +1487,7 @@ void NodeContext::initValid(bool bHasValidChild, bool bIsIterateChild) = xShapeProps->getPropertySetInfo()->hasPropertyByName("MediaURL"); if (bHasMediaURL && (xShapeProps->getPropertyValue("MediaURL") >>= sURL)) { - mbValid = IsAudioURL(sURL); + mbValid = IsAudioURL(sURL) || IsVideoURL(sURL); } } }