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 */
030/**
031 * 
032 */
033package org.openimaj.demos.video;
034
035import java.awt.AWTEvent;
036import java.awt.Dimension;
037import java.awt.GridBagConstraints;
038import java.awt.GridBagLayout;
039import java.awt.HeadlessException;
040import java.awt.Toolkit;
041import java.awt.event.AWTEventListener;
042import java.awt.event.ActionEvent;
043import java.awt.event.ActionListener;
044import java.awt.event.KeyEvent;
045import java.io.File;
046import java.io.IOException;
047
048import javax.swing.JButton;
049import javax.swing.JFrame;
050import javax.swing.JLabel;
051import javax.swing.JPanel;
052import javax.swing.SwingConstants;
053
054import org.openimaj.demos.Demo;
055import org.openimaj.demos.video.utils.NumberKeySeekListener;
056import org.openimaj.demos.video.utils.ProcessingPanel;
057import org.openimaj.demos.video.utils.SourcePanel;
058import org.openimaj.image.DisplayUtilities.ImageComponent;
059import org.openimaj.image.MBFImage;
060import org.openimaj.video.Video;
061import org.openimaj.video.VideoDisplay;
062import org.openimaj.video.VideoDisplay.EndAction;
063import org.openimaj.video.VideoDisplay.Mode;
064import org.openimaj.video.VideoDisplayListener;
065import org.openimaj.video.capture.VideoCapture;
066import org.openimaj.video.xuggle.XuggleVideo;
067
068/**
069 *      A demo of the video functions and video processing functions in OpenIMAJ.
070 *      This demo shows a window which allows the user to select between webcam
071 *      video or video from a file. It also provides a set of pre-defined processing
072 *      operators which can be turned on and off for the video.
073 * 
074 *  @author David Dupplaw (dpd@ecs.soton.ac.uk)
075 *      
076 *      @created 28 Sep 2011
077 */
078@Demo(
079                author = "David Dupplaw",
080                description = "A simple GUI that demonstrates various video " +
081                                "processing functionalities " +
082                                "in OpenIMAJ and allows processing of both file and live videos.",
083                                keywords = { "video" },
084                                title = "Video Processing"
085                )
086public class VideoProcessingDemo extends JPanel implements VideoDisplayListener<MBFImage>
087{
088        /** */
089        private static final long serialVersionUID = 1L;
090
091        /** The video */
092        private Video<MBFImage> video;
093
094        /** The video display which will play the video */
095        private VideoDisplay<MBFImage> videoDisplay;
096
097        /** The image component into which the video is being painted (reused) */
098        private final ImageComponent ic;
099
100        /** Button to stop the video */
101        private JButton stopButton;
102
103        /** Button to play the video */
104        private JButton playButton;
105
106        /** Button to pause the video */
107        private JButton pawsButton;
108
109        /** The thread which is running the video playback */
110        private Thread videoThread;
111
112        /** The panel which provides the processing functions */
113        private ProcessingPanel processingPanel;
114
115        /** A label to show the number of frames per second being processed */
116        private JLabel fps;
117
118        /** The time a frame started to be processed. */
119        private long startTime;
120
121        /**
122         *      Default constructor.
123         * 
124         *      @throws IOException
125         */
126        public VideoProcessingDemo() throws IOException
127        {
128                this.ic = new ImageComponent( true );
129                this.ic.setPreferredSize( new Dimension(320,240) );
130                this.init();
131        }
132
133        /**
134         *      Sets up all the graphical widgets.
135         */
136        private void init()
137        {
138                this.setLayout( new GridBagLayout() );
139
140                // --------------------------------------------------------
141                // Video display
142                // --------------------------------------------------------
143                final GridBagConstraints gbc = new GridBagConstraints();
144                gbc.fill = GridBagConstraints.BOTH;
145                gbc.weightx = gbc.weighty = 1;
146                gbc.gridx = gbc.gridy = 0;
147                gbc.gridwidth = 3;
148
149                this.add( this.ic, gbc );
150
151                // --------------------------------------------------------
152                // Controls panels
153                // --------------------------------------------------------
154                gbc.gridx += gbc.gridwidth; gbc.gridwidth = 1;
155                final JPanel p = new JPanel( new GridBagLayout() );
156
157                final GridBagConstraints sgbc = new GridBagConstraints();
158                sgbc.fill = GridBagConstraints.BOTH;
159                sgbc.weightx = 0; sgbc.weighty = 1;
160                sgbc.gridx = sgbc.gridy = 0;
161                sgbc.gridwidth = 1;
162
163                p.add( new SourcePanel(this), sgbc );
164                sgbc.gridy++;
165                p.add( this.processingPanel = new ProcessingPanel(), sgbc );
166
167                this.add( p, gbc );
168                final int t = gbc.gridx;
169
170                // --------------------------------------------------------
171                // Navigation buttons
172                // --------------------------------------------------------
173                gbc.gridy++;
174                gbc.gridx = 0;
175                gbc.gridwidth = 1;
176                gbc.fill = GridBagConstraints.HORIZONTAL;
177
178                this.stopButton = new JButton( "STOP" );
179                this.playButton = new JButton( "PLAY" );
180                this.pawsButton = new JButton( "PAUSE" );
181
182                this.stopButton.addActionListener( new ActionListener()
183                {
184                        @Override
185                        public void actionPerformed( final ActionEvent e )
186                        {
187                                VideoProcessingDemo.this.videoDisplay.setMode( Mode.STOP );
188                        }
189                });
190                this.playButton.addActionListener( new ActionListener()
191                {
192                        @Override
193                        public void actionPerformed( final ActionEvent e )
194                        {
195                                VideoProcessingDemo.this.videoDisplay.setMode( Mode.PLAY );
196                        }
197                });
198                this.pawsButton.addActionListener( new ActionListener()
199                {
200                        @Override
201                        public void actionPerformed( final ActionEvent e )
202                        {
203                                VideoProcessingDemo.this.videoDisplay.setMode( Mode.PAUSE );
204                        }
205                });
206
207                this.add( this.playButton, gbc );
208                gbc.gridx++;
209                this.add( this.pawsButton, gbc );
210
211                gbc.gridx = t; gbc.weightx = 1;
212                this.add( this.fps = new JLabel(""), gbc );
213                this.fps.setHorizontalTextPosition( SwingConstants.CENTER );
214        }
215
216        /**
217         *      @return The image component used for displaying the video image.
218         */
219        public ImageComponent getImageComponent()
220        {
221                return this.ic;
222        }
223
224        /**
225         *      Set the video source to be the webcam
226         *  @throws IOException
227         */
228        public void useWebcam() throws IOException
229        {
230                // Stop any existing video
231                this.stopVideo();
232
233                // Setup a new video from the VideoCapture class
234                this.video = new VideoCapture( 320, 240 );
235
236                // Reset the video displayer to use the capture class
237                this.videoDisplay = new VideoDisplay<MBFImage>( this.video, this.ic );
238
239                // Make sure the listeners are sorted
240                this.addListeners();
241
242                // Start the new video playback thread
243                this.videoThread = new Thread(this.videoDisplay);
244                this.videoThread.start();
245        }
246
247        /**
248         *      Set the processing source to be the file
249         *  @param f
250         */
251        public void useFile( final File f )
252        {
253                // Stop any existing video
254                this.stopVideo();
255
256                // Setup a new video from the video file
257                this.video = new XuggleVideo( f , false);
258
259                // Reset the video displayer to use the file video
260                this.videoDisplay = new VideoDisplay<MBFImage>( this.video, this.ic );
261                this.videoDisplay.setEndAction( EndAction.LOOP );
262
263                // Make sure all the listeners are added to this new display
264                this.addListeners();
265                this.addVideoFileListeners();
266
267                // Start the new video playback thread
268                this.videoThread = new Thread(this.videoDisplay);
269                this.videoThread.start();
270        }
271
272        private void addVideoFileListeners() {
273                final long eventMask = AWTEvent.KEY_EVENT_MASK;
274                final NumberKeySeekListener keyEventListener = new NumberKeySeekListener(this.videoDisplay);
275                Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
276                        @Override
277                        public void eventDispatched(final AWTEvent event) {
278                                switch (event.getID()) {
279                                case KeyEvent.KEY_PRESSED:
280                                        final KeyEvent kevent = (KeyEvent) event;
281                                        keyEventListener.keyPressed(kevent);
282                                        break;
283                                };
284                        }
285                }, eventMask);
286                //              rp.addKeyListener(new NumberKeySeekListener(videoDisplay));
287        }
288
289        /**
290         *      Stops the current video.
291         */
292        private void stopVideo()
293        {
294                if( this.video instanceof VideoCapture )
295                        ((VideoCapture)this.video).stopCapture();
296                if( this.videoDisplay != null )
297                        this.videoDisplay.setMode( Mode.STOP );
298        }
299
300        /**
301         *      Adds the default listeners to the video display
302         */
303        private void addListeners()
304        {
305                this.videoDisplay.addVideoListener( this );
306                this.videoDisplay.addVideoListener( this.processingPanel );
307        }
308
309        /**
310         *  {@inheritDoc}
311         *  @see org.openimaj.video.VideoDisplayListener#afterUpdate(org.openimaj.video.VideoDisplay)
312         */
313        @Override
314        public void afterUpdate( final VideoDisplay<MBFImage> display )
315        {
316                final double diff = System.currentTimeMillis() - this.startTime;
317                final double d = Math.round(1d/(diff/10000d))/10d;
318
319                this.fps.setText( ""+d+" fps" );
320                this.startTime = System.currentTimeMillis();
321        }
322
323        /**
324         *  {@inheritDoc}
325         *  @see org.openimaj.video.VideoDisplayListener#beforeUpdate(org.openimaj.image.Image)
326         */
327        @Override
328        public void beforeUpdate( final MBFImage frame )
329        {
330        }
331
332        /**
333         * 
334         *  @param args
335         */
336        public static void main( final String[] args )
337        {
338                try
339                {
340                        final VideoProcessingDemo demo = new VideoProcessingDemo() ;
341                        final JFrame f = new JFrame( "Video Processing Demo" );
342                        f.getContentPane().add(demo );
343                        f.pack();
344                        f.setVisible( true );
345                        //              demo.useFile(new File("/Users/ss/Downloads/20070701_185500_bbcthree_doctor_who_confidential.ts"));
346                }
347                catch( final HeadlessException e )
348                {
349                        e.printStackTrace();
350                }
351                catch( final IOException e )
352                {
353                        e.printStackTrace();
354                }
355        }
356}