
package com.frank.ffmpeg.metadata;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 *  Retrieve frame and extract metadata from an input media file.
 */
public class FFmpegMediaRetriever {

	static {
    	try {
    	    System.loadLibrary("media-handle");
        } catch (UnsatisfiedLinkError e) {
            Log.e("FFmpegMediaRetriever", "loadLibrary error=" + e.toString());
        }
    }

    // The field is accessed by native method
    private long mNativeRetriever;

    public FFmpegMediaRetriever() {
        native_init();
    	native_setup();
    }

    public void setDataSource(String path) {
        native_setDataSource(path);
    }
    
    /**
     * Sets the data source (FileDescriptor) to use. It is the caller's
     * responsibility to close the file descriptor. It is safe to do so as soon
     * as this call returns. Call this method before the rest of the methods in
     * this class. This method may be time-consuming.
     * 
     * @param fd the FileDescriptor for the file you want to play
     */
    public void setDataSource(FileDescriptor fd)
            throws IllegalArgumentException {
        if (fd == null) {
            return;
        }
        native_setDataSourceFD(fd, 0, 0x7ffffffffffffffL);
    }
    
    /**
     * Sets the data source as a content Uri. Call this method before 
     * the rest of the methods in this class. This method may be time-consuming.
     * 
     * @param context the Context to use when resolving the Uri
     * @param uri the Content URI of the data you want to play
     *
     */
    public void setDataSource(Context context, Uri uri) throws IllegalArgumentException, SecurityException {
        if (uri == null) {
            throw new IllegalArgumentException();
        }
        
        String scheme = uri.getScheme();
        if(scheme == null || scheme.equals("file")) {
            setDataSource(uri.getPath());
            return;
        }

        AssetFileDescriptor fd = null;
        try {
            ContentResolver resolver = context.getContentResolver();
            try {
                fd = resolver.openAssetFileDescriptor(uri, "r");
            } catch(FileNotFoundException e) {
                throw new IllegalArgumentException();
            }
            if (fd == null) {
                throw new IllegalArgumentException();
            }
            FileDescriptor descriptor = fd.getFileDescriptor();
            if (!descriptor.valid()) {
                throw new IllegalArgumentException();
            }
            if (fd.getDeclaredLength() < 0) {
                setDataSource(descriptor);
            } else {
                native_setDataSourceFD(descriptor, fd.getStartOffset(), fd.getDeclaredLength());
            }
            return;
        } catch (SecurityException ex) {
            ex.printStackTrace();
        } finally {
            try {
                if (fd != null) {
                    fd.close();
                }
            } catch(IOException ioEx) {
                ioEx.printStackTrace();
            }
        }
        setDataSource(uri.toString());
    }

    public String extractMetadata(String key) {
        if (key == null || key.isEmpty()) {
            return null;
        }
        return native_extractMetadata(key);
    }

    /**
     * Call this method after setDataSource(). This method finds a
     * representative frame close to the given time position by considering
     * the given option if possible, and returns it as a bitmap. This is
     * useful for generating a thumbnail for an input data source or just
     * obtain and display a frame at the given time position.
     *
     * @param timeUs The time position where the frame will be retrieved.
     *
     * @param option a hint on how the frame is found. Use
     * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
     * that has a timestamp earlier than or the same as timeUs. Use
     * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
     * that has a timestamp later than or the same as timeUs. Use
     * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
     * that has a timestamp closest to or the same as timeUs. Use
     * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
     * or may not be a sync frame but is closest to or the same as timeUs.
     * {@link #OPTION_CLOSEST} often has larger performance overhead compared
     * to the other options if there is no sync frame located at timeUs.
     *
     * @return A Bitmap containing a representative video frame, which
     *         can be null, if such a frame cannot be retrieved.
     */
    public Bitmap getFrameAtTime(long timeUs, int option) {
        if (option < OPTION_PREVIOUS_SYNC ||
                option > OPTION_CLOSEST) {
            throw new IllegalArgumentException("Unsupported option: " + option);
        }

        byte [] picture = native_getFrameAtTime(timeUs, option);
        if (picture != null) {
            return BitmapFactory.decodeByteArray(picture, 0, picture.length, null);
        }

        return null;
    }

    public Bitmap getFrameAtTime(long timeUs) {
        return getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC);
    }

    /**
     * Call this method after setDataSource(). This method finds a
     * representative frame close to the given time position by considering
     * the given option if possible, and returns it as a bitmap. This is
     * useful for generating a thumbnail for an input data source or just
     * obtain and display a frame at the given time position.
     *
     * @param timeUs The time position where the frame will be retrieved.
     *
     * @param option a hint on how the frame is found. Use
     * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
     * that has a timestamp earlier than or the same as timeUs. Use
     * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
     * that has a timestamp later than or the same as timeUs. Use
     * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
     * that has a timestamp closest to or the same as timeUs. Use
     * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
     * or may not be a sync frame but is closest to or the same as timeUs.
     * {@link #OPTION_CLOSEST} often has larger performance overhead compared
     * to the other options if there is no sync frame located at timeUs.
     *
     * @return A Bitmap containing a representative video frame, which
     *         can be null, if such a frame cannot be retrieved.
     */
    public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) {
        if (option < OPTION_PREVIOUS_SYNC ||
                option > OPTION_CLOSEST) {
            throw new IllegalArgumentException("Unsupported option: " + option);
        }

        byte [] picture = native_getScaleFrameAtTime(timeUs, option, width, height);
        if (picture != null) {
            return BitmapFactory.decodeByteArray(picture, 0, picture.length, null);
        }

        return null;
    }

    public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) {
        return getScaledFrameAtTime(timeUs, OPTION_CLOSEST_SYNC, width, height);
    }

    public Bitmap getAudioThumbnail() {
        byte[] picture = native_getAudioThumbnail();
        if (picture != null) {
            return BitmapFactory.decodeByteArray(picture, 0, picture.length, null);
        }

        return null;
    }

    public void release() {
        native_release();
    }

    private native void native_setup();

    private native void native_init();

    private native void native_setDataSource(String path) throws IllegalArgumentException;

    private native void native_setDataSourceFD(FileDescriptor fd, long offset, long length) throws IllegalArgumentException;

    private native String native_extractMetadata(String key);

    private native void native_setSurface(Object surface);

    private native byte[] native_getFrameAtTime(long timeUs, int option);

    private native byte[] native_getScaleFrameAtTime(long timeUs, int option, int width, int height);

    private native byte[] native_getAudioThumbnail();

    private native void native_release();

    /**
     * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
     * a sync (or key) frame associated with a data source that is located
     * right before or at the given time.
     *
     * @see #getFrameAtTime(long, int)
     */
    int OPTION_PREVIOUS_SYNC    = 0x00;
    /**
     * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
     * a sync (or key) frame associated with a data source that is located
     * right after or at the given time.
     *
     * @see #getFrameAtTime(long, int)
     */
    int OPTION_NEXT_SYNC        = 0x01;
    /**
     * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
     * a sync (or key) frame associated with a data source that is located
     * closest to (in time) or at the given time.
     *
     * @see #getFrameAtTime(long, int)
     */
    int OPTION_CLOSEST_SYNC     = 0x02;
    /**
     * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
     * a frame (not necessarily a key frame) associated with a data source that
     * is located closest to or at the given time.
     *
     * @see #getFrameAtTime(long, int)
     */
    int OPTION_CLOSEST          = 0x03;


    /**
     * The metadata key to retrieve the original name of the file.
     */
    public static String METADATA_KEY_FILENAME = "filename";
    /**
     * The metadata key to retrieve the main language.
     */
    public static String METADATA_KEY_LANGUAGE = "language";
    /**
     * The metadata key to retrieve the name.
     */
    public static String METADATA_KEY_TITLE = "title";
    /**
     * The metadata key to retrieve the general bitrate.
     */
    public static String METADATA_KEY__BITRATE = "bitrate";
    /**
     * The metadata key to retrieve the duration in milliseconds.
     */
    public static String METADATA_KEY_DURATION = "duration";
    /**
     * The metadata key to retrieve the audio codec.
     */
    public static String METADATA_KEY_AUDIO_CODEC = "audio_codec";
    /**
     * The metadata key to retrieve the video codec.
     */
    public static String METADATA_KEY_VIDEO_CODEC = "video_codec";
    /**
     * This key retrieves the video degree of rotation.
     * The video rotation angle may be 0, 90, 180, or 270 degrees.
     */
    public static String METADATA_KEY_VIDEO_ROTATION = "rotate";
    /**
     * This metadata key retrieves the average frame rate (in frames/sec).
     */
    public static String METADATA_KEY_FRAME_RATE = "frame_rate";
    /**
     * The metadata key to retrieve the file size in bytes.
     */
    public static String METADATA_KEY_FILE_SIZE = "file_size";
    /**
     * The metadata key to retrieve the video width.
     */
    public static String METADATA_KEY_VIDEO_WIDTH = "video_width";
    /**
     * The metadata key to retrieve the video height.
     */
    public static String METADATA_KEY_VIDEO_HEIGHT = "video_height";
    /**
     * The metadata key to retrieve the mime type.
     */
    public static String METADATA_KEY_MIME_TYPE = "mime_type";
    /**
     * The metadata key to retrieve sample rate.
     */
    public static String METADATA_KEY_SAMPLE_RATE = "sample_rate";
    /**
     * The metadata key to retrieve channel count.
     */
    public static String METADATA_KEY_CHANNEL_COUNT = "channel_count";
    /**
     * The metadata key to retrieve channel layout.
     */
    public static String METADATA_KEY_CHANNEL_LAYOUT = "channel_layout";
    /**
     * The metadata key to retrieve pixel format.
     */
    public static String METADATA_KEY_PIXEL_FORMAT = "pixel_format";

}
