001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.content.slideshow;
031
032import java.awt.Component;
033import java.awt.Dimension;
034import java.awt.event.KeyEvent;
035import java.awt.event.KeyListener;
036import java.awt.image.BufferedImage;
037import java.io.File;
038import java.io.IOException;
039import java.net.URL;
040
041import org.apache.commons.io.FileUtils;
042import org.openimaj.image.DisplayUtilities.ImageComponent;
043import org.openimaj.image.ImageUtilities;
044import org.openimaj.image.MBFImage;
045import org.openimaj.image.processing.transform.MBFProjectionProcessor;
046import org.openimaj.video.VideoDisplay;
047import org.openimaj.video.VideoDisplay.EndAction;
048import org.openimaj.video.VideoDisplayListener;
049import org.openimaj.video.xuggle.XuggleVideo;
050
051import Jama.Matrix;
052
053/**
054 * Slide that shows a video. Number keys are bound to seek to different points
055 * in the video.
056 *
057 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
058 *
059 */
060public class VideoSlide implements Slide, VideoDisplayListener<MBFImage>, KeyListener {
061        URL url;
062        VideoDisplay<MBFImage> display;
063        private URL background;
064        protected PictureSlide pictureSlide;
065        private final Matrix transform;
066        private ImageComponent panel;
067        private BufferedImage bimg;
068        private MBFImage mbfImage;
069        private XuggleVideo video;
070        private EndAction endAction = EndAction.LOOP;
071
072        /**
073         * Default constructor.
074         *
075         * @param video
076         * @param background
077         * @param transform
078         * @param endAction
079         * @throws IOException
080         */
081        public VideoSlide(final URL video, final URL background, final Matrix transform, EndAction endAction)
082                        throws IOException
083        {
084                this.url = makeURL(video);
085                this.background = background;
086                this.pictureSlide = new PictureSlide(this.background);
087                this.transform = transform;
088                this.endAction = endAction;
089        }
090
091        /**
092         * Default constructor.
093         *
094         * @param video
095         * @param transform
096         * @param endAction
097         * @throws IOException
098         */
099        public VideoSlide(final URL video, final Matrix transform, EndAction endAction) throws IOException {
100                this.url = makeURL(video);
101                this.transform = transform;
102                this.endAction = endAction;
103        }
104
105        private URL makeURL(URL url) throws IOException {
106                if (url.getProtocol().startsWith("jar")) {
107                        final File tmp = File.createTempFile("movie", ".tmp");
108                        tmp.deleteOnExit();
109                        FileUtils.copyURLToFile(url, tmp);
110                        url = tmp.toURI().toURL();
111                }
112                return url;
113        }
114
115        /**
116         * Default constructor.
117         *
118         * @param video
119         * @param background
120         * @param endAction
121         * @throws IOException
122         */
123        public VideoSlide(final URL video, final URL background, EndAction endAction) throws IOException {
124                this(video, background, null, endAction);
125        }
126
127        @Override
128        public Component getComponent(final int width, final int height) throws IOException {
129                if (this.pictureSlide == null) {
130                        this.mbfImage = new MBFImage(width, height, 3);
131                        this.panel = (ImageComponent) new PictureSlide(this.mbfImage).getComponent(width, height);
132                }
133                else {
134                        this.panel = (ImageComponent) this.pictureSlide.getComponent(width, height);
135                        this.mbfImage = this.pictureSlide.mbfImage.clone();
136                }
137
138                this.panel.setSize(width, height);
139                this.panel.setPreferredSize(new Dimension(width, height));
140
141                this.video = new XuggleVideo(this.url, false);
142                this.display = VideoDisplay.createOffscreenVideoDisplay(this.video);
143                this.display.setEndAction(this.endAction);
144
145                this.display.addVideoListener(this);
146
147                return this.panel;
148        }
149
150        @Override
151        public void close() {
152                this.display.close();
153        }
154
155        @Override
156        public void afterUpdate(final VideoDisplay<MBFImage> display) {
157                // do nothing
158        }
159
160        @Override
161        public void beforeUpdate(final MBFImage frame) {
162                if (this.transform != null) {
163                        final MBFImage bgCopy = this.mbfImage.clone();
164                        final MBFProjectionProcessor proj = new MBFProjectionProcessor();
165                        proj.setMatrix(this.transform);
166                        proj.accumulate(frame);
167                        proj.performProjection(0, 0, bgCopy);
168                        this.panel.setImage(this.bimg = ImageUtilities.createBufferedImageForDisplay(bgCopy, this.bimg));
169                }
170                else
171                        this.panel.setImage(this.bimg = ImageUtilities.createBufferedImageForDisplay(frame, this.bimg));
172        }
173
174        @Override
175        public void keyPressed(final KeyEvent key) {
176                final int code = key.getKeyCode();
177                if (code >= KeyEvent.VK_0 && code <= KeyEvent.VK_9) {
178                        final double prop = (code - KeyEvent.VK_0) / 10.0;
179                        final long dur = this.video.getDuration();
180                        this.display.seek((long) (dur * prop));
181                }
182                if (code == KeyEvent.VK_SPACE) {
183                        this.display.togglePause();
184                }
185        }
186
187        @Override
188        public void keyReleased(final KeyEvent arg0) {
189                // do nothing
190        }
191
192        @Override
193        public void keyTyped(final KeyEvent arg0) {
194                // do nothing
195        }
196}