/*
 *  ====================================================================
 *    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:
 *  &lt;repositories&gt;
 *  &lt;repository&gt;
 *  &lt;id&gt;xuggle repo&lt;/id&gt;
 *  &lt;url&gt;http://xuggle.googlecode.com/svn/trunk/repo/share/java/&lt;/url&gt;
 *  &lt;/repository&gt;
 *  &lt;/repositories&gt;
 *  ...
 *  &lt;dependency&gt;
 *  &lt;groupId&gt;xuggle&lt;/groupId&gt;
 *  &lt;artifactId&gt;xuggle-xuggler&lt;/artifactId&gt;
 *  &lt;version&gt;5.4&lt;/version&gt;
 *  &lt;/dependency&gt;
 * 
 * @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";

        try (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));
        }
    }

    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;
            }
        }
    }

}
