/* * JMFImages2Movie.java * * Version 1.0 Sep 18, 2008 * * Copyright notice * * Brief description * * (c) 2008 by dbreuer */ package de.fhkoeln.santiago.components.jmf; import java.awt.Dimension; import java.io.File; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Vector; import javax.media.Buffer; import javax.media.ConfigureCompleteEvent; import javax.media.ControllerEvent; import javax.media.ControllerListener; import javax.media.DataSink; import javax.media.EndOfMediaEvent; import javax.media.Format; import javax.media.Manager; import javax.media.MediaLocator; import javax.media.NoDataSinkException; import javax.media.NoProcessorException; import javax.media.NotConfiguredError; import javax.media.NotRealizedError; import javax.media.PrefetchCompleteEvent; import javax.media.Processor; import javax.media.RealizeCompleteEvent; import javax.media.ResourceUnavailableEvent; import javax.media.Time; import javax.media.control.TrackControl; import javax.media.datasink.DataSinkErrorEvent; import javax.media.datasink.DataSinkEvent; import javax.media.datasink.DataSinkListener; import javax.media.datasink.EndOfStreamEvent; import javax.media.format.UnsupportedFormatException; import javax.media.format.VideoFormat; import javax.media.protocol.ContentDescriptor; import javax.media.protocol.DataSource; import javax.media.protocol.FileTypeDescriptor; import javax.media.protocol.PullBufferDataSource; import javax.media.protocol.PullBufferStream; public class JMFImages2Movie implements ControllerListener, DataSinkListener, MediaAction { private static final int DEFAULT_FRAME_RATE = 2; private static final int DEFAULT_MOVIE_HEIGHT = 480; private static final int DEFAULT_MOVIE_WIDTH = 640; private static final String JPEG_FILTER = ".jpg"; private final List<String> inputFiles; private final MediaLocator uriToOutput; // For the JMF Code private Object waitSync = new Object(); private Object waitFileSync = new Object(); private boolean stateTransitionOK = true; private boolean fileDone = false; private boolean fileSuccess = true; public JMFImages2Movie(String pathToImages, String pathToOutput) throws FileNotFoundException { this.uriToOutput = createMediaLocator(pathToOutput); inputFiles = new Vector<String>(); inputFiles.addAll(getFilesInDirectory(pathToImages)); } /* (non-Javadoc) * @see javax.media.ControllerListener#controllerUpdate(javax.media.ControllerEvent) */ public void controllerUpdate(ControllerEvent event) { if (event instanceof ConfigureCompleteEvent || event instanceof RealizeCompleteEvent || event instanceof PrefetchCompleteEvent) { synchronized (waitSync) { stateTransitionOK = true; waitSync.notifyAll(); } } else if (event instanceof ResourceUnavailableEvent) { synchronized (waitSync) { stateTransitionOK = false; waitSync.notifyAll(); } } else if (event instanceof EndOfMediaEvent) { event.getSourceController().stop(); event.getSourceController().close(); } } /* (non-Javadoc) * @see javax.media.datasink.DataSinkListener#dataSinkUpdate(javax.media.datasink.DataSinkEvent) */ public void dataSinkUpdate(DataSinkEvent event) { if (event instanceof EndOfStreamEvent) { synchronized (waitFileSync) { fileDone = true; waitFileSync.notifyAll(); } } else if (event instanceof DataSinkErrorEvent) { synchronized (waitFileSync) { fileDone = true; fileSuccess = false; waitFileSync.notifyAll(); } } } /** * @return the inputFiles */ protected List<String> getInputFiles() { return this.inputFiles; } /** * @return the pathToOutput */ protected MediaLocator getOutputLocator() { return this.uriToOutput; } /* (non-Javadoc) * @see de.fhkoeln.cosima.components.jmf.MediaAction#performAction() */ public void performAction() { ImageDataSource dataSource = new ImageDataSource(DEFAULT_MOVIE_WIDTH, DEFAULT_MOVIE_HEIGHT, DEFAULT_FRAME_RATE, inputFiles); System.out.println("Trying to create processor ..."); try { Processor processor = Manager.createProcessor(dataSource); System.out.println("Processor was successfully created!"); processor.addControllerListener(this); // Put the Processor into configured state so we can set // some processing options on the processor. processor.configure(); if (!waitForState(processor, Processor.Configured)) throw new NotConfiguredError("Processor could not be configured."); // Set the output content descriptor to QuickTime. processor .setContentDescriptor(new ContentDescriptor( FileTypeDescriptor.QUICKTIME)); // Query for the processor for supported formats. // Then set it on the processor. TrackControl control[] = processor.getTrackControls(); Format formats[] = control[0].getSupportedFormats(); if (formats == null || formats.length <= 0) throw new UnsupportedFormatException("The mux doesn't support the input format.", control[0].getFormat()); control[0].setFormat(formats[0]); System.out.println("Setting the track format to: " + formats[0]); // We are done with programming the processor. Let's just // realize it. processor.realize(); if (!waitForState(processor, Processor.Realized)) throw new NotRealizedError("Processor could not be configured."); // Now, we'll need to create a DataSink. DataSink dataSink; if ((dataSink = createDataSink(processor, getOutputLocator())) == null) throw new NoDataSinkException("Failed to create a DataSink for the given output MediaLocator."); dataSink.addDataSinkListener(this); fileDone = false; System.out.println("Start transcoding images into movie ..."); // The transcoding process starts here ... processor.start(); dataSink.start(); // Wait for EndOfStream event. waitForFileDone(); // Cleanup. dataSink.close(); processor.removeControllerListener(this); System.out.println("Processing done!"); } catch (NoProcessorException e) { System.err.println("The processor could not be created!"); e.printStackTrace(); } catch (IOException e) { System.err.println("While reading the input something went wrong."); e.printStackTrace(); } catch (UnsupportedFormatException e) { e.printStackTrace(); } catch (NoDataSinkException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } private boolean waitForFileDone() { synchronized (waitFileSync) { try { while (!fileDone) waitFileSync.wait(); } catch (Exception e) { } } return fileSuccess; } /** * @param processor * @param outputLocator * @return * @throws Exception */ private DataSink createDataSink(Processor processor, MediaLocator outputLocator) throws Exception { DataSource dataSource; if ((dataSource = processor.getDataOutput()) == null) throw new Exception("Something is really wrong: the processor does not have an output DataSource"); DataSink dataSink; try { System.out.println("Creating DataSink for: " + outputLocator); dataSink = Manager.createDataSink(dataSource, outputLocator); dataSink.open(); } catch (NoDataSinkException e) { System.out.println("Cannot create the DataSink: " + e); return null; } return dataSink; } /** * Scans a given directory for all files matching the * JPEG_FILTER Constant and adds all full pathnames of those * files in a List Object. * * @param pathToDirectory * The path to the directory from which the images should * be fetched * @return A List representing all full paths to any single image * inside the directory. */ private List<String> getFilesInDirectory(String pathToDirectory) { FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { return !name.startsWith(".") && name.endsWith(JPEG_FILTER); } }; File directory = new File(pathToDirectory); File[] filesInDirectory = directory.listFiles(filter); List<String> pathnames = new ArrayList<String>(); System.out.println("Reading files in directory " + directory.toString() + " ..."); for (File file : filesInDirectory) { pathnames.add(file.toString()); // logging output System.out.println(" -> File to process: " + file.toString()); } return pathnames; } /** * Block until the processor has transitioned to the given state. * Return false if the transition failed. */ private boolean waitForState(Processor p, int state) { synchronized (waitSync) { try { while (p.getState() < state && stateTransitionOK) waitSync.wait(); } catch (Exception e) { } } return stateTransitionOK; } private MediaLocator createMediaLocator(String uri) throws FileNotFoundException { MediaLocator locator = null; if (uri.indexOf(":") > 0) locator = new MediaLocator(uri); if (uri.startsWith(File.separator)) locator = new MediaLocator("file:" + uri); if (locator == null) { throw new FileNotFoundException("The output file could not be realized with URI " + uri); } else { return locator; } } public class ImageDataSource extends PullBufferDataSource { ImageSourceStream streams[]; public ImageDataSource(int width, int height, int frameRate, List<String> images) { streams = new ImageSourceStream[1]; streams[0] = new ImageSourceStream(width, height, frameRate, images); } public PullBufferStream[] getStreams() { return streams; } public void connect() throws IOException {} public void disconnect() {} public String getContentType() { return ContentDescriptor.RAW; } public Object getControl(String type) { return null; } public Object[] getControls() { return new Object[0]; } public Time getDuration() { return DURATION_UNKNOWN; } public void start() throws IOException {} public void stop() throws IOException {} } class ImageSourceStream implements PullBufferStream { Iterator<String> images; int height, width; VideoFormat format; public ImageSourceStream(int width, int height, int frameRate, List<String> images) { this.width = width; this.height = height; this.images = images.listIterator(); format = new VideoFormat(VideoFormat.JPEG, new Dimension(width, height), Format.NOT_SPECIFIED, Format.byteArray, (float) frameRate); } public Format getFormat() { return format; } public void read(Buffer buffer) throws IOException { if (images.hasNext()) { String imagePath = (String) images.next(); System.out.println("Reading image '" + imagePath + "'"); // Open a random access File for this image RandomAccessFile randomAccessFile; randomAccessFile = new RandomAccessFile(imagePath, "r"); byte data[] = null; // check the input buffer type & size if (buffer.getData() instanceof byte[]) data = (byte[]) buffer.getData(); // Check to see the given buffer is big enough for the frame. if (data == null || data.length < randomAccessFile.length()) { data = new byte[(int) randomAccessFile.length()]; buffer.setData(data); } // Read the entire JPEG image from the file. randomAccessFile.readFully(data, 0, (int) randomAccessFile.length()); System.out.println(" read " + randomAccessFile.length() + " bytes."); buffer.setOffset(0); buffer.setLength((int) randomAccessFile.length()); buffer.setFormat(format); buffer.setFlags(buffer.getFlags() | Buffer.FLAG_KEY_FRAME); // Close the random access file. randomAccessFile.close(); } else { // We are done. Set EndOfMedia. System.out.println("Done reading all images."); buffer.setEOM(true); buffer.setOffset(0); buffer.setLength(0); } } public boolean willReadBlock() { return false; } public boolean endOfStream() { return images.hasNext(); } public ContentDescriptor getContentDescriptor() { return new ContentDescriptor(ContentDescriptor.RAW); } public long getContentLength() { return 0; } public Object getControl(String type) { return null; } public Object[] getControls() { return new Object[0]; } } }