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.video.xuggle;
034
035import static com.xuggle.xuggler.Global.DEFAULT_TIME_UNIT;
036import static java.util.concurrent.TimeUnit.MILLISECONDS;
037
038import java.awt.image.BufferedImage;
039
040import org.openimaj.image.ImageUtilities;
041import org.openimaj.image.MBFImage;
042import org.openimaj.video.VideoWriter;
043
044import com.xuggle.mediatool.IMediaWriter;
045import com.xuggle.mediatool.ToolFactory;
046
047/**
048 *      An implementation of the video writer class that uses the Xuggler
049 *      video API to write videos.  Note that Xuggler will resize any images
050 *      to fit within the given output size during its write process. 
051 *
052 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
053 *  @created 27 Jul 2011
054 *      
055 */
056public class XuggleVideoWriter extends VideoWriter<MBFImage>
057{
058        /** The media writer we'll use to write the video */
059        private IMediaWriter writer = null;
060        
061        /** The filename to write the video to */
062        private String filename = "output.mp4";
063        
064        /** Keep track of the timecode of the next frame */
065        private long nextFrameTime = 0;
066
067        /** This is the time between each frame in milliseconds */
068        private long interFrameTime = 400;
069
070        /**
071         *      Default constructor that takes the frame size and frame rate
072         *      of the resulting video.
073         * 
074         *      @param filename The filename to write the video to
075         *      @param width The width of the video frame in pixels
076         *      @param height The height of the video frame in pixels
077         *      @param frameRate The frame rate of the resulting video
078         */
079        public XuggleVideoWriter( String filename, int width, 
080                        int height, double frameRate )
081        {
082                super( width, height, frameRate );
083                
084                this.filename = filename;
085                this.initialise();
086
087                long sd = (long)(1000/frameRate);
088                this.interFrameTime  = DEFAULT_TIME_UNIT.convert(sd, MILLISECONDS);
089        }
090        
091        /**
092         *      Initialise the writer
093         */
094        public void initialise()
095        {
096                if( writer == null )
097                {
098                        // First, let's make a IMediaWriter to write the file.
099                        this.writer  = ToolFactory.makeWriter( filename );
100                         
101                        // We tell it we're going to add one video stream, with id 0,
102                        // at position 0
103                        this.writer.addVideoStream(0, 0, width, height);
104                        
105                        // Reset the next frame's timecode
106                        this.nextFrameTime = 0;
107                }
108        }
109        
110        /**
111         *      {@inheritDoc}
112         *      @see org.openimaj.video.processor.VideoProcessor#processingComplete()
113         */
114        @Override
115        public void processingComplete()
116        {
117                this.close();
118        }
119        
120        /**
121         *      Close the video stream
122         */
123        @Override
124        public void close()
125        {
126                this.writer.close();
127        }
128        
129        /** 
130         *      {@inheritDoc}
131         *      @see org.openimaj.video.VideoWriter#addFrame(org.openimaj.image.Image)
132         */
133        @Override
134        public void addFrame( MBFImage frame )
135        {
136                // Create a buffered image for Xuggler
137        BufferedImage f = ImageUtilities.createBufferedImage( frame );
138        f = convertToType( f, BufferedImage.TYPE_3BYTE_BGR );
139        
140        // Encode the image to the video stream
141        this.writer.encodeVideo( 0, f, nextFrameTime, DEFAULT_TIME_UNIT );
142
143        // Set up the next timecode
144                this.nextFrameTime += this.interFrameTime;
145        }
146        
147        /**
148         * Convert a {@link BufferedImage} of any type, to {@link BufferedImage} of
149         * a specified type. If the source image is the same type as the target
150         * type, then original image is returned, otherwise new image of the correct
151         * type is created and the content of the source image is copied into the
152         * new image.
153         * 
154         * @param sourceImage the image to be converted
155         * @param targetType the desired BufferedImage type
156         * 
157         * @return a BufferedImage of the specified target type.
158         */
159        public static BufferedImage convertToType( BufferedImage sourceImage, 
160                        int targetType )
161        {
162                BufferedImage image;
163
164                // if the source image is already the target type, return the source image
165                if( sourceImage.getType() == targetType )
166                        image = sourceImage;
167                // otherwise create a new image of the target type and draw the new image
168                else
169                {
170                        image = new BufferedImage( sourceImage.getWidth(), 
171                                        sourceImage.getHeight(), targetType );
172                        image.getGraphics().drawImage( sourceImage, 0, 0, null );
173                }
174
175                return image;
176        }
177
178        @Override
179        public void reset() 
180        {
181                // Cannot reset the Xuggle video writer.
182        }
183}