/* | |
* ==================================================================== | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* ==================================================================== | |
*/ | |
package org.apache.poi.xslf.usermodel; | |
import java.awt.Rectangle; | |
import java.awt.image.BufferedImage; | |
import java.io.ByteArrayOutputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.net.URL; | |
import java.text.DecimalFormat; | |
import javax.imageio.ImageIO; | |
import javax.xml.namespace.QName; | |
import org.apache.poi.openxml4j.opc.PackagePart; | |
import org.apache.poi.openxml4j.opc.PackagePartName; | |
import org.apache.poi.openxml4j.opc.PackageRelationship; | |
import org.apache.poi.openxml4j.opc.PackagingURIHelper; | |
import org.apache.poi.openxml4j.opc.TargetMode; | |
import org.apache.poi.sl.usermodel.PictureData.PictureType; | |
import org.apache.xmlbeans.XmlCursor; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink; | |
import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTExtension; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTSlide; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTTLCommonMediaNodeData; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTTLCommonTimeNodeData; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTTimeNodeList; | |
import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeIndefinite; | |
import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeNodeFillType; | |
import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeNodeRestartType; | |
import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeNodeType; | |
import com.xuggle.mediatool.IMediaReader; | |
import com.xuggle.mediatool.MediaListenerAdapter; | |
import com.xuggle.mediatool.ToolFactory; | |
import com.xuggle.mediatool.event.IVideoPictureEvent; | |
import com.xuggle.xuggler.Global; | |
import com.xuggle.xuggler.IContainer; | |
import com.xuggle.xuggler.io.InputOutputStreamHandler; | |
/** | |
* Adding multiple videos to a slide | |
* | |
* need the Xuggler 5.4 jars: | |
* <repositories> | |
* <repository> | |
* <id>xuggle repo</id> | |
* <url>http://xuggle.googlecode.com/svn/trunk/repo/share/java/</url> | |
* </repository> | |
* </repositories> | |
* ... | |
* <dependency> | |
* <groupId>xuggle</groupId> | |
* <artifactId>xuggle-xuggler</artifactId> | |
* <version>5.4</version> | |
* </dependency> | |
* | |
* @see <a href="http://stackoverflow.com/questions/15197300/apache-poi-xslf-adding-movie-to-the-slide">Apache POI XSLF Adding movie to the slide</a> | |
* @see <a href="http://apache-poi.1045710.n5.nabble.com/Question-about-embedded-video-in-PPTX-files-tt5718461.html">Question about embedded video in PPTX files</a> | |
*/ | |
public class AddVideoToPptx { | |
static DecimalFormat df_time = new DecimalFormat("0.####"); | |
public static void main(String[] args) throws Exception { | |
URL video = new URL("http://archive.org/download/test-mpeg/test-mpeg.mpg"); | |
// URL video = new URL("file:test-mpeg.mpg"); | |
XMLSlideShow pptx = new XMLSlideShow(); | |
// add video file | |
String videoFileName = video.getPath().substring(video.getPath().lastIndexOf('/')+1); | |
PackagePartName partName = PackagingURIHelper.createPartName("/ppt/media/"+videoFileName); | |
PackagePart part = pptx.getPackage().createPart(partName, "video/mpeg"); | |
OutputStream partOs = part.getOutputStream(); | |
InputStream fis = video.openStream(); | |
byte buf[] = new byte[1024]; | |
for (int readBytes; (readBytes = fis.read(buf)) != -1; partOs.write(buf, 0, readBytes)); | |
fis.close(); | |
partOs.close(); | |
XSLFSlide slide1 = pptx.createSlide(); | |
XSLFPictureShape pv1 = addPreview(pptx, slide1, part, 5, 50, 50); | |
addVideo(pptx, slide1, part, pv1, 5); | |
addTimingInfo(slide1, pv1); | |
XSLFPictureShape pv2 = addPreview(pptx, slide1, part, 9, 50, 250); | |
addVideo(pptx, slide1, part, pv2, 9); | |
addTimingInfo(slide1, pv2); | |
FileOutputStream fos = new FileOutputStream("pptx-with-video.pptx"); | |
pptx.write(fos); | |
fos.close(); | |
pptx.close(); | |
} | |
static XSLFPictureShape addPreview(XMLSlideShow pptx, XSLFSlide slide1, PackagePart videoPart, double seconds, int x, int y) throws IOException { | |
// get preview after 5 sec. | |
IContainer ic = IContainer.make(); | |
InputOutputStreamHandler iosh = new InputOutputStreamHandler(videoPart.getInputStream()); | |
if (ic.open(iosh, IContainer.Type.READ, null) < 0) return null; | |
IMediaReader mediaReader = ToolFactory.makeReader(ic); | |
// stipulate that we want BufferedImages created in BGR 24bit color space | |
mediaReader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR); | |
ImageSnapListener isl = new ImageSnapListener(seconds); | |
mediaReader.addListener(isl); | |
// read out the contents of the media file and | |
// dispatch events to the attached listener | |
while (!isl.hasFired && mediaReader.readPacket() == null) ; | |
mediaReader.close(); | |
ic.close(); | |
// add snapshot | |
BufferedImage image1 = isl.image; | |
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
ImageIO.write(image1, "jpeg", bos); | |
XSLFPictureData snap = pptx.addPicture(bos.toByteArray(), PictureType.JPEG); | |
XSLFPictureShape pic1 = slide1.createPicture(snap); | |
pic1.setAnchor(new Rectangle(x, y, image1.getWidth(), image1.getHeight())); | |
return pic1; | |
} | |
static void addVideo(XMLSlideShow pptx, XSLFSlide slide1, PackagePart videoPart, XSLFPictureShape pic1, double seconds) throws IOException { | |
// add video shape | |
PackagePartName partName = videoPart.getPartName(); | |
PackageRelationship prsEmbed1 = slide1.getPackagePart().addRelationship(partName, TargetMode.INTERNAL, "http://schemas.microsoft.com/office/2007/relationships/media"); | |
PackageRelationship prsExec1 = slide1.getPackagePart().addRelationship(partName, TargetMode.INTERNAL, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video"); | |
CTPicture xpic1 = (CTPicture)pic1.getXmlObject(); | |
CTHyperlink link1 = xpic1.getNvPicPr().getCNvPr().addNewHlinkClick(); | |
link1.setId(""); | |
link1.setAction("ppaction://media"); | |
// add video relation | |
CTApplicationNonVisualDrawingProps nvPr = xpic1.getNvPicPr().getNvPr(); | |
nvPr.addNewVideoFile().setLink(prsExec1.getId()); | |
CTExtension ext = nvPr.addNewExtLst().addNewExt(); | |
// see http://msdn.microsoft.com/en-us/library/dd950140(v=office.12).aspx | |
ext.setUri("{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}"); | |
String p14Ns = "http://schemas.microsoft.com/office/powerpoint/2010/main"; | |
XmlCursor cur = ext.newCursor(); | |
cur.toEndToken(); | |
cur.beginElement(new QName(p14Ns, "media", "p14")); | |
cur.insertNamespace("p14", p14Ns); | |
cur.insertAttributeWithValue(new QName(STRelationshipId.type.getName().getNamespaceURI(), "embed"), prsEmbed1.getId()); | |
cur.beginElement(new QName(p14Ns, "trim", "p14")); | |
cur.insertAttributeWithValue("st", df_time.format(seconds*1000.0)); | |
cur.dispose(); | |
} | |
static void addTimingInfo(XSLFSlide slide1, XSLFPictureShape pic1) { | |
// add slide timing information, so video can be controlled | |
CTSlide xslide = slide1.getXmlObject(); | |
CTTimeNodeList ctnl; | |
if (!xslide.isSetTiming()) { | |
CTTLCommonTimeNodeData ctn = xslide.addNewTiming().addNewTnLst().addNewPar().addNewCTn(); | |
ctn.setDur(STTLTimeIndefinite.INDEFINITE); | |
ctn.setRestart(STTLTimeNodeRestartType.NEVER); | |
ctn.setNodeType(STTLTimeNodeType.TM_ROOT); | |
ctnl = ctn.addNewChildTnLst(); | |
} else { | |
ctnl = xslide.getTiming().getTnLst().getParArray(0).getCTn().getChildTnLst(); | |
} | |
CTTLCommonMediaNodeData cmedia = ctnl.addNewVideo().addNewCMediaNode(); | |
cmedia.setVol(80000); | |
CTTLCommonTimeNodeData ctn = cmedia.addNewCTn(); | |
ctn.setFill(STTLTimeNodeFillType.HOLD); | |
ctn.setDisplay(false); | |
ctn.addNewStCondLst().addNewCond().setDelay(STTLTimeIndefinite.INDEFINITE); | |
cmedia.addNewTgtEl().addNewSpTgt().setSpid(""+pic1.getShapeId()); | |
} | |
static class ImageSnapListener extends MediaListenerAdapter { | |
final double SECONDS_BETWEEN_FRAMES; | |
final long MICRO_SECONDS_BETWEEN_FRAMES; | |
boolean hasFired = false; | |
BufferedImage image = null; | |
// The video stream index, used to ensure we display frames from one and | |
// only one video stream from the media container. | |
int mVideoStreamIndex = -1; | |
// Time of last frame write | |
long mLastPtsWrite = Global.NO_PTS; | |
public ImageSnapListener(double seconds) { | |
SECONDS_BETWEEN_FRAMES = seconds; | |
MICRO_SECONDS_BETWEEN_FRAMES = | |
(long)(Global.DEFAULT_PTS_PER_SECOND * SECONDS_BETWEEN_FRAMES); | |
} | |
@Override | |
public void onVideoPicture(IVideoPictureEvent event) { | |
if (event.getStreamIndex() != mVideoStreamIndex) { | |
// if the selected video stream id is not yet set, go ahead an | |
// select this lucky video stream | |
if (mVideoStreamIndex != -1) return; | |
mVideoStreamIndex = event.getStreamIndex(); | |
} | |
long evtTS = event.getTimeStamp(); | |
// if uninitialized, back date mLastPtsWrite to get the very first frame | |
if (mLastPtsWrite == Global.NO_PTS) | |
mLastPtsWrite = Math.max(0, evtTS - MICRO_SECONDS_BETWEEN_FRAMES); | |
// if it's time to write the next frame | |
if (evtTS - mLastPtsWrite >= MICRO_SECONDS_BETWEEN_FRAMES) { | |
if (!hasFired) { | |
image = event.getImage(); | |
hasFired = true; | |
} | |
// update last write time | |
mLastPtsWrite += MICRO_SECONDS_BETWEEN_FRAMES; | |
} | |
} | |
} | |
} |