/*
 * JPEGImagesToVideo.java
 * 
 * From http://java.sun.com/javase/technologies/desktop/media/jmf/2.1.1/solutions/JpegImagesToMovie.java  1.3 01/03/13
 * 
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 * 
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear facility.
 * Licensee represents and warrants that it will not use or redistribute the
 * Software for such purposes.
 */
package com.eteks.sweethome3d.swing;

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;

import javax.media.ConfigureCompleteEvent;
import javax.media.Controller;
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.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
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.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.FileTypeDescriptor;

/**
 * This program takes a list of images and convert them into a
 * QuickTime movie.
 */
public class JPEGImagesToVideo {
  private Object  waitSync;
  private boolean stateTransitionOK;
  private Object  waitFileSync;
  private boolean fileDone;
  private String  fileError;

  /**
   * Creates a video file at the given size and frame rate. 
   */
  public void createVideoFile(int width, int height, int frameRate, DataSource dataSource, File file) throws IOException {
    this.waitSync = new Object();
    this.stateTransitionOK = true;
    this.waitFileSync = new Object();
    this.fileDone = false;
    this.fileError = null;
    
    ControllerListener controllerListener = new ControllerListener() {
        public void controllerUpdate(ControllerEvent ev) {
          if (ev instanceof ConfigureCompleteEvent 
              || ev instanceof RealizeCompleteEvent
              || ev instanceof PrefetchCompleteEvent) {
            synchronized (waitSync) {
              stateTransitionOK = true;
              waitSync.notifyAll();
            }
          } else if (ev instanceof ResourceUnavailableEvent) {
            synchronized (waitSync) {
              stateTransitionOK = false;
              waitSync.notifyAll();
            }
          } else if (ev instanceof EndOfMediaEvent) {
            ev.getSourceController().stop();
            ev.getSourceController().close();
          }
        }
      };
    DataSinkListener dataSinkListener = new DataSinkListener() {
        public void dataSinkUpdate(DataSinkEvent ev) {
          if (ev instanceof EndOfStreamEvent) {
            synchronized (waitFileSync) {
              fileDone = true;
              waitFileSync.notifyAll();
            }
          } else if (ev instanceof DataSinkErrorEvent) {
            synchronized (waitFileSync) {
              fileDone = true;
              fileError = "Data sink error";
              waitFileSync.notifyAll();
            }
          }
        }
      };
    Processor processor = null;
    DataSink dataSink = null;
    try {
      processor = Manager.createProcessor(dataSource);
      processor.addControllerListener(controllerListener);
      processor.configure();
      // Put the Processor into configured state so we can set
      // some processing options on the processor.
      if (!waitForState(processor, Processor.Configured)) {
        throw new IOException("Failed to configure the processor.");
      }

      // 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 trackControls[] = processor.getTrackControls();
      Format format[] = trackControls [0].getSupportedFormats();
      if (format == null || format.length <= 0) {
        throw new IOException("The mux does not support the input format: " + trackControls [0].getFormat());
      }

      trackControls [0].setFormat(format [0]);

      // We are done with programming the processor. Let's just realize it.
      processor.realize();
      if (!waitForState(processor, Controller.Realized)) {
        throw new IOException("Failed to realize the processor.");
      }

      // Now, we'll need to create a DataSink.
      // Caution: do not use file.toURI().toURL() with JMF
      dataSink = Manager.createDataSink(processor.getDataOutput(), 
          new MediaLocator(file.toURL()));
      dataSink.open();
      dataSink.addDataSinkListener(dataSinkListener);
      this.fileDone = false;

      // Start the actual transcoding
      processor.start();
      dataSink.start();

      // Wait for EndOfStream event.
      synchronized (this.waitFileSync) {
        while (!this.fileDone) {
          this.waitFileSync.wait();
        }
      }
      
      if (this.fileError != null) {
        throw new IOException(this.fileError);
      }
    } catch (NoProcessorException ex) {
      IOException ex2 = new IOException(ex.getMessage());
      ex2.initCause(ex);
      throw ex2;
    } catch (NoDataSinkException ex) {
      IOException ex2 = new IOException("Failed to create a DataSink for the given output MediaLocator");
      ex2.initCause(ex);
      throw ex2;
    } catch (InterruptedException ex) {
      if (dataSink != null) {
        dataSink.stop();
      }
      throw new InterruptedIOException("Video creation interrupted");
    } finally {
      if (dataSink != null) {
        dataSink.close();
        dataSink.removeDataSinkListener(dataSinkListener);
      }
      if (processor != null) {
        processor.close();
        processor.removeControllerListener(controllerListener);
      }
    }
  }

  /**
   * Blocks until the processor has transitioned to the given state. Return false
   * if the transition failed.
   */
  private boolean waitForState(Processor p, int state) throws InterruptedException {
    synchronized (waitSync) {
      while (p.getState() < state && stateTransitionOK) {
        waitSync.wait();
      }
    }
    return stateTransitionOK;
  }
}
