/*******************************************************************************
 * Copyright (c) 2013, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package io.humble.video.awt;

import io.humble.video.MediaPicture;
import io.humble.video.PixelFormat;

import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import java.util.Collections;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.awt.image.BufferedImage;

/**
 * This factory class creates {@link MediaPictureConverter} objects for
 * translation between a
 * number of {@link MediaPicture} and {@link BufferedImage} types.  Not
 * all image and picture types are supported.  When an unsupported
 * converter is requested, a descriptive {@link
 * UnsupportedOperationException} is thrown.
 * <p>  Each converter can
 * translate between any supported {@link io.humble.video.PixelFormat.Type}
 *  and a single {@link
 * BufferedImage} type.  Converters can optionally resize during the
 * conversion process.
 * </p>
 * <p>
 * Each converter will re-sample the {@link MediaPicture} it is converting
 * to get the data in the same native byte layout as a {@link BufferedImage}
 * if needed (using {@link MediaPictureResampler}).  This step takes time and creates
 * a new temporary copy of the image data.  To avoid this step
 * you can make sure the {@link MediaPicture#getPixelType()} is of the same
 * binary type as a {@link BufferedImage}.
 * </p>
 * <p>  Put another way, if you're
 * converting to {@link BufferedImage#TYPE_3BYTE_BGR}, we will not resample
 * if the {@link MediaPicture#getPixelType()} is
 *  {@link io.humble.video.PixelFormat.Type#BGR24}.
 * </p>
 */

public class MediaPictureConverterFactory
{
  /**
   * Default constructor
   */
  protected MediaPictureConverterFactory() {

  }

  /** Converts between MediaPictures and {@link BufferedImage} of type
   * {@link BufferedImage#TYPE_3BYTE_BGR} */

  public static final String HUMBLE_BGR_24 = "HUMBLE-BGR-24";

  // the registered converter types

  private static Map<String, Type> mConverterTypes = new HashMap<String, Type>();

  // register the known converters

  static
  {
    registerConverter(new Type(HUMBLE_BGR_24, BgrConverter.class,
        PixelFormat.Type.PIX_FMT_BGR24, BufferedImage.TYPE_3BYTE_BGR));
  }

  /**
   * Register a converter with this factory. The factory oragnizes the
   * converters by descriptor, and thus each should be unique unless one
   * whishes to replace an existing converter.
   *
   * @param converterType the type for the converter to be registered
   *
   * @return the converter type which this converter displaced, or NULL
   * of this is a novel converter
   */

  public static Type registerConverter(Type converterType)
  {
    return mConverterTypes.put(converterType.getDescriptor(), converterType);
  }

  /**
   * Unregister a converter with this factory.
   *
   * @param converterType the type for the converter to be unregistered
   *
   * @return the converter type which removed, or NULL of this converter
   * was not recognized
   */

  public static Type unregisterConverter(Type converterType)
  {
    return mConverterTypes.remove(converterType.getDescriptor());
  }

  /**
   * Get a collection of the registered converters.  The collection is unmodifiable.
   *
   * @return an unmodifiable collection of converter types.
   */

  public static Collection<Type> getRegisteredConverters()
  {
    return Collections.unmodifiableCollection(mConverterTypes.values());
  }

  /**
   * Find a converter given a type descriptor.
   *
   * @param descriptor a unique string which describes this converter
   *
   * @return the converter found or NULL if it was not found.
   */

  public static Type findRegisteredConverter(String descriptor)
  {
    return mConverterTypes.get(descriptor);
  }

  /**
   * Find a descriptor given a {@link BufferedImage}.
   *
   * @param image a buffered image for which to find a descriptor
   *
   * @return the descriptor which matches the image or NULL if it was
   *         not found
   */

  public static String findDescriptor(BufferedImage image)
  {
    for (Type converterType: getRegisteredConverters())
      if (converterType.getImageType() == image.getType())
        return converterType.getDescriptor();

    return null;
  }

  /**
   * Create a converter which translates betewen {@link BufferedImage}
   * and {@link MediaPicture} types.  The {@link
   * io.humble.video.PixelFormat.Type} and size are extracted from
   * the passed in picture.  This factory will attempt to create a
   * converter which can perform the translation.  If no converter can
   * be created, a descriptive {@link UnsupportedOperationException} is
   * thrown.
   *
   * @param converterDescriptor the unique string descriptor of the
   *        converter which is to be created
   * @param picture the picture from which size and type are extracted
   *
   * @throws UnsupportedOperationException if the converter can not be
   *         found
   * @throws UnsupportedOperationException if the found converter can
   *         not be properly initialized
   * @throws IllegalArgumentException if the passed {@link
   *         MediaPicture} is NULL;
   */

  public static MediaPictureConverter createConverter(
    String converterDescriptor,
    MediaPicture picture)
  {
    if (picture == null)
      throw new IllegalArgumentException("The picture is NULL.");

    return createConverter(converterDescriptor, picture.getFormat(),
      picture.getWidth(), picture.getHeight());
  }

  /**
   * Create a converter which translates between {@link BufferedImage}
   * and {@link MediaPicture}.  This factory will attempt to
   * create a converter which can perform the translation.  If no
   * converter can be created, a descriptive {@link
   * UnsupportedOperationException} is thrown.
   *
   * @param image a prototypical {@link BufferedImage} that is in the format you want to convert to/from.
   * @param picture a prototypical {@link MediaPicture} in the format you want to convert to/from.
   *
   * @throws UnsupportedOperationException if the converter can not be
   *         found
   * @throws UnsupportedOperationException if the found converter can
   *         not be properly initialized
   */
  public static MediaPictureConverter createConverter(
      BufferedImage image,
      MediaPicture picture)
  {

    if (image == null)
      throw new IllegalArgumentException("cannot pass null image");
    if (picture == null)
      throw new IllegalArgumentException("cannot pass null picture");
    return createConverter(image, picture.getFormat());
  }
  /**
   * Create a converter which translates between {@link BufferedImage}
   * and {@link MediaPicture} types. The {@link BufferedImage} type and
   * size are extracted from the passed in image.  This factory will
   * attempt to create a converter which can perform the translation.
   * If no converter can be created, a descriptive {@link
   * UnsupportedOperationException} is thrown.
   *
   * @param image the image from which size and type are extracted
   * @param pictureType the picture type of the converter
   *
   * @throws UnsupportedOperationException if no converter for the
   *         specified BufferedImage type exists
   * @throws UnsupportedOperationException if the found converter can
   *         not be properly initialized
   * @throws IllegalArgumentException if the passed {@link
   *         BufferedImage} is NULL;
   */

  public static MediaPictureConverter createConverter(
    BufferedImage image,
    PixelFormat.Type pictureType)
  {
    if (image == null)
      throw new IllegalArgumentException("The image is NULL.");

    // find the converter type based in image type

    String converterDescriptor = findDescriptor(image);
    if (converterDescriptor == null)
      throw new UnsupportedOperationException(
        "No converter found for BufferedImage type #" +
        image.getType());

    // create and return the converter

    return createConverter(converterDescriptor, pictureType,
      image.getWidth(), image.getHeight());
  }

  /**
   * Create a converter which translates between {@link BufferedImage}
   * and {@link MediaPicture} types.  This factory will attempt to
   * create a converter which can perform the translation.  If no
   * converter can be created, a descriptive {@link
   * UnsupportedOperationException} is thrown.
   *
   * @param converterDescriptor the unique string descriptor of the
   *        converter which is to be created
   * @param pictureType the picture type of the converter
   * @param width the width of pictures and images
   * @param height the height of pictures and images
   *
   * @throws UnsupportedOperationException if the converter can not be
   *         found
   * @throws UnsupportedOperationException if the found converter can
   *         not be properly initialized
   */

  public static MediaPictureConverter createConverter(
    String converterDescriptor,
    PixelFormat.Type pictureType,
    int width, int height)
  {
    return createConverter(converterDescriptor, pictureType,
      width, height, width, height);
  }

  /**
   * Create a converter which translates betewen {@link BufferedImage}
   * and {@link MediaPicture} types.  This factory will attempt to
   * create a converter which can perform the translation.  If different
   * image and pictures sizes are passed the converter will resize
   * during translation.  If no converter can be created, a descriptive
   * {@link UnsupportedOperationException} is thrown.
   *
   * @param converterDescriptor the unique string descriptor of the
   *        converter which is to be created
   * @param pictureType the picture type of the converter
   * @param pictureWidth the width of pictures
   * @param pictureHeight the height of pictures
   * @param imageWidth the width of images
   * @param imageHeight the height of images
   *
   * @throws UnsupportedOperationException if the converter can not be
   *         found
   * @throws UnsupportedOperationException if the converter can not be
   *         properly created or initialized
   */

  public static MediaPictureConverter createConverter(
    String converterDescriptor,
    PixelFormat.Type pictureType,
    int pictureWidth, int pictureHeight,
    int imageWidth, int imageHeight)
  {
    MediaPictureConverter converter = null;

    // establish the converter type

    Type converterType = findRegisteredConverter(converterDescriptor);
    if (null == converterType)
      throw new UnsupportedOperationException(
        "No converter \"" + converterDescriptor + "\" found.");

    // create the converter

    try
    {
      // establish the constructor

      Constructor<? extends MediaPictureConverter> converterConstructor =
        converterType.getConverterClass().getConstructor(PixelFormat.Type.class,
          int.class, int.class, int.class, int.class);

      // create the converter

      converter = converterConstructor.newInstance(
        pictureType, pictureWidth, pictureHeight, imageWidth, imageHeight);
    }
    catch (NoSuchMethodException e)
    {
      throw new UnsupportedOperationException(
        "Converter " + converterType.getConverterClass() +
        " requries a constructor of the form "  +
        "(PixelFormat.Type, int, int, int, int)");
    }
    catch (InvocationTargetException e)
    {
      Throwable cause = e.getCause();
      if (cause != null && cause instanceof OutOfMemoryError)
      {
        throw (OutOfMemoryError)cause;
      }
      else
      {
        throw new UnsupportedOperationException(
            "Converter " + converterType.getConverterClass() +
            " constructor failed with: " + e.getCause());
      }
    }
    catch (IllegalAccessException e)
    {
      Throwable cause = e.getCause();
      if (cause != null && cause instanceof OutOfMemoryError)
      {
        throw (OutOfMemoryError)cause;
      }
      else
      {
        throw new UnsupportedOperationException(
            "Converter " + converterType.getConverterClass() +
            " constructor failed with: " + e.getCause());
      }
    }
    catch (InstantiationException e)
    {
      Throwable cause = e.getCause();
      if (cause != null && cause instanceof OutOfMemoryError)
      {
        throw (OutOfMemoryError)cause;
      }
      else
      {
        throw new UnsupportedOperationException(
            "Converter " + converterType.getConverterClass() +
            " constructor failed with: " + e.getCause());
      }
    }

    // return the newly created converter

    return converter;
  }

  /**
   * This class describes a converter type and is used to register and
   * unregister types with {@link MediaPictureConverterFactory}.  The factory
   * oragnizes the converters by descriptor, and thus each should be
   * unique unless you wish to replace an existing converter.
   */

  public static class Type
  {
    /** The unique string which describes this converter. */

    final private String mDescriptor;

    /** The class responsible for converting between types. */

    final private Class<? extends MediaPictureConverter> mConverterClass;

    /**
     * The {@link io.humble.video.PixelFormat.Type} which the
     * picture must be in to convert it to a {@link BufferedImage}
     */

    final private PixelFormat.Type mPictureType;

    /**
     * The {@link BufferedImage} type which the image must be in to
     * convert it to a {@link io.humble.video.MediaPicture}
     */

    final private int mImageType;

    /** Construct a complete converter type description.
     *
     * @param descriptor a unique string which describes this converter
     * @param converterClass the class which converts between pictures
     *        and images
     * @param pictureType the {@link
     *        io.humble.video.PixelFormat.Type} type which the
     *        picture must be in to convert it to an image
     * @param imageType the {@link BufferedImage} type which the picture
     *        must be in to convert it to a {@link BufferedImage}
     */

    public Type(String descriptor, Class<? extends MediaPictureConverter> converterClass,
      PixelFormat.Type pictureType, int imageType)
    {
      mDescriptor = descriptor;
      mConverterClass = converterClass;
      mPictureType = pictureType;
      mImageType = imageType;
    }

    /** Get the unique string which describes this converter. */

    public String getDescriptor()
    {
      return mDescriptor;
    }

    /** Get the class responsible for converting between types. */

    public Class<? extends MediaPictureConverter> getConverterClass()
    {
      return mConverterClass;
    }

    /**
     * Get the {@link io.humble.video.PixelFormat.Type} which the
     * picture must be in to convert it to a {@link BufferedImage}
     */

    public PixelFormat.Type getPictureType()
    {
      return mPictureType;
    }

    /**
     * Get the {@link BufferedImage} type which the image must be in to
     * convert it to a {@link io.humble.video.MediaPicture}
     */

    public int getImageType()
    {
      return mImageType;
    }

    /**
     * Get a string description of this conveter type.
     */

    public String toString()
    {

      return getDescriptor() + ": picture type " + getPictureType() +
        ", image type " + getImageType();
    }
  }

  /**
   * Convert a {@link BufferedImage} of any type, to {@link
   * BufferedImage} of a specified type.  If the source image is the
   * same type as the target type, then original image is returned,
   * otherwise new image of the correct type is created and the content
   * of the source image is copied into the new image.
   *
   * @param sourceImage the image to be converted
   * @param targetType the desired BufferedImage type
   *
   * @return a BufferedImage of the specifed target type.
   *
   * @see BufferedImage
   */

  public static BufferedImage convertToType(BufferedImage sourceImage,
    int targetType)
  {
    BufferedImage image;

    // if the source image is already the target type, return the source image

    if (sourceImage.getType() == targetType)
      image = sourceImage;

    // otherwise create a new image of the target type and draw the new
    // image

    else
    {
      image = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(),
        targetType);
      image.getGraphics().drawImage(sourceImage, 0, 0, null);
    }

    return image;
  }
}


/*******************************************************************************
 * Copyright (c) 2013, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

package io.humble.ferry;

/**
 * Internal Only.  The JNIMemoryParachute is a very weird beast.  Don't look here.
 *

 * Damn it... I told you not to.  So in normal cases Java handles out
 * of memory conditions by throwing an {@link OutOfMemoryError}.  However
 * when you add JNI to the mix, things get complicated.  In the ferry
 * system Java calls native code which in turns calls Java code.
 *

 * In fact sometimes native code calls back into Java code to allocate
 * memory (using the {@link JNIMemoryAllocator}).  However a problem occurs
 * if the {@link JNIMemoryAllocator} runs out of heap memory -- it throws
 * an OutOfMemoryError back into the native code.
 *

 *

 * The native code is smart enough to see that, and to just return back to
 * it's calling Java code where the {@link OutOfMemoryError} should be
 * thrown again.
 *

 *

 * The problem is in the various JNI implementations -- they sometimes need
 * to allocate heap memory just to return from native code.  When they attempt
 * to do that to return, they run into the original {@link OutOfMemoryError},
 * and fail.  The error differs depending on JVM.  On Sun's Java JRE for
 * Windows it can result in Windows terminating the JVM.  On Linux it can
 * result in Java terminating itself with an uncatchable {@link OutOfMemoryError}.
 *

 *

 * So enter the {@link JNIMemoryParachute}.  The {@link JNIMemoryAllocator}
 * allocates an instance of a {@link JNIMemoryParachute} at startup.  The
 * {@link JNIMemoryParachute} just holds on to some heap memory (about 10k)
 * "just in case".  Then if the {@link JNIMemoryAllocator} fails to allocate,
 * it pulls the rip cord on the parachute, and forces a Garbage Collect, before
 * returning the JNI.  The hope is that 10K is enough heap space for the JNI
 * implementation to correctly return to the underlying Java layer and handle
 * the original {@link OutOfMemoryError}.
 *

 *
Now this is really just a hail mary (at the expense of 10k of memory) -
 * you can still get failures if multiple threads are all trying to go through
 * the JNI layer at the same time, but this helps minimize the odds of that
 * long enough for you to catch an error.

 *
See.  Told you to not read.

 *
 * @author aclarke
 *
 */
final class JNIMemoryParachute
{
  private final static JNIMemoryParachute mParachute;
  static {
    mParachute = new JNIMemoryParachute();
  }

  /**
   * Factory method for the parachute -- do not cache;  use this so that
   * we have a nice option for provider either thread-specific
   * or global parachutes.
   *
   * @return a parachute to use for this memory request.
   */
  static JNIMemoryParachute getParachute()
  {
    return mParachute;
  }

  private volatile byte[] mPayload;
  private final int PAYLOAD_BYTES=10000;
  private JNIMemoryParachute()
  {
    // create a payload on first creation
    packChute();
  }

  /**
   * Checks the current parachute and recreates (reloads) our payload
   * if needed.
   * @return true on success pretty much all the time because...
   * @throws OutOfMemoryError if we can't reload the parachute
   */
  boolean packChute() throws OutOfMemoryError
  {
    if (mPayload != null)
      return true;

    // otherwise the payload is null
    synchronized(this)
    {
      if (mPayload != null)
        return true;
      try {
        //System.out.println("Packing the parachute! this=" + this);
        mPayload = new byte[PAYLOAD_BYTES];
        mPayload[0] = 'P';
        mPayload[1] = 'A';
        mPayload[2] = 'R';
        mPayload[3] = 'A';
        mPayload[4] = 'C';
        mPayload[5] = 'H';
        mPayload[6] = 'U';
        mPayload[7] = 'T';
        mPayload[8] = 'E';
        mPayload[9] = '!';
        return true;
      } catch (OutOfMemoryError e) {
        // we failed to create the parachute.  Also known as bad.
        // forward it on.
        throw e;
      }
    }
  }

  void pullCord()
  {
    // do one check outside the lock to avoid a sync if we're
    // not loaded.
    if (mPayload == null)
      return;
    synchronized(this)
    {
      if (mPayload == null)
        return;
      //System.out.println("Ripping the cord! this=" + this);
      // otherwise, we're going to free up our payload,
      // and do a quick allocation to suggest to Java to do
      // a GC if needed
      mPayload = null;
      try {
        byte[] discardBytes = new byte[4];
        discardBytes[1]=2;
      } catch (OutOfMemoryError e) {
        // ignore because the caller will deal with that.
        do {

        } while(false);
      }
    }
  }

}


/*******************************************************************************
 * Copyright (c) 2013, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package io.humble.video.customio;

import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of URLProtocolHandler that can read from
 * {@link ReadableByteChannel}
 * objects or write to {@link WritableByteChannel} object.
 *
 *

 *
 * The {@link IURLProtocolHandler#URL_RDWR} mode is not supported, and
 * {@link #isStreamed(String, int)} will always return true.
 *
 *

 *
 * @author aclarke
 *
 */

public class ReadableWritableChannelHandler implements IURLProtocolHandler
{
  private final Logger log = LoggerFactory.getLogger(this.getClass());

  /**
   * Whether or not this class will default calling
   * {@link Closeable#close()} on a stream when
   * {@link #close()} is called on this method.  Can be
   * overriden in constructors.
   */
  public final static boolean CLOSE_STREAM_ON_CLOSE=true;

  private final ReadableByteChannel mReadChannel;
  private final WritableByteChannel mWriteChannel;
  private final boolean mCloseStreamOnClose;
  private Channel mOpenStream = null;

  /**
   * Create a handler that can only be opened for reading.
   * @param channel an input channel.
   */
  public ReadableWritableChannelHandler(ReadableByteChannel channel)
  {
    this(channel, null, CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Create a handler that can only be opened for writing.
   * @param channel an output channel
   */
  public ReadableWritableChannelHandler(WritableByteChannel channel)
  {
    this(null, channel, CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Creates a new handler.  If you pass in non null arguments
   * for both in and out, the handler may be opened in either
   * {@link IURLProtocolHandler#URL_RDONLY_MODE} or
   * {@link IURLProtocolHandler#URL_WRONLY_MODE}, but not at
   * the same time.
   *

   * {@link IURLProtocolHandler#URL_RDWR} mode is not supported
   * by this handler.
   *

   * @param in The channel to read from if asked to.
   * @param out The channel to write from if asked to.
   * @param closeStreamOnClose Whether or not to automatically
   *   call {@link Closeable#close()} on the given channel
   *   when {@link #close()} is called.
   */
  public ReadableWritableChannelHandler(
      ReadableByteChannel in, WritableByteChannel out,
      boolean closeStreamOnClose)
  {
    if (in == null && out == null)
      throw new IllegalArgumentException("must pass one non null stream");
    mReadChannel = in;
    mWriteChannel = out;
    mCloseStreamOnClose = closeStreamOnClose;
  }

  /**
   * {@inheritDoc}
   */

  public int close()
  {
    int retval = 0;
    try
    {
      if (mOpenStream != null && mCloseStreamOnClose)
      {
        mOpenStream.close();
      }
    }
    catch (IOException e)
    {
      log.error("could not close stream {}: {}", mOpenStream, e);
      retval = -1;
    }
    mOpenStream = null;
    return retval;
  }

  /**
   * {@inheritDoc}
   */

  public int open(String url, int flags)
  {
    if (mOpenStream != null)
    {
      log.debug("attempting to open already open handler: {}",
          mOpenStream);
      return -1;
    }
    switch (flags)
    {
      case URL_RDWR:
        log.debug("do not support read/write mode for Java IO Handlers");
        return -1;
      case URL_WRONLY_MODE:
        mOpenStream = mWriteChannel;
        if (mOpenStream == null)
        {
          log.error("No OutputStream specified for writing: {}",url);
          return -1;
        }
        break;
      case URL_RDONLY_MODE:
        mOpenStream = mReadChannel;
        if (mOpenStream == null)
        {
          log.error("No InputStream specified for reading: {}", url);
          return -1;
        }
        break;
      default:
        log.error("Invalid flag passed to open: {}", url);
        return -1;
    }

    return 0;
  }

  /**
   * {@inheritDoc}
   */

  public int read(byte[] buf, int size)
  {
    int ret = -1;
    if (mOpenStream == null || !(mOpenStream instanceof ReadableByteChannel))
      return -1;

    try
    {
      ReadableByteChannel channel = (ReadableByteChannel) mOpenStream;
      ByteBuffer buffer = ByteBuffer.allocate(size);
      ret = channel.read(buffer);
      if (ret > 0) {
        buffer.flip();
        buffer.get(buf, 0, ret);
      }
      return ret;
    }
    catch (IOException e)
    {
      log.error("Got IO exception reading from channel: {}; {}",
          mOpenStream, e);
      return -1;
    }
  }

  /**
   * {@inheritDoc}
   *
   * This method is not supported on this class and always return -1;
   */

  public long seek(long offset, int whence)
  {
    return -1;
  }

  /**
   * {@inheritDoc}
   */

  public int write(byte[] buf, int size)
  {
    if (mOpenStream == null ||
        !(mOpenStream instanceof WritableByteChannel))
      return -1;

    try
    {
      WritableByteChannel channel = (WritableByteChannel) mOpenStream;
      ByteBuffer buffer = ByteBuffer.allocate(size);
      buffer.put(buf, 0, size);
      buffer.flip();
      return channel.write(buffer);
    }
    catch (IOException e)
    {
      log.error("Got error writing to file: {}; {}", mOpenStream, e);
      return -1;
    }
  }

  /**
   * {@inheritDoc}
   * Always true for this class.
   */

  public boolean isStreamed(String url, int flags)
  {
    return true;
  }
  /**
   * Returns the channel we'd input from if asked.
   * @return the channel
   */
  public ReadableByteChannel getReadChannel()
  {
    return mReadChannel;
  }
  /**
   * Returns the channel we'd output to if asked.
   * @return the channel
   */
  public WritableByteChannel getWriteChannel()
  {
    return mWriteChannel;
  }
  /**
   * Returns the channel currently {@link #open(String, int)}.
   * @return the openStream
   */
  public Channel getOpenChannel()
  {
    return mOpenStream;
  }

  /**
   * Will this handler call {@link Closeable#close()} automatically
   * when its {@link #close()} method is called by Humble Video?
   *
   * @return the closeStreamOnClose setting
   */
  public boolean isCloseStreamOnClose()
  {
    return mCloseStreamOnClose;
  }

}


/*******************************************************************************
 * Copyright (c) 2013, Art Clarke.  All rights reserved.
 *
 * This file is part of Humble-Video.
 *
 * Humble-Video is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Humble-Video is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Humble-Video.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package io.humble.video.customio;

import java.io.Closeable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.ByteChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.humble.video.Container;

/**
 * Allows Humble Video to read from and write to many different types of Java I/O
 * objects, plus custom {@link IURLProtocolHandler} objects.
 *
 *

 * Most of the time, Container hides this away from you, but in case you're
 * interested, here's the underlying class doing the IO magic.
 *

 *
 * To use for reading (assuming an InputStream object called inputStream):

 *
 *

 * Container container = Container.make();
 * container.open(inputStream, null);
 *

 *

 * or for writing:
 *

 *
 *

 * Container container = Container.make();
 * container.open(outputStream, null);
 *

 *

 * Really. That's it.
 *

 *

 *
 * All streams that are mapped in this factory share the same name space, even
 * if registered under different protocols. So, if "exampleone" and "exampletwo"
 * were both registered as protocols for this factory, then
 * "exampleone:filename" is the same as "exampletwo:filename" and will map to
 * the same input and output streams. In reality, they are all mapped to the
 * {@link #DEFAULT_PROTOCOL} protocol.
 *
 *

 */

public class HumbleIO implements IURLProtocolHandlerFactory
{
  private final Logger log = LoggerFactory.getLogger(this.getClass());
  /**
   * The default protocol string that this factory uses (
   * {@value #DEFAULT_PROTOCOL}).
   */

  public final static String DEFAULT_PROTOCOL = "humble";

  /**
   * Do we undo mappings on open by default?
   */
  private final static boolean DEFAULT_UNMAP_URL_ON_OPEN = true;

  /**
   * Do we call {@link Closeable#close()} by default when closing?
   */
  private final static boolean DEFAULT_CLOSE_STREAM_ON_CLOSE = true;

  /**
   * A thread-safe mapping between URLs and registration information
   */
  private final ConcurrentMap<String, RegistrationInformation> mURLs = new ConcurrentHashMap<String, RegistrationInformation>();

  /**
   * The singleton Factory object for this class loader.
   */
  private final static HumbleIO mFactory = new HumbleIO();

  /**
   * The static constructor just registered the singleton factory under the
   * DEFAULT_PROTOCOL protocol.
   */
  static
  {
    registerFactory(DEFAULT_PROTOCOL);
  }

  /**
   * Package level constructor. We don't allow people to create their own
   * version of this factory
   */
  HumbleIO()
  {

  }

  /**
   * Register a new protocol name for this factory that Humble.IO will use for
   * the given protocol.
   *
   *

   *
   * A default factory for the protocol {@value #DEFAULT_PROTOCOL} will already
   * be registered. This just allows you to register the same factory under
   * different strings if you want.
   *
   *

   *

   *
   * NOTE: Protocol can only contain alpha characters.
   *
   *

   *
   * @param protocolPrefix The protocol (e.g. "yourapphandler").
   * @return The factory registered
   */

  static HumbleIO registerFactory(String protocolPrefix)
  {
    URLProtocolManager manager = URLProtocolManager.getManager();
    manager.registerFactory(protocolPrefix, mFactory);
    return mFactory;
  }

  /**
   * Get the singleton factory object for this class.
   *
   * @return the factory
   */

  static public HumbleIO getFactory()
  {
    return mFactory;
  }

  /**
   * Generates a unique name suitable for using in the map methods for the URL
   * parameter.
   *
   * @param src The object you want to generate a unique name for, or null if
   *        you don't have one.
   * @return A unique name (will be unique across time and space).
   */
  static public String generateUniqueName(Object src)
  {
    return generateUniqueName(src, null);
  }

  /**
   * Generates a unique name suitable for using in the map methods for the URL
   * parameter.
   *
   * @param src The object you want to generate a unique name for, or null if
   *        you don't have one.
   * @param extension an option extension to append to the generated URL.
   * @return A unique name (will be unique across time and space).
   */
  static public String generateUniqueName(Object src, String extension)
  {
    StringBuilder builder = new StringBuilder();
    builder.append(UUID.randomUUID().toString());
    if (src != null)
    {
      builder.append("-");
      builder.append(src.getClass().getName());
      builder.append("-");
      builder.append(Integer.toHexString(src.hashCode()));
    }
    if (extension != null)
    {
      builder.append(extension);
    }
    return builder.toString();
  }

  /**
   * Maps a {@link IURLProtocolHandler} to a url that Humble can open.
   *
   * {@link #unmap(String)} will be called automatically after this URL is
   * opened.
   *
   * @param handler the handler
   * @return a string that is suitable for passing to {@link Container}'s open
   *         methods.
   */
  public static String map(IURLProtocolHandler handler)
  {
    return map(generateUniqueName(handler), handler, DEFAULT_UNMAP_URL_ON_OPEN);
  }

  /**
   * Maps a {@link IURLProtocolHandler} to a url that Humble can open.
   *
   * {@link #unmap(String)} will be called automatically after this URL is
   * opened.
   *
   * @param url the unique string to use for the mapping.
   * @param handler the handler
   * @return a string that is suitable for passing to {@link Container}'s open
   *         methods.
   */
  public static String map(String url, IURLProtocolHandler handler)
  {
    return map(url, handler, DEFAULT_UNMAP_URL_ON_OPEN);
  }

  /**
   * Maps a {@link DataInput} object to a URL for use by Humble.
   *
   * @param input the {@link DataInput}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(DataInput input)
  {
    return map(generateUniqueName(input), input, null,
        DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link DataInput} object to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param input the {@link DataInput}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(String url, DataInput input)
  {
    return map(url, input, null, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link DataOutput} object to a URL for use by Humble.
   *
   * @param output the {@link DataOutput}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(DataOutput output)
  {
    return map(generateUniqueName(output), null, output,
        DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link DataOutput} object to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param output the {@link DataOutput}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(String url, DataOutput output)
  {
    return map(url, null, output, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link RandomAccessFile} object to a URL for use by Humble.
   *
   * @param file the {@link RandomAccessFile}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(RandomAccessFile file)
  {
    return map(generateUniqueName(file), file, file, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link RandomAccessFile} object to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param file the {@link RandomAccessFile}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(String url, RandomAccessFile file)
  {
    return map(url, file, file, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link ReadableByteChannel} to a URL for use by Humble.
   *
   * @param channel the {@link ReadableByteChannel}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(ReadableByteChannel channel)
  {
    return map(generateUniqueName(channel), channel, null,
        DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link ReadableByteChannel} to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param channel the {@link ReadableByteChannel}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(String url, ReadableByteChannel channel)
  {
    return map(url, channel, null, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link WritableByteChannel} to a URL for use by Humble.
   *
   * @param channel the {@link WritableByteChannel}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(WritableByteChannel channel)
  {
    return map(generateUniqueName(channel), null, channel,
        DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link WritableByteChannel} to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param channel the {@link WritableByteChannel}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(String url, WritableByteChannel channel)
  {
    return map(url, null, channel, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link ByteChannel} to a URL for use by Humble.
   *
   * @param channel the {@link ByteChannel}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(ByteChannel channel)
  {
    return map(generateUniqueName(channel), channel, channel,
        DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link ByteChannel} to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param channel the {@link ByteChannel}
   * @return a string that can be passed to {@link Container}'s open methods.
   */
  public static String map(String url, ByteChannel channel)
  {
    return map(url, channel, channel, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps an {@link InputStream} to a URL for use by Humble.
   *
   * @param in the stream
   *
   * @return Returns a URL that can be passed to Humble's {@link Container}'s
   *         open method, and will result in IO being performed on the passed in
   *         streams.
   */

  public static String map(InputStream in)
  {
    return map(generateUniqueName(in), in, null, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps an {@link InputStream} to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param in the stream
   *
   * @return Returns a URL that can be passed to Humble's {@link Container}'s
   *         open method, and will result in IO being performed on the passed in
   *         streams.
   */

  public static String map(String url, InputStream in)
  {
    return map(url, in, null, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps an {@link OutputStream} to a URL for use by Humble.
   *
   * @param out the stream
   *
   * @return Returns a URL that can be passed to Humble's {@link Container}'s
   *         open method, and will result in IO being performed on the passed in
   *         streams.
   */

  public static String map(OutputStream out)
  {
    return map(generateUniqueName(out), null, out, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps an {@link OutputStream} to a URL for use by Humble.
   *
   * @param url the URL to use.
   * @param out the stream
   *
   * @return Returns a URL that can be passed to Humble's {@link Container}'s
   *         open method, and will result in IO being performed on the passed in
   *         streams.
   */

  public static String map(String url, OutputStream out)
  {
    return map(url, null, out, DEFAULT_UNMAP_URL_ON_OPEN,
        DEFAULT_CLOSE_STREAM_ON_CLOSE);
  }

  /**
   * Maps a {@link DataInput} or {@link DataOutput} object to a URL for use by
   * Humble.
   *
   * @param url the url
   * @param in the input to use
   * @param out the output to use
   * @param unmapOnOpen should we remove the mapping as soon as the resulting
   *        handler is opened?
   * @param closeOnClose should we call {@link Closeable#close()} on the
   *        underlying input or output objects when
   *        {@link IURLProtocolHandler#close()} is called (if supported by the
   *        underlying object)?
   * @return Returns a URL that can be passed to Humble's {@link Container}'s
   *         open method, and will result in IO being performed on the passed in
   *         streams.
   */

  public static String map(String url, DataInput in, DataOutput out,
      boolean unmapOnOpen, boolean closeOnClose)
  {
    return map(url, new DataInputOutputHandler(in, out, closeOnClose));
  }

  /**
   * Maps an {@link ReadableByteChannel} or {@link WritableByteChannel} to a URL
   * for use by Humble.
   *
   * @param url the url
   * @param in the input to use
   * @param out the output to use
   * @param unmapOnOpen should we remove the mapping as soon as the resulting
   *        handler is opened?
   * @param closeOnClose should we call {@link Closeable#close()} on the
   *        underlying input or output objects when
   *        {@link IURLProtocolHandler#close()} is called (if supported by the
   *        underlying object)?
   * @return Returns a URL that can be passed to Humble's {@link Container}'s
   *         open method, and will result in IO being performed on the passed in
   *         streams.
   */

  public static String map(String url, ReadableByteChannel in,
      WritableByteChannel out, boolean unmapOnOpen, boolean closeOnClose)
  {
    return map(url, new ReadableWritableChannelHandler(in, out, closeOnClose));
  }

  /**
   * Maps an {@link InputStream} or {@link OutputStream} to a URL for use by
   * Humble.
   *
   * @param url the url
   * @param in the input to use
   * @param out the output to use
   * @param unmapOnOpen should we remove the mapping as soon as the resulting
   *        handler is opened?
   * @param closeOnClose should we call {@link Closeable#close()} on the
   *        underlying input or output objects when
   *        {@link IURLProtocolHandler#close()} is called?
   *
   * @return Returns a URL that can be passed to Humble's {@link Container}'s
   *         open method, and will result in IO being performed on the passed in
   *         streams.
   */

  public static String map(String url, InputStream in, OutputStream out,
      boolean unmapOnOpen, boolean closeOnClose)
  {
    return map(url, new InputOutputStreamHandler(in, out, closeOnClose));
  }

  /**
   * Maps a {@link IURLProtocolHandler} to a url that Humble can open.
   *
   * @param url the unique string to use for the mapping.
   * @param handler the handler
   * @param unmapUrlOnOpen if true, when Humble opens the
   *        {@link IURLProtocolHandler}, {@link #unmap(String)} will be called
   *        automatically.
   * @return a string that is suitable for passing to {@link Container}'s open
   *         methods.
   */
  public static String map(String url, IURLProtocolHandler handler,
      boolean unmapUrlOnOpen)
  {
    if (mFactory.mapIO(url, handler, unmapUrlOnOpen) != null)
      throw new RuntimeException("url is already mapped: " + url);

    return DEFAULT_PROTOCOL + ":" + URLProtocolManager.getResourceFromURL(url);
  }

  /**
   * Undoes a URL to {@link InputStream} or {@link OutputStream} mapping.
   * Forwards to {@link #getFactory()}.{@link #unmapIO(String)}
   */
  public static IURLProtocolHandler unmap(String url)
  {
    return mFactory.unmapIO(url);
  }

  /**
   * Maps the given url or file name to the given {@link IURLProtocolHandler} or
   * so that Humble calls to open the URL it will call back to the handler.
   *
   *

   *
   * If you set unmapOnOpen to false, or you never actually open this mapping,
   * then you must ensure that you call {@link #unmapIO(String)} at some point
   * in time to remove the mapping, or we will hold onto references to the
   * handler you passed in.
   *
   *

   *
   * @param url A file or URL. If a URL, the protocol will be stripped off and
   *        replaced with {@link #DEFAULT_PROTOCOL} when registering.
   * @param handler An {@link IURLProtocolHandler} for the url
   * @param unmapUrlOnOpen If true, the handler will unmap itself after an
   *        {@link Container} opens the registered URL. If true, you do not
   *        need to call {@link #unmapIO(String)} for this url.
   * @return The previous handler for this url, or null if none.
   *
   *
   * @throws IllegalArgumentException if url is null or zero length
   * @throws IllegalArgumentException if handler is null
   */

  public IURLProtocolHandler mapIO(String url, IURLProtocolHandler handler,
      boolean unmapUrlOnOpen)
  {
    {
      if (url == null || url.length() <= 0)
        throw new IllegalArgumentException("must pass in non-zero url");
      if (handler == null)
      {
        throw new IllegalArgumentException("must pass in a non null handler");
      }
      String streamName = URLProtocolManager.getResourceFromURL(url);
      RegistrationInformation tuple = new RegistrationInformation(streamName,
          handler, unmapUrlOnOpen);
      RegistrationInformation oldTuple = mURLs.putIfAbsent(streamName, tuple);
      return oldTuple == null ? null : oldTuple.getHandler();
    }
  }

  /**
   * Unmaps a registration between a URL and the underlying i/o objects.
   *

   * If URL contains a protocol it is ignored when trying to find the matching
   * IO stream.
   *

   *
   * @param url The stream name to unmap
   * @return the {@link IURLProtocolHandler} that had been registered for that
   *         url, or null if none.
   */
  public IURLProtocolHandler unmapIO(String url)
  {
    if (url == null || url.length() <= 0)
      throw new IllegalArgumentException("must pass in non-zero url");
    String streamName = URLProtocolManager.getResourceFromURL(url);
    RegistrationInformation oldTuple = mURLs.remove(streamName);
    return oldTuple == null ? null : oldTuple.getHandler();
  }

  /**
   * {@inheritDoc}
   */

  public IURLProtocolHandler getHandler(String protocol, String url, int flags)
  {
    // Note: We need to remove any protocol markers from the url
    String streamName = URLProtocolManager.getResourceFromURL(url);

    RegistrationInformation tuple = mURLs.get(streamName);
    if (tuple != null)
    {
      IURLProtocolHandler handler = tuple.getHandler();
      if (tuple.isUnmappingOnOpen())
      {
        IURLProtocolHandler oldHandler = unmapIO(tuple.getName());

        // the unmapIO is an atomic operation
        if (handler != null && !handler.equals(oldHandler))
        {
          // someone already unmapped this stream
          log.error("stream {} already unmapped; it was likely already opened",

          tuple.getName());
          return null;
        }
      }
      return handler;
    }
    return null;
  }

  /**
   * A set of information about a registered handler.
   *
   * @author aclarke
   *
   */

  private static class RegistrationInformation
  {
    private final String mName;
    private final boolean mIsUnmappingOnOpen;
    private final IURLProtocolHandler mHandler;

    public RegistrationInformation(String streamName,
        IURLProtocolHandler handler, boolean unmapUrlOnOpen)
    {
      mName = streamName;
      mHandler = handler;
      mIsUnmappingOnOpen = unmapUrlOnOpen;
    }

    /**
     * The name of this handler registration, without any protocol.
     *
     * @return the name
     */
    public String getName()
    {
      return mName;
    }

    /**
     * Should the handler unmap the stream after it is opened?
     *
     * @return the decision
     */
    public boolean isUnmappingOnOpen()
    {
      return mIsUnmappingOnOpen;
    }

    public IURLProtocolHandler getHandler()
    {
      return mHandler;
    }
  }
}


/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.ferry;

/**
 * Internal Only

 *

 * C++ wrapper to SLF4J Java Logging frame work.

 *

 *

 * If not running inside a JVM, then this class

 * just does a rudimentary printout of log messages

 * to stderr.

 *

 *

 * Otherwise, it forwards to Java's SLF4J logging framework.

 *

 */
public class Logger {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>

  private volatile long swigCPtr;
  /**
   * Internal Only.
   */
  protected boolean swigCMemOwn;
  @SuppressWarnings("unused")
  private JNINativeFinalizer mUnusedVariableToAllowImports;

  /**
   * Internal Only.
   *
   * DO NOT USE: Do not allocate this method using new.  Not part of public API.
   *

   * Unfortunately this constructor is public because the internal
   * implementation needs it to be, but do not pass in values to this method
   * as you may end up crashing the virtual machine.
   *

   *
   * @param cPtr A C pointer to direct memory; did we mention don't call this.
   * @param cMemoryOwn I'm not even going to tell you.  Stop it.  Go away.
   *
   */
  protected Logger(long cPtr, boolean cMemoryOwn) {
    swigCPtr = cPtr;
    swigCMemOwn = cMemoryOwn;
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(Logger obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new IllegalStateException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Compares two values, returning true if the underlying objects in native code are the same object.
   *
   * That means you can have two different Java objects, but when you do a comparison, you'll find out
   * they are the EXACT same object.
   *
   * @return True if the underlying native object is the same.  False otherwise.
   */
  public boolean equals(Object obj) {
    boolean equal = false;
    if (obj instanceof Logger)
      equal = (((Logger)obj).swigCPtr == this.swigCPtr);
    return equal;
  }

  /**
   * Get a hashable value for this object.
   *
   * @return the hashable value.
   */
  public int hashCode() {
     return (int)swigCPtr;
  }

  /**
   * Internal Only.  Finalize this object.  Note this should only exists on non RefCounted objects.
   */
  protected void finalize()
  {
    delete();
  }

  // <<<<<<<<<<<<<<<<<<<<<<<<<<<
  // JNIHelper.swg: End generated code
  /**
   * Releases any underlying native memory and marks this object
   * as invalid.
   *

   * Normally Ferry manages when to release native memory.
   *

   *

   * In the unlikely event you want to control EXACTLY when a native
   * object is released, each Humble object has a {@link #delete()}
   * method that you can use. Once you call {@link #delete()},
   * you must ENSURE your object is never referenced again from
   * that Java object -- Ferry tries to help you avoid crashes if you
   * accidentally use an object after deletion but on this but we
   * cannot offer 100% protection (specifically if another thread
   *  is accessing that object EXACTLY when you {@link #delete()} it).
   *

   */

  public synchronized void delete() {
    if (swigCPtr != 0) {
      if (swigCMemOwn) {
        swigCMemOwn = false;
        FerryJNI.delete_Logger(swigCPtr);
      }
      swigCPtr = 0;
    }
  }

/**
 * Returns a new Logger object for this loggerName.

 *

 * @param aLoggerName A name (no spaces allowed) for this logger.

 *

 * @return a New logger for logging; caller must delete when done.
 */
  public static Logger getLogger(String aLoggerName) {
    long cPtr = FerryJNI.Logger_getLogger(aLoggerName);
    return (cPtr == 0) ? null : new Logger(cPtr, false);
  }

/**
 * Get a Logger object, but ask the Logger code to

 * free it up once the JavaVM shuts down.  Use at your

 * own risk.

 *

 * @param aLoggerName A name (no spaces allowed) for this logger.

 *

 * @return A new logger for logging; caller must not call delete

 *  and must not use the logger once the JavaVM (or main) has exited.
 */
  public static Logger getStaticLogger(String aLoggerName) {
    long cPtr = FerryJNI.Logger_getStaticLogger(aLoggerName);
    return (cPtr == 0) ? null : new Logger(cPtr, false);
  }

/**
 * Log the message to the logger, using sprintf() format

 * strings.

 *

 * @param filename The filename that is logging, or NULL.

 * @param lineNo The line number where this log statement is executed from.

 *  or 0.

 * @param level Level to log at.

 * @param format    A format specification string, in sprintf format.

 * @return if the message was actually logged.
 */
  public boolean log(String filename, int lineNo, Logger.Level level, String format) {
    return FerryJNI.Logger_log(swigCPtr, this, filename, lineNo, level.swigValue(), format);
  }

  public boolean error(String filename, int lineNo, String format) {
    return FerryJNI.Logger_error(swigCPtr, this, filename, lineNo, format);
  }

  public boolean warn(String filename, int lineNo, String format) {
    return FerryJNI.Logger_warn(swigCPtr, this, filename, lineNo, format);
  }

  public boolean info(String filename, int lineNo, String format) {
    return FerryJNI.Logger_info(swigCPtr, this, filename, lineNo, format);
  }

  public boolean debug(String filename, int lineNo, String format) {
    return FerryJNI.Logger_debug(swigCPtr, this, filename, lineNo, format);
  }

  public boolean trace(String filename, int lineNo, String format) {
    return FerryJNI.Logger_trace(swigCPtr, this, filename, lineNo, format);
  }

  public boolean isPrintStackTrace() {
    return FerryJNI.Logger_isPrintStackTrace(swigCPtr, this);
  }

  public void setPrintStackTrace(boolean value) {
    FerryJNI.Logger_setPrintStackTrace(swigCPtr, this, value);
  }

  public boolean isLogging(Logger.Level level) {
    return FerryJNI.Logger_isLogging(swigCPtr, this, level.swigValue());
  }

  public void setIsLogging(Logger.Level level, boolean value) {
    FerryJNI.Logger_setIsLogging(swigCPtr, this, level.swigValue(), value);
  }

  public static boolean isGlobalLogging(Logger.Level level) {
    return FerryJNI.Logger_isGlobalLogging(level.swigValue());
  }

  public static void setGlobalIsLogging(Logger.Level level, boolean value) {
    FerryJNI.Logger_setGlobalIsLogging(level.swigValue(), value);
  }

  public String getName() {
    return FerryJNI.Logger_getName(swigCPtr, this);
  }

  /**
   * Different logging levels (noiseness) supported by us.
   */
  public enum Level {
    LEVEL_ERROR(FerryJNI.Logger_LEVEL_ERROR_get()),
    LEVEL_WARN(FerryJNI.Logger_LEVEL_WARN_get()),
    LEVEL_INFO(FerryJNI.Logger_LEVEL_INFO_get()),
    LEVEL_DEBUG(FerryJNI.Logger_LEVEL_DEBUG_get()),
    LEVEL_TRACE(FerryJNI.Logger_LEVEL_TRACE_get()),
  ;

    public final int swigValue() {
      return swigValue;
    }

    public static Level swigToEnum(int swigValue) {
      Level[] swigValues = Level.class.getEnumConstants();
      if (swigValue < swigValues.length && swigValue >= 0 && swigValues[swigValue].swigValue == swigValue)
        return swigValues[swigValue];
      for (Level swigEnum : swigValues)
        if (swigEnum.swigValue == swigValue)
          return swigEnum;
      throw new IllegalArgumentException("No enum " + Level.class + " with value " + swigValue);
    }

    @SuppressWarnings("unused")
    private Level() {
      this.swigValue = SwigNext.next++;
    }

    @SuppressWarnings("unused")
    private Level(int swigValue) {
      this.swigValue = swigValue;
      SwigNext.next = swigValue+1;
    }

    @SuppressWarnings("unused")
    private Level(Level swigEnum) {
      this.swigValue = swigEnum.swigValue;
      SwigNext.next = this.swigValue+1;
    }

    private final int swigValue;

    private static class SwigNext {
      private static int next = 0;
    }
  }

}


/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.video;
import io.humble.ferry.*;
/**
 * A MediaAudioResampler object resamples MediaAudio objects from

 * one format/sample-rate/channel-layout to another.
 */
public class MediaAudioResampler extends MediaResampler {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>
  /**
   * This method is only here to use some references and remove
   * a Eclipse compiler warning.
   */
  @SuppressWarnings("unused")
  private void noop()
  {
    Buffer.make(null, 1);
  }

  private volatile long swigCPtr;

  /**
   * Internal Only.
   */
  protected MediaAudioResampler(long cPtr, boolean cMemoryOwn) {
    super(VideoJNI.MediaAudioResampler_SWIGUpcast(cPtr), cMemoryOwn);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.
   */
  protected MediaAudioResampler(long cPtr, boolean cMemoryOwn,
      java.util.concurrent.atomic.AtomicLong ref)
  {
    super(VideoJNI.MediaAudioResampler_SWIGUpcast(cPtr),
     cMemoryOwn, ref);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(MediaAudioResampler obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new IllegalStateException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Create a new MediaAudioResampler object that is actually referring to the
   * exact same underlying native object.
   *
   * @return the new Java object.
   */
  @Override
  public MediaAudioResampler copyReference() {
    if (swigCPtr == 0)
      return null;
    else
      return new MediaAudioResampler(swigCPtr, swigCMemOwn, getJavaRefCount());
  }

  /**
   * Compares two values, returning true if the underlying objects in native code are the same object.
   *
   * That means you can have two different Java objects, but when you do a comparison, you'll find out
   * they are the EXACT same object.
   *
   * @return True if the underlying native object is the same.  False otherwise.
   */
  public boolean equals(Object obj) {
    boolean equal = false;
    if (obj instanceof MediaAudioResampler)
      equal = (((MediaAudioResampler)obj).swigCPtr == this.swigCPtr);
    return equal;
  }

  /**
   * Get a hashable value for this object.
   *
   * @return the hashable value.
   */
  public int hashCode() {
     return (int)swigCPtr;
  }

  // <<<<<<<<<<<<<<<<<<<<<<<<<<<
  // JNIHelper.swg: End generated code

/**
 * Create a new MediaAudioResampler.
 */
  public static MediaAudioResampler make(AudioChannel.Layout outLayout, int outSampleRate, AudioFormat.Type outFormat, AudioChannel.Layout inLayout, int inSampleRate, AudioFormat.Type inFormat) {
    long cPtr = VideoJNI.MediaAudioResampler_make(outLayout.swigValue(), outSampleRate, outFormat.swigValue(), inLayout.swigValue(), inSampleRate, inFormat.swigValue());
    return (cPtr == 0) ? null : new MediaAudioResampler(cPtr, false);
  }

/**
 * Get output channel layout.
 */
  public AudioChannel.Layout getOutputLayout() {
    return AudioChannel.Layout.swigToEnum(VideoJNI.MediaAudioResampler_getOutputLayout(swigCPtr, this));
  }

/**
 * Get input channel layout.
 */
  public AudioChannel.Layout getInputLayout() {
    return AudioChannel.Layout.swigToEnum(VideoJNI.MediaAudioResampler_getInputLayout(swigCPtr, this));
  }

/**
 * Get output sample rate.
 */
  public int getOutputSampleRate() {
    return VideoJNI.MediaAudioResampler_getOutputSampleRate(swigCPtr, this);
  }

/**
 * Get input sample rate.
 */
  public int getInputSampleRate() {
    return VideoJNI.MediaAudioResampler_getInputSampleRate(swigCPtr, this);
  }

/**
 * Get output audio format.
 */
  public AudioFormat.Type getOutputFormat() {
    return AudioFormat.Type.swigToEnum(VideoJNI.MediaAudioResampler_getOutputFormat(swigCPtr, this));
  }

/**
 * Get input audio format.
 */
  public AudioFormat.Type getInputFormat() {
    return AudioFormat.Type.swigToEnum(VideoJNI.MediaAudioResampler_getInputFormat(swigCPtr, this));
  }

/**
 * Get number of input channels (derived from #getInputLayout()).
 */
  public int getInputChannels() {
    return VideoJNI.MediaAudioResampler_getInputChannels(swigCPtr, this);
  }

/**
 * Get number of output channels (derived from #getOutputLayout()).
 */
  public int getOutputChannels() {
    return VideoJNI.MediaAudioResampler_getOutputChannels(swigCPtr, this);
  }

/**
 * Opens the resampler so it can be ready for resampling.

 * You should NOT set options after you open this object.
 */
  public void open() {
    VideoJNI.MediaAudioResampler_open(swigCPtr, this);
  }

/**
 * Convert audio.

 *

 * in can be set to null to flush the last few samples out at the

 * end.

 *

 * If more input is provided than output space then the input will be buffered.

 * You can avoid this buffering by providing more output space than input.

 * Conversion will run directly without copying whenever possible.

 *

 * @param out       output audio object. caller is responsible for making the correct size.

 * @param in        input audio.

 *

 * @return number of samples output per channel.

 * @throws RuntimeError if we get an error or InvalidArgument if the attributes of

 *   in or out do not match what this resampler expected.
 */
  public int resample(MediaSampled out, MediaSampled in) {
    return VideoJNI.MediaAudioResampler_resample(swigCPtr, this, MediaSampled.getCPtr(out), out, MediaSampled.getCPtr(in), in);
  }

  public int resampleAudio(MediaAudio out, MediaAudio in) {
    return VideoJNI.MediaAudioResampler_resampleAudio(swigCPtr, this, MediaAudio.getCPtr(out), out, MediaAudio.getCPtr(in), in);
  }

/**
 * Convert the next timestamp from input to output

 * timestamps are in 1/(in_sample_rate * out_sample_rate) units.

 *

 * Note: There are 2 slightly differently behaving modes.

 *       First is when automatic timestamp compensation is not used, (min_compensation >= FLT_MAX)

 *              in this case timestamps will be passed through with delays compensated

 *       Second is when automatic timestamp compensation is used, (min_compensation < FLT_MAX)

 *              in this case the output timestamps will match output sample numbers

 *

 * @param pts   timestamp for the next input sample, INT64_MIN if unknown

 * @return the output timestamp for the next output sample
 */
  public long getNextPts(long pts) {
    return VideoJNI.MediaAudioResampler_getNextPts(swigCPtr, this, pts);
  }

/**
 * Activate resampling compensation.
 */
  public void setCompensation(int sample_delta, int compensation_distance) {
    VideoJNI.MediaAudioResampler_setCompensation(swigCPtr, this, sample_delta, compensation_distance);
  }

/**
 * Set a customized input channel mapping.

 *

 *

 * @return AVERROR error code in case of failure.

 *

 * Set a customized remix matrix.

 *

 *

 *

 * @return AVERROR error code in case of failure.

 *

 * Drops the specified number of output samples.

 * @return # of samples dropped.
 */
  public int dropOutput(int count) {
    return VideoJNI.MediaAudioResampler_dropOutput(swigCPtr, this, count);
  }

/**
 * Injects the specified number of silence samples.

 * @return # of samples injected.
 */
  public int injectSilence(int count) {
    return VideoJNI.MediaAudioResampler_injectSilence(swigCPtr, this, count);
  }

/**
 * Gets the delay the next input sample will experience relative to the next output sample.

 *

 * The resampler can buffer data if more input has been provided than available

 * output space, also converting between sample rates needs a delay.

 * This function returns the sum of all such delays.

 * The exact delay is not necessarily an integer value in either input or

 * output sample rate. Especially when downsampling by a large value, the

 * output sample rate may be a poor choice to represent the delay, similarly

 * for upsampling and the input sample rate.

 *

 * @param base  timebase in which the returned delay will be

 *              if its set to 1 the returned delay is in seconds

 *              if its set to 1000 the returned delay is in milli seconds

 *              if its set to the input sample rate then the returned delay is in input samples

 *              if its set to the output sample rate then the returned delay is in output samples

 *              an exact rounding free delay can be found by using LCM(in_sample_rate, out_sample_rate)

 * @return the delay in 1/base units.
 */
  public long getDelay(long base) {
    return VideoJNI.MediaAudioResampler_getDelay(swigCPtr, this, base);
  }

/**
 * Returns the number of resampled samples (rounded up) that would

 * be required when resampling a given number of samples.

 * That was a mouthful, yes? So here's the way to think of this. If your input

 * audio is at 48000 hz, and you pass in 0.5 seconds of audio, that's 24,000 input samples.

 * But if you're resampling to 22050 hz, then 0.5 seconds of audio is 11,025 output samples.

 * So getNumResampledSamples(24000) would return 11025 if the input

 * sample rate was 48,000 and hte output was 22,050.
 */
  public int getNumResampledSamples(int numSamples) {
    return VideoJNI.MediaAudioResampler_getNumResampledSamples(swigCPtr, this, numSamples);
  }

/**
 * Get the timebase used when outputting time stamps for audio.

 *

 * Defaults to 1 / (the lowest common multiple of getInputSampleRate()

 *   and getOutputSampleRate()) in order to ensure that no rounding

 *   of time stamps occur.

 *

 * For example, if the input sample rate is 22050 and the output sample

 * rate is 44100, then the output time base will be (1/44100). But if the

 * input sample rate is 48000 and the output sample rate is 22050, then

 * the output time base will be (1/lcm(48000,22050)) which will be 1/7056000

 * (trust me). This is done so that timestamp values do not get rounded (and

 * therefore introduce drift).
 */
  public Rational getTimeBase() {
    long cPtr = VideoJNI.MediaAudioResampler_getTimeBase(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * Set the timebase to use for timestamps on output audio.

 *

 * @throws InvalidArgument if null.
 */
  public void setTimeBase(Rational rational) {
    VideoJNI.MediaAudioResampler_setTimeBase(swigCPtr, this, Rational.getCPtr(rational), rational);
  }

  public MediaResampler.State getState() {
    return MediaResampler.State.swigToEnum(VideoJNI.MediaAudioResampler_getState(swigCPtr, this));
  }

}


/* ----------------------------------------------------------------------------
 * This file was automatically generated by SWIG (http://www.swig.org).
 * Version 2.0.6
 *
 * Do not make changes to this file unless you know what you are doing--modify
 * the SWIG interface file instead.
 * ----------------------------------------------------------------------------- */

package io.humble.video;
import io.humble.ferry.*;
/**
 * Represents a stream of similar data (eg video) in a Container.

 *

 * Streams are really virtual concepts; Container objects really just contain

 * a bunch of Packets.  But each Packet usually has a stream

 * id associated with it, and all Packets with that stream id represent

 * the same type of (usually time-based) data.  For example in many FLV

 * video files, there is a stream with id "0" that contains all video data, and

 * a stream with id "1" that contains all audio data.

 *

 * You use an Stream object to get properly configured Decoders

 * for decoding, and to tell Encoders how to encode Packets when

 * decoding.

 *

 */
public class ContainerStream extends RefCounted {
  // JNIHelper.swg: Start generated code
  // >>>>>>>>>>>>>>>>>>>>>>>>>>>
  /**
   * This method is only here to use some references and remove
   * a Eclipse compiler warning.
   */
  @SuppressWarnings("unused")
  private void noop()
  {
    Buffer.make(null, 1);
  }

  private volatile long swigCPtr;

  /**
   * Internal Only.
   */
  protected ContainerStream(long cPtr, boolean cMemoryOwn) {
    super(VideoJNI.ContainerStream_SWIGUpcast(cPtr), cMemoryOwn);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.
   */
  protected ContainerStream(long cPtr, boolean cMemoryOwn,
      java.util.concurrent.atomic.AtomicLong ref)
  {
    super(VideoJNI.ContainerStream_SWIGUpcast(cPtr),
     cMemoryOwn, ref);
    swigCPtr = cPtr;
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that obj is proxying for.
   *
   * @param obj The java proxy object for a native object.
   * @return The raw pointer obj is proxying for.
   */
  protected static long getCPtr(ContainerStream obj) {
    if (obj == null) return 0;
    return obj.getMyCPtr();
  }

  /**
   * Internal Only.  Not part of public API.
   *
   * Get the raw value of the native object that we're proxying for.
   *
   * @return The raw pointer we're proxying for.
   */
  protected long getMyCPtr() {
    if (swigCPtr == 0) throw new IllegalStateException("underlying native object already deleted");
    return swigCPtr;
  }

  /**
   * Create a new ContainerStream object that is actually referring to the
   * exact same underlying native object.
   *
   * @return the new Java object.
   */
  @Override
  public ContainerStream copyReference() {
    if (swigCPtr == 0)
      return null;
    else
      return new ContainerStream(swigCPtr, swigCMemOwn, getJavaRefCount());
  }

  /**
   * Compares two values, returning true if the underlying objects in native code are the same object.
   *
   * That means you can have two different Java objects, but when you do a comparison, you'll find out
   * they are the EXACT same object.
   *
   * @return True if the underlying native object is the same.  False otherwise.
   */
  public boolean equals(Object obj) {
    boolean equal = false;
    if (obj instanceof ContainerStream)
      equal = (((ContainerStream)obj).swigCPtr == this.swigCPtr);
    return equal;
  }

  /**
   * Get a hashable value for this object.
   *
   * @return the hashable value.
   */
  public int hashCode() {
     return (int)swigCPtr;
  }

  // <<<<<<<<<<<<<<<<<<<<<<<<<<<
  // JNIHelper.swg: End generated code

  /**
   * Get an ordered sequence of index entries in this {@link Stream}.
   *
   * @return A list of entries.  Will always return a non-null
   *   list, but if there are no entries the list size will be zero.
   */
  public java.util.List<IndexEntry> getIndexEntries()
  {
    final int numEntries = getNumIndexEntries();
    java.util.List<IndexEntry> retval = new java.util.ArrayList<IndexEntry>(Math.max(numEntries, 10));
    for(int i = 0; i < numEntries; i++) {
      final IndexEntry entry = getIndexEntry(i);
      if (entry != null) {
       retval.add(entry);
      }
    }

    return retval;
  }

/**
 * Get the relative position this stream has in the hosting

 * Container object.

 * @return The Index within the Container of this stream.
 */
  public int getIndex() {
    return VideoJNI.ContainerStream_getIndex(swigCPtr, this);
  }

/**
 * Return a container format specific id for this stream.

 * @return The (container format specific) id of this stream.
 */
  public int getId() {
    return VideoJNI.ContainerStream_getId(swigCPtr, this);
  }

/**
 * Get the (sometimes estimated) average frame rate of this container.

 * For variable frame-rate containers (they do exist) this is just

 * an approximation.  Better to use getTimeBase().

 *

 * For contant frame-rate containers, this will be 1 / ( getTimeBase() )

 *

 * @return The frame-rate of this container.
 */
  public Rational getFrameRate() {
    long cPtr = VideoJNI.ContainerStream_getFrameRate(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * The time base in which all timestamps (e.g. Presentation Time Stamp (PTS)

 * and Decompression Time Stamp (DTS)) are represented.  For example

 * if the time base is 1/1000, then the difference between a PTS of 1 and

 * a PTS of 2 is 1 millisecond.  If the timebase is 1/1, then the difference

 * between a PTS of 1 and a PTS of 2 is 1 second.

 *

 * @return The time base of this stream.
 */
  public Rational getTimeBase() {
    long cPtr = VideoJNI.ContainerStream_getTimeBase(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * Return the start time, in #getTimeBase() units, when this stream

 * started.

 * @return The start time.
 */
  public long getStartTime() {
    return VideoJNI.ContainerStream_getStartTime(swigCPtr, this);
  }

/**
 * Return the duration, in #getTimeBase() units, of this stream,

 * or Global#NO_PTS if unknown.

 * @return The duration (in getTimeBase units) of this stream, if known.
 */
  public long getDuration() {
    return VideoJNI.ContainerStream_getDuration(swigCPtr, this);
  }

/**
 * The current Decompression Time Stamp that will be used on this stream,

 * in #getTimeBase() units.

 * @return The current Decompression Time Stamp that will be used on this stream.
 */
  public long getCurrentDts() {
    return VideoJNI.ContainerStream_getCurrentDts(swigCPtr, this);
  }

/**
 * Get the number of index entries in this stream.

 * @return The number of index entries in this stream.

 * @see #getIndexEntry(int)
 */
  public int getNumIndexEntries() {
    return VideoJNI.ContainerStream_getNumIndexEntries(swigCPtr, this);
  }

/**
 * Returns the number of encoded frames if known.  Note that frames here means

 * encoded frames, which can consist of many encoded audio samples, or

 * an encoded video frame.

 *

 * @return The number of frames (encoded) in this stream.
 */
  public long getNumFrames() {
    return VideoJNI.ContainerStream_getNumFrames(swigCPtr, this);
  }

/**
 * Gets the sample aspect ratio.

 *

 * @return The sample aspect ratio.
 */
  public Rational getSampleAspectRatio() {
    long cPtr = VideoJNI.ContainerStream_getSampleAspectRatio(swigCPtr, this);
    return (cPtr == 0) ? null : new Rational(cPtr, false);
  }

/**
 * Get the underlying container for this stream, or null if Humble Video

 * doesn't know.

 *

 * @return the container, or null if we don't know.
 */
  public Container getContainer() {
    long cPtr = VideoJNI.ContainerStream_getContainer(swigCPtr, this);
    return (cPtr == 0) ? null : new Container(cPtr, false);
  }

/**
 * Get how the decoding codec should parse data from this stream.

 * @return the parse type.
 */
  public ContainerStream.ParseType getParseType() {
    return ContainerStream.ParseType.swigToEnum(VideoJNI.ContainerStream_getParseType(swigCPtr, this));
  }

/**
 * Set the parse type the decoding codec should use.  Set to

 * ParseType#PARSE_NONE if you don't want any parsing

 * to be done.

 *

 * Warning: do not set this flag unless you know what you're doing,

 * and do not set after you've started reading packets.

 *

 *

 * @param type The type to set.
 */
  public void setParseType(ContainerStream.ParseType type) {
    VideoJNI.ContainerStream_setParseType(swigCPtr, this, type.swigValue());
  }

/**
 * Get the KeyValueBag for this object,

 * or null if none.

 *

 * If the Container or Stream object

 * that this KeyValueBag came from was opened

 * for reading, then changes via KeyValueBag#setValue(String, String)

 * will have no effect on the underlying media.

 *

 *

 * If the Container or Stream object

 * that this KeyValueBag came from was opened

 * for writing, then changes via KeyValueBag#setValue(String, String)

 * will have no effect after Container#writeHeader()

 * is called.

 *

 * @return the KeyValueBag.
 */
  public KeyValueBag getMetaData() {
    long cPtr = VideoJNI.ContainerStream_getMetaData(swigCPtr, this);
    return (cPtr == 0) ? null : new KeyValueBag(cPtr, false);
  }

/**
 * Search for the given time stamp in the key-frame index for this Stream.

 *

 * Not all ContainerFormat implementations

 * maintain key frame indexes, but if they have one,

 * then this method searches in the Stream index

 * to quickly find the byte-offset of the nearest key-frame to

 * the given time stamp.

 *

 * @param wantedTimeStamp the time stamp wanted, in the stream's

 *                        time base units.

 * @param flags A bitmask of the SEEK_FLAG_* flags, or 0 to turn

 *              all flags off.  If Container#SEEK_FLAG_BACKWARDS then the returned

 *              index will correspond to the time stamp which is <=

 *              the requested one (not supported by all demuxers).

 *              If Container#SEEK_FLAG_BACKWARDS is not set then it will be >=.

 *              if Container#SEEK_FLAG_ANY seek to any frame, only

 *              keyframes otherwise (not supported by all demuxers).

 * @return The IndexEntry for the nearest appropriate timestamp

 *   in the index, or null if it can't be found.
 */
  public IndexEntry findTimeStampEntryInIndex(long wantedTimeStamp, int flags) {
    long cPtr = VideoJNI.ContainerStream_findTimeStampEntryInIndex(swigCPtr, this, wantedTimeStamp, flags);
    return (cPtr == 0) ? null : new IndexEntry(cPtr, false);
  }

/**
 * Search for the given time stamp in the key-frame index for this Stream.

 *

 * Not all ContainerFormat implementations

 * maintain key frame indexes, but if they have one,

 * then this method searches in the Stream index

 * to quickly find the index entry position of the nearest key-frame to

 * the given time stamp.

 *

 * @param wantedTimeStamp the time stamp wanted, in the stream's

 *                        time base units.

 * @param flags A bitmask of the SEEK_FLAG_* flags, or 0 to turn

 *              all flags off.  If Container#SEEK_FLAG_BACKWARDS then the returned

 *              index will correspond to the time stamp which is <=

 *              the requested one (not supported by all demuxers).

 *              If Container#SEEK_FLAG_BACKWARDS is not set then it will be >=.

 *              if Container#SEEK_FLAG_ANY seek to any frame, only

 *              keyframes otherwise (not supported by all demuxers).

 * @return The position in this Stream index, or -1 if it cannot

 *   be found or an index is not maintained.

 * @see #getIndexEntry(int)
 */
  public int findTimeStampPositionInIndex(long wantedTimeStamp, int flags) {
    return VideoJNI.ContainerStream_findTimeStampPositionInIndex(swigCPtr, this, wantedTimeStamp, flags);
  }

/**
 * Get the IndexEntry at the given position in this

 * Stream object's index.

 *

 * Not all ContainerFormat types maintain

 * Stream indexes, but if they do,

 * this method can return those entries.

 *

 *

 * Do not modify the Container this stream

 * is from between calls to this method and

 * #getNumIndexEntries() as indexes may

 * be compacted while processing.

 *

 * @param position The position in the index table.
 */
  public IndexEntry getIndexEntry(int position) {
    long cPtr = VideoJNI.ContainerStream_getIndexEntry(swigCPtr, this, position);
    return (cPtr == 0) ? null : new IndexEntry(cPtr, false);
  }

/**
 * Get the Stream.Disposition of this stream.
 */
  public ContainerStream.Disposition getDisposition() {
    return ContainerStream.Disposition.swigToEnum(VideoJNI.ContainerStream_getDisposition(swigCPtr, this));
  }

/**
 * For containers with Stream.Disposition.DISPOSITION_ATTACHED_PIC,

 * this returns a read-only copy of the packet containing the

 * picture (needs to be decoded separately).
 */
  public MediaPacket getAttachedPic() {
    long cPtr = VideoJNI.ContainerStream_getAttachedPic(swigCPtr, this);
    return (cPtr == 0) ? null : new MediaPacket(cPtr, false);
  }

/**
 * Check if the stream is matched by the stream specifier.

 *

 * See the "stream specifiers" chapter in the FFmpeg documentation for the syntax

 * of specifier: https://www.ffmpeg.org/ffmpeg.html#Stream-selection

 *

 * @param specifier the specifier string

 * @return true if this stream is matched by specifier; false if this stream is not

 *   matched by specifier;

 *

 * @throws InvalidArgument if the specifier is invalid.

 *

 * Note: A stream specifier can match several streams in a container.
 */
  public boolean matchSpecifier(String specifier) {
    return VideoJNI.ContainerStream_matchSpecifier(swigCPtr, this, specifier);
  }

  /**
   * The disposition of this stream. Some streams can have special

   * meanings in some Containers.
   */
  public enum Disposition {
    DISPOSITION_NONE(VideoJNI.ContainerStream_DISPOSITION_NONE_get()),
    DISPOSITION_DEFAULT(VideoJNI.ContainerStream_DISPOSITION_DEFAULT_get()),
    DISPOSITION_DUB(VideoJNI.ContainerStream_DISPOSITION_DUB_get()),
    DISPOSITION_ORIGINAL(VideoJNI.ContainerStream_DISPOSITION_ORIGINAL_get()),
    DISPOSITION_COMMENT(VideoJNI.ContainerStream_DISPOSITION_COMMENT_get()),
    DISPOSITION_LYRICS(VideoJNI.ContainerStream_DISPOSITION_LYRICS_get()),
    DISPOSITION_KARAOKE(VideoJNI.ContainerStream_DISPOSITION_KARAOKE_get()),
  /**
   * Track should be used during playback by default.

   * Useful for subtitle track that should be displayed

   * even when user did not explicitly ask for subtitles.
   */
    DISPOSITION_FORCED(VideoJNI.ContainerStream_DISPOSITION_FORCED_get()),
  /**
   * stream for hearing impaired audiences
   */
    DISPOSITION_HEARING_IMPAIRED(VideoJNI.ContainerStream_DISPOSITION_HEARING_IMPAIRED_get()),
  /**
   * stream for visual impaired audiences  stream without voice
   */
    DISPOSITION_VISUAL_IMPAIRED(VideoJNI.ContainerStream_DISPOSITION_VISUAL_IMPAIRED_get()),
    DISPOSITION_CLEAN_EFFECTS(VideoJNI.ContainerStream_DISPOSITION_CLEAN_EFFECTS_get()),
  /**
   * The stream is stored in the file as an attached picture/"cover art" (e.g.

   * APIC frame in ID3v2). The single packet associated with it will be returned

   * among the first few packets read from the file unless seeking takes place.

   * It can also be accessed at any time in #getAttachedPic().
   */
    DISPOSITION_ATTACHED_PIC(VideoJNI.ContainerStream_DISPOSITION_ATTACHED_PIC_get()),
  ;

    public final int swigValue() {
      return swigValue;
    }

    public static Disposition swigToEnum(int swigValue) {
      Disposition[] swigValues = Disposition.class.getEnumConstants();
      if (swigValue < swigValues.length && swigValue >= 0 && swigValues[swigValue].swigValue == swigValue)
        return swigValues[swigValue];
      for (Disposition swigEnum : swigValues)
        if (swigEnum.swigValue == swigValue)
          return swigEnum;
      throw new IllegalArgumentException("No enum " + Disposition.class + " with value " + swigValue);
    }

    @SuppressWarnings("unused")
    private Disposition() {
      this.swigValue = SwigNext.next++;
    }

    @SuppressWarnings("unused")
    private Disposition(int swigValue) {
      this.swigValue = swigValue;
      SwigNext.next = swigValue+1;
    }

    @SuppressWarnings("unused")
    private Disposition(Disposition swigEnum) {
      this.swigValue = swigEnum.swigValue;
      SwigNext.next = this.swigValue+1;
    }

    private final int swigValue;

    private static class SwigNext {
      private static int next = 0;
    }
  }

  /**
   * What types of parsing can we do on a call to

   * Source#read(Packet)
   */
  public enum ParseType {
  /**
   * No special instructions
   */
    PARSE_NONE(VideoJNI.ContainerStream_PARSE_NONE_get()),
  /**
   * full parsing and repack
   */
    PARSE_FULL(VideoJNI.ContainerStream_PARSE_FULL_get()),
  /**
   * Only parse headers, do not repack.
   */
    PARSE_HEADERS(VideoJNI.ContainerStream_PARSE_HEADERS_get()),
  /**
   * full parsing and interpolation of timestamps for frames not starting on a packet boundary
   */
    PARSE_TIMESTAMPS(VideoJNI.ContainerStream_PARSE_TIMESTAMPS_get()),
  /**
   * full parsing and repack of the first frame only, only implemented for H.264 currently
   */
    PARSE_FULL_ONCE(VideoJNI.ContainerStream_PARSE_FULL_ONCE_get()),
  /**
   * full parsing and repack with timestamp and position generation by parser for raw

   * this assumes that each packet in the file contains no demuxer level headers and

   * just codec level data, otherwise position generation would fail
   */
    PARSE_FULL_RAW(VideoJNI.ContainerStream_PARSE_FULL_RAW_get()),
  ;

    public final int swigValue() {
      return swigValue;
    }

    public static ParseType swigToEnum(int swigValue) {
      ParseType[] swigValues = ParseType.class.getEnumConstants();
      if (swigValue < swigValues.length && swigValue >= 0 && swigValues[swigValue].swigValue == swigValue)
        return swigValues[swigValue];
      for (ParseType swigEnum : swigValues)
        if (swigEnum.swigValue == swigValue)
          return swigEnum;
      throw new IllegalArgumentException("No enum " + ParseType.class + " with value " + swigValue);
    }

    @SuppressWarnings("unused")
    private ParseType() {
      this.swigValue = SwigNext.next++;
    }

    @SuppressWarnings("unused")
    private ParseType(int swigValue) {
      this.swigValue = swigValue;
      SwigNext.next = swigValue+1;
    }

    @SuppressWarnings("unused")
    private ParseType(ParseType swigEnum) {
      this.swigValue = swigEnum.swigValue;
      SwigNext.next = this.swigValue+1;
    }

    private final int swigValue;

    private static class SwigNext {
      private static int next = 0;
    }
  }

}