diff --git a/README.md b/README.md index 4e20f08..ed8aa87 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,26 @@ # MediaMetadataRetrieverCompat -多媒体元数据兼容方案 - 支持获取视频缩略图、视频信息 +多媒体元数据兼容方案 - 支持获取图片、视频、音频文件的媒体信息、视频缩略图 ## __简介__ MediaMetadataRetrieverCompat 内部有两种实现(根据自身需求选择初始化方式)   `FFmpegMediaMetadataRetriever` -       基于[FFmpegMediaMetadataRetriever](https://github.com/wseemann/FFmpegMediaMetadataRetriever),体积大但取帧速度快 +       基于[FFmpegMediaMetadataRetriever](https://github.com/wseemann/FFmpegMediaMetadataRetriever),对视频资源有增强,但库体积较大 `MediaMetadataRetriever` -       基于原生API,不会增加apk体积但取帧慢 +       基于原生API ## __示例apk__ -![](screenshot/example-download.png) +![](screenshot/example-download_1.0.8.png) ## __效果演示__ -![](screenshot/screenshot_auto.gif) -![](screenshot/screenshot_ffmpeg.gif) -![](screenshot/screenshot_android.gif) +![](screenshot/screenshot.gif) ## __快速开始__ ``` //核心库 必选 -implementation 'com.dyhdyh.compat.mmrc:media-metadata-retriever-compat:1.0.7' +implementation 'com.dyhdyh.compat.mmrc:media-metadata-retriever-compat:1.0.8' //当需要FFmpegMediaMetadataRetriever时必选 implementation 'com.dyhdyh.remake:FFmpegMediaMetadataRetriever-java:1.0.14' @@ -50,7 +48,7 @@ MediaMetadataRetrieverCompat mmrc = MediaMetadataRetrieverCompat.create(); //本地文件 mmrc.setDataSource(inputFile); -//网络视频(建议放在子线程) +//网络资源(需要放在子线程,网络操作将会交给API处理,请慎用,建议自行下载后设置File) mmrc.setDataSource(url, headers); //Uri @@ -72,8 +70,8 @@ String framerate = mmrc.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KE ... ``` -## __获取本地视频缩略图__ -耗时操作,请放在子线程 +## __获取缩略图__ +耗时操作,请放在子线程,获取到的缩略图会根据资源信息自动旋转 ``` //获取第一帧原尺寸图片 @@ -84,7 +82,4 @@ mmrc.getFrameAtTime(timeUs, option); //获取指定位置指定宽高的缩略图 mmrc.getScaledFrameAtTime(timeUs, MediaMetadataRetrieverCompat.OPTION_CLOSEST, width, height); - -//获取指定位置指定宽高并且旋转的缩略图 -mmrc.getScaledFrameAtTime(timeUs, MediaMetadataRetrieverCompat.OPTION_CLOSEST, width, height, rotate); ``` diff --git a/config.gradle b/config.gradle index 3e91829..69df821 100644 --- a/config.gradle +++ b/config.gradle @@ -1,8 +1,8 @@ ext { android = [ - versionCode : 8, - versionName : "1.0.7", + versionCode : 9, + versionName : "1.0.8", compileSdkVersion: 26, buildToolsVersion: "26.0.2", @@ -21,7 +21,7 @@ ext { //项目git地址 gitUrl : "https://github.com/dengyuhan/MediaMetadataRetrieverCompat.git", //项目描述 - description: "多媒体元数据兼容方案 - 支持获取视频缩略图、视频信息", + description: "多媒体元数据兼容方案 - 支持获取图片、视频、音频文件的媒体信息、视频缩略图", //开发者的一些基本信息 authorId : "dengyuhan", authorName : "dengyuhan", diff --git a/example/build.gradle b/example/build.gradle index 177c8e8..4591167 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -33,5 +33,6 @@ dependencies { implementation 'com.dyhdyh.remake:FFmpegMediaMetadataRetriever-java:1.0.14' implementation 'com.dyhdyh.remake:FFmpegMediaMetadataRetriever-armeabi-v7a:1.0.14' + implementation 'com.android.support:exifinterface:' + rootProject.ext.android.supportVersion compile project(':media-metadata-retriever-compat') } diff --git a/example/src/main/assets/test.gif b/example/src/main/assets/test.gif new file mode 100644 index 0000000..8f52c7e Binary files /dev/null and b/example/src/main/assets/test.gif differ diff --git a/example/src/main/assets/test.jpg b/example/src/main/assets/test.jpg new file mode 100644 index 0000000..c679830 Binary files /dev/null and b/example/src/main/assets/test.jpg differ diff --git a/example/src/main/java/com/dyhdyh/compat/mmrc/example/MainActivity.java b/example/src/main/java/com/dyhdyh/compat/mmrc/example/MainActivity.java index af6b63b..27a11db 100644 --- a/example/src/main/java/com/dyhdyh/compat/mmrc/example/MainActivity.java +++ b/example/src/main/java/com/dyhdyh/compat/mmrc/example/MainActivity.java @@ -43,11 +43,11 @@ public class MainActivity extends AppCompatActivity implements RadioGroup.OnChec RecyclerView rv; View layout_info; - private File testFile; - private ThumbnailAdapter mThumbnailAdapter; private MediaMetadataRetrieverCompat mmrc; + private Disposable mThumbnailDisposable; + private SimpleDateFormat mDateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); @Override @@ -68,15 +68,15 @@ protected void onCreate(Bundle savedInstanceState) { AssetsManager.copyAsset(this, new AssetFile(), getExternalCacheDir()); - testFile = new File(getExternalCacheDir(), "test.mp4"); - mmrc = MediaMetadataRetrieverCompat.create(); //mmrc = MediaMetadataRetrieverCompat.create(MediaMetadataRetrieverCompat.RETRIEVER_FFMPEG); //mmrc = MediaMetadataRetrieverCompat.create(MediaMetadataRetrieverCompat.RETRIEVER_ANDROID); + + } public void clickMediaMetadata(View v) { - final String path = TextUtils.isEmpty(ed.getText()) ? testFile.getAbsolutePath() : ed.getText().toString(); + final String path = TextUtils.isEmpty(ed.getText()) ? new File(getExternalCacheDir(), "test.mp4").getAbsolutePath() : ed.getText().toString(); //这里示例用子线程 实际开发中根据需求 Observable.create(new ObservableOnSubscribe() { @@ -89,14 +89,6 @@ public void subscribe(@NonNull ObservableEmitter s) throws Exception { } else { mmrc.setDataSource(new File(path)); } - //获取缩略图 - long duration = mmrc.extractMetadataLong(MediaMetadataRetrieverCompat.METADATA_KEY_DURATION); - int width = mmrc.extractMetadataInt(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_WIDTH); - int height = mmrc.extractMetadataInt(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_HEIGHT); - int thumbnailCount = (int) Math.max(1, Math.min(10, duration / 1000)); - int thumbnailWidth = getResources().getDimensionPixelSize(R.dimen.thumbnail_size); - final int thumbnailHeight = (int) ((float) thumbnailWidth / width * height); - buildThumbnail(thumbnailCount, thumbnailWidth, thumbnailHeight); s.onNext(buildMetadataInfo()); } catch (Exception e) { e.printStackTrace(); @@ -114,6 +106,15 @@ public void onSubscribe(Disposable d) { @Override public void onNext(String s) { + //获取缩略图 + long duration = mmrc.extractMetadataLong(MediaMetadataRetrieverCompat.METADATA_KEY_DURATION); + int width = mmrc.extractMetadataInt(MediaMetadataRetrieverCompat.METADATA_KEY_WIDTH); + int height = mmrc.extractMetadataInt(MediaMetadataRetrieverCompat.METADATA_KEY_HEIGHT); + int thumbnailCount = (int) Math.max(1, Math.min(10, duration / 1000)); + int thumbnailWidth = getResources().getDimensionPixelSize(R.dimen.thumbnail_size); + final int thumbnailHeight = (int) ((float) thumbnailWidth / width * height); + buildThumbnail(thumbnailCount, thumbnailWidth, thumbnailHeight); + LoadingBar.cancel(layout_info); tv.setText(s); } @@ -132,12 +133,12 @@ private String buildMetadataInfo() { sb.append("API类型:"); sb.append(mmrc.getMediaMetadataRetriever().getClass().getSimpleName()); sb.append("\n"); - String width = mmrc.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_WIDTH); + String width = mmrc.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KEY_WIDTH); if (width != null) { sb.append("\n宽:"); sb.append(width); } - String height = mmrc.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_HEIGHT); + String height = mmrc.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KEY_HEIGHT); if (height != null) { sb.append("\n高:"); sb.append(height); @@ -150,7 +151,7 @@ private String buildMetadataInfo() { sb.append(duration); sb.append("毫秒)"); } - String rotation = mmrc.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_ROTATION); + String rotation = mmrc.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KEY_ROTATION); if (rotation != null) { sb.append("\n旋转角度:"); sb.append(rotation); @@ -189,7 +190,13 @@ private String buildMetadataInfo() { } private void buildThumbnail(final int count, final int width, final int height) { - mThumbnailAdapter = null; + if (mThumbnailDisposable != null && mThumbnailDisposable.isDisposed()) { + mThumbnailDisposable.dispose(); + } + + mThumbnailAdapter = new ThumbnailAdapter(count);//每秒取1帧 + rv.setAdapter(mThumbnailAdapter); + //取帧是耗时的操作,需要放在子线程 Observable.create(new ObservableOnSubscribe() { @Override @@ -211,13 +218,13 @@ public void subscribe(@NonNull ObservableEmitter s) throws Exce }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new SimpleObserver() { + @Override + public void onSubscribe(Disposable d) { + mThumbnailDisposable = d; + } @Override public void onNext(ThumbnailBitmap bitmap) { - if (mThumbnailAdapter == null) { - mThumbnailAdapter = new ThumbnailAdapter(count);//每秒取1帧 - rv.setAdapter(mThumbnailAdapter); - } //刷新adapter mThumbnailAdapter.setThumbnail(bitmap.getIndex(), bitmap.getBitmap()); } @@ -282,4 +289,14 @@ public void clickAudio(MenuItem item) { ed.setText(new File(getExternalCacheDir(), "test_audio.mp3").getAbsolutePath()); clickMediaMetadata(null); } + + public void clickJpg(MenuItem item) { + ed.setText(new File(getExternalCacheDir(), "test.jpg").getAbsolutePath()); + clickMediaMetadata(null); + } + + public void clickGif(MenuItem item) { + ed.setText(new File(getExternalCacheDir(), "test.gif").getAbsolutePath()); + clickMediaMetadata(null); + } } diff --git a/example/src/main/java/com/dyhdyh/compat/mmrc/example/ThumbnailAdapter.java b/example/src/main/java/com/dyhdyh/compat/mmrc/example/ThumbnailAdapter.java index a280146..e5e6b75 100644 --- a/example/src/main/java/com/dyhdyh/compat/mmrc/example/ThumbnailAdapter.java +++ b/example/src/main/java/com/dyhdyh/compat/mmrc/example/ThumbnailAdapter.java @@ -25,11 +25,7 @@ public Holder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(Holder holder, int position) { - if (bitmaps[position] == null) { - holder.iv_thumbnail.setImageResource(R.mipmap.ic_launcher); - } else { - holder.iv_thumbnail.setImageBitmap(bitmaps[position]); - } + holder.iv_thumbnail.setImageBitmap(bitmaps[position]); } @Override diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml index 0d8de4f..690a186 100644 --- a/example/src/main/res/layout/activity_main.xml +++ b/example/src/main/res/layout/activity_main.xml @@ -55,7 +55,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="clickMediaMetadata" - android:text="获取视频信息/缩略图" /> + android:text="获取媒体数据/缩略图" /> \ No newline at end of file diff --git a/example/src/main/res/menu/menu.xml b/example/src/main/res/menu/menu.xml index 6475d1c..f424d60 100644 --- a/example/src/main/res/menu/menu.xml +++ b/example/src/main/res/menu/menu.xml @@ -6,6 +6,12 @@ + + diff --git a/example/src/main/res/values/dimens.xml b/example/src/main/res/values/dimens.xml index eb4783e..4bad53a 100644 --- a/example/src/main/res/values/dimens.xml +++ b/example/src/main/res/values/dimens.xml @@ -3,5 +3,5 @@ 16dp 16dp - 100dp + 150dp diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index 70c3aaa..91f84b9 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ MediaMetadataRetrieverCompat - 输入视频路径,支持本地文件和网络视频 + 文件路径,支持本地文件和网络文件 diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/IMediaMetadataRetriever.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/IMediaMetadataRetriever.java index 5a819cd..afa5536 100644 --- a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/IMediaMetadataRetriever.java +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/IMediaMetadataRetriever.java @@ -4,7 +4,9 @@ import android.graphics.Bitmap; import android.net.Uri; +import java.io.File; import java.io.FileDescriptor; +import java.io.FileNotFoundException; import java.util.Map; /** @@ -13,7 +15,7 @@ */ public interface IMediaMetadataRetriever { - void setDataSource(String path); + void setDataSource(File inputFile) throws FileNotFoundException; void setDataSource(String uri, Map headers); @@ -33,7 +35,7 @@ public interface IMediaMetadataRetriever { byte[] getEmbeddedPicture(); - String extractMetadata(String keyCode); + String extractMetadata(int keyCode); void release(); } diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/MediaMetadataRetrieverCompat.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/MediaMetadataRetrieverCompat.java index c2cd36e..a377678 100644 --- a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/MediaMetadataRetrieverCompat.java +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/MediaMetadataRetrieverCompat.java @@ -3,19 +3,20 @@ import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; -import android.text.TextUtils; import android.util.Log; import com.dyhdyh.compat.mmrc.impl.FFmpegMediaMetadataRetrieverImpl; +import com.dyhdyh.compat.mmrc.impl.ImageMediaMetadataRetrieverImpl; import com.dyhdyh.compat.mmrc.impl.MediaMetadataRetrieverImpl; import com.dyhdyh.compat.mmrc.transform.BitmapRotateTransform; -import com.dyhdyh.compat.mmrc.transform.MetadataTransform; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.regex.Pattern; /** * author dengyuhan @@ -25,7 +26,8 @@ public class MediaMetadataRetrieverCompat { private final String TAG = "MediaMetadataRetriever"; private IMediaMetadataRetriever mImpl; - private MediaMetadataRetrieverImpl mAndroidImpl; + private IMediaMetadataRetriever mOriginalImpl; + private IMediaMetadataRetriever mImageImpl; public static final int VALUE_EMPTY = -1; @@ -44,16 +46,34 @@ public class MediaMetadataRetrieverCompat { public static final int METADATA_KEY_NUM_TRACKS = 10; public static final int METADATA_KEY_ALBUMARTIST = 13; public static final int METADATA_KEY_DISC_NUMBER = 14; + + /** + * @deprecated {@link #METADATA_KEY_WIDTH} + */ + @Deprecated public static final int METADATA_KEY_VIDEO_WIDTH = 18; + /** + * @deprecated {@link #METADATA_KEY_HEIGHT} + */ + @Deprecated public static final int METADATA_KEY_VIDEO_HEIGHT = 19; + /** + * @deprecated {@link #METADATA_KEY_ROTATION} + */ + @Deprecated public static final int METADATA_KEY_VIDEO_ROTATION = 24; + + public static final int METADATA_KEY_WIDTH = 18; + public static final int METADATA_KEY_HEIGHT = 19; + public static final int METADATA_KEY_ROTATION = 24; public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; - public static final int RETRIEVER_FFMPEG = 0; + public static final int RETRIEVER_AUTOMATIC = 0; public static final int RETRIEVER_ANDROID = 1; + public static final int RETRIEVER_FFMPEG = 2; public static MediaMetadataRetrieverCompat create() { - return new MediaMetadataRetrieverCompat(RETRIEVER_FFMPEG); + return new MediaMetadataRetrieverCompat(RETRIEVER_AUTOMATIC); } public static MediaMetadataRetrieverCompat create(int type) { @@ -73,22 +93,22 @@ public MediaMetadataRetrieverCompat() { */ @Deprecated public MediaMetadataRetrieverCompat(int type) { - if (type == RETRIEVER_FFMPEG) { + if (type == RETRIEVER_AUTOMATIC || type == RETRIEVER_FFMPEG) { try { - //创建实例前先检查是否引入FFmpegMediaMetadataRetriever + //创建实例前先检查FFmpegMediaMetadataRetriever是否可用 Class.forName("wseemann.media.FFmpegMediaMetadataRetriever"); - //优先ffmpeg - this.mImpl = new FFmpegMediaMetadataRetrieverImpl(); + this.mOriginalImpl = new FFmpegMediaMetadataRetrieverImpl(type == RETRIEVER_AUTOMATIC); } catch (Exception e) { - //不行就自带的 - this.mImpl = new MediaMetadataRetrieverImpl(); - Log.d(TAG, "FFmpegMediaMetadataRetrieverImpl初始化失败,使用原生API"); + //如果不可用 就创建原生实例 + this.mOriginalImpl = new MediaMetadataRetrieverImpl(); + Log.d(TAG, "FFmpegMediaMetadataRetrieverImpl初始化失败,已切换至原生API"); e.printStackTrace(); } - this.mAndroidImpl = new MediaMetadataRetrieverImpl(); } else { - this.mImpl = new MediaMetadataRetrieverImpl(); + this.mOriginalImpl = new MediaMetadataRetrieverImpl(); } + this.mImpl = mOriginalImpl; + this.mImageImpl = new ImageMediaMetadataRetrieverImpl(); } public IMediaMetadataRetriever getMediaMetadataRetriever() { @@ -100,48 +120,55 @@ public IMediaMetadataRetriever getMediaMetadataRetriever() { * @deprecated {@link #setDataSource(File)} */ @Deprecated - public void setDataSource(String path) { - try { - setMediaDataSource(path); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } + public void setMediaDataSource(String path) throws IOException { + setMediaDataSource(new File(path)); } /** - * @param path + * @param file * @deprecated {@link #setDataSource(File)} */ @Deprecated - public void setMediaDataSource(String path) throws FileNotFoundException { - setMediaDataSource(new File(path)); + public void setMediaDataSource(File file) throws IOException { + setDataSource(file); } + /** - * @param file - * @deprecated {@link #setDataSource(File)} + * @param path */ - @Deprecated - public void setMediaDataSource(File file) throws FileNotFoundException { - setDataSource(file); + public void setDataSource(String path) { + try { + setDataSource(new File(path)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } } public void setDataSource(File inputFile) throws FileNotFoundException { if (inputFile == null || !inputFile.exists()) { throw new FileNotFoundException("文件不存在 " + (inputFile != null ? inputFile.getAbsolutePath() : "")); } - String inputPath = inputFile.getAbsolutePath(); - mImpl.setDataSource(inputPath); - if (mAndroidImpl != null) { - mAndroidImpl.setDataSource(inputPath); - } + runDynamicSetDataSource(new Runnable() { + @Override + public void run() { + try { + mImpl.setDataSource(inputFile); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + }); + } public void setDataSource(Context context, Uri uri) { - mImpl.setDataSource(context, uri); - if (mAndroidImpl != null) { - mAndroidImpl.setDataSource(context, uri); - } + runDynamicSetDataSource(new Runnable() { + @Override + public void run() { + mImpl.setDataSource(context, uri); + } + }); } /** @@ -151,41 +178,36 @@ public void setDataSource(Context context, Uri uri) { * @param headers */ public void setDataSource(String uri, Map headers) { - if (headers == null) { - headers = new HashMap<>(); - } - mImpl.setDataSource(uri, headers); - if (mAndroidImpl != null) { - mAndroidImpl.setDataSource(uri, headers); - } + Map newHeaders = headers == null ? new HashMap<>() : headers; + runDynamicSetDataSource(new Runnable() { + @Override + public void run() { + mImpl.setDataSource(uri, newHeaders); + } + }); } public void setDataSource(FileDescriptor fd, long offset, long length) { - mImpl.setDataSource(fd, offset, length); - if (mAndroidImpl != null) { - mAndroidImpl.setDataSource(fd, offset, length); - } + runDynamicSetDataSource(new Runnable() { + @Override + public void run() { + mImpl.setDataSource(fd, offset, length); + } + }); } public void setDataSource(FileDescriptor fd) { - mImpl.setDataSource(fd); - if (mAndroidImpl != null) { - mAndroidImpl.setDataSource(fd); - } + runDynamicSetDataSource(new Runnable() { + @Override + public void run() { + mImpl.setDataSource(fd); + } + }); } public String extractMetadata(int keyCode) { - String keyCodeString = MetadataTransform.transform(this.mImpl.getClass(), keyCode); - if (TextUtils.isEmpty(keyCodeString)) { - return null; - } - String metadata = mImpl.extractMetadata(keyCodeString); - if (metadata == null && mAndroidImpl != null) { - //如果ffmpeg失败,自带api替代 - String androidKeyCodeString = MetadataTransform.transform(mAndroidImpl.getClass(), keyCode); - metadata = mAndroidImpl.extractMetadata(androidKeyCodeString); - } + String metadata = mImpl.extractMetadata(keyCode); Log.d(TAG, "extractMetadata : " + keyCode + " = " + metadata); return metadata; } @@ -206,7 +228,6 @@ public int extractMetadataInt(int keyCode) { } } - public float extractMetadataFloat(int keyCode) { try { return Float.parseFloat(extractMetadata(keyCode)); @@ -215,76 +236,89 @@ public float extractMetadataFloat(int keyCode) { } } + /** + * 获取第一帧bitmap + * + * @return + */ public Bitmap getFrameAtTime() { - Bitmap frame = this.mImpl.getFrameAtTime(); - if (frame == null && mAndroidImpl != null) { - return mAndroidImpl.getFrameAtTime(); - } - return frame; + return this.mImpl.getFrameAtTime(); } + /** + * 获取指定时间的bitmap + * + * @param timeUs 微秒 + * @param option {@link #OPTION_PREVIOUS_SYNC} 早于timeUs的同步帧; + * {@link #OPTION_NEXT_SYNC} 晚于timeUs的同步帧; + * {@link #OPTION_CLOSEST_SYNC} 最接近timeUs的同步帧; + * {@link #OPTION_CLOSEST} 最接近timeUs的帧,但可能不是同步帧(性能开销较大). + * @return + */ public Bitmap getFrameAtTime(long timeUs, int option) { - Bitmap frame = this.mImpl.getFrameAtTime(timeUs, option); - if (frame == null && mAndroidImpl != null) { - return mAndroidImpl.getFrameAtTime(timeUs, option); - } - return frame; + return this.mImpl.getFrameAtTime(timeUs, option); } + /** + * 获取指定时间指定宽高的bitmap + * + * @param timeUs + * @param width + * @param height + * @return + */ public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) { - Bitmap frame = this.mImpl.getScaledFrameAtTime(timeUs, width, height); - if (frame == null && mAndroidImpl != null) { - return mAndroidImpl.getScaledFrameAtTime(timeUs, width, height); - } - return frame; + return this.mImpl.getScaledFrameAtTime(timeUs, width, height); } + /** + * 指定获取方式获取指定时间指定宽高的bitmap + * + * @param timeUs + * @param width + * @param height + * @return + */ public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) { - Bitmap frame = this.mImpl.getScaledFrameAtTime(timeUs, option, width, height); - if (frame == null && mAndroidImpl != null) { - return mAndroidImpl.getScaledFrameAtTime(timeUs, option, width, height); - } - return frame; + return this.mImpl.getScaledFrameAtTime(timeUs, option, width, height); } + /** + * 指定获取方式获取指定时间指定宽高的bitmap并根据角度旋转 + * + * @param timeUs + * @param option + * @param width + * @param height + * @param rotate + * @return + * @deprecated 在其它方法已开启自动旋转 + */ + @Deprecated public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height, float rotate) { - boolean isVertical = isVertical(rotate); - Bitmap frame = getScaledFrameAtTime(timeUs, option, - isVertical ? height : width, isVertical ? width : height); - if (isRotate(rotate)) { + Bitmap frame = getScaledFrameAtTime(timeUs, option, width, height); + if (frame != null && isRotate(rotate)) { return BitmapRotateTransform.transform(frame, rotate); } return frame; } - public Bitmap getScaledFrameAtTime(long timeUs, int option, float widthScale, float heightScale, float rotate) { - int widthInt = extractMetadataInt(METADATA_KEY_VIDEO_WIDTH); - int heightInt = extractMetadataInt(METADATA_KEY_VIDEO_HEIGHT); - int width = widthInt <= 0 ? 0 : (int) (widthInt * widthScale); - int height = heightInt <= 0 ? 0 : (int) (heightInt * heightScale); + @Deprecated + public Bitmap getScaledFrameAtTime(long timeUs, int option, float scale, float rotate) { + int widthInt = extractMetadataInt(METADATA_KEY_WIDTH); + int heightInt = extractMetadataInt(METADATA_KEY_HEIGHT); + int width = widthInt <= 0 ? 0 : (int) (widthInt * scale); + int height = heightInt <= 0 ? 0 : (int) (heightInt * scale); return getScaledFrameAtTime(timeUs, option, width, height, rotate); } public byte[] getEmbeddedPicture() { - byte[] embeddedPicture = mImpl.getEmbeddedPicture(); - if (embeddedPicture == null && mAndroidImpl != null) { - return mAndroidImpl.getEmbeddedPicture(); - } - return embeddedPicture; + return mImpl.getEmbeddedPicture(); } public void release() { mImpl.release(); - if (mAndroidImpl != null) { - mAndroidImpl.release(); - } - } - - - private boolean isVertical(float rotate) { - float abs = Math.abs(rotate); - return abs == 90 || abs == 270; } @@ -298,4 +332,27 @@ public static boolean isRotate(float rotate) { float abs = Math.abs(rotate); return abs % 360 != 0; } + + + /** + * 根据输入源动态切换引擎 + * + * @param runnable + */ + private void runDynamicSetDataSource(Runnable runnable) { + try { + //设置输入源前先还原实例 + this.mImpl = this.mOriginalImpl; + runnable.run(); + } catch (RuntimeException e) { + final Pattern pattern = Pattern.compile("(?=.*status)(?=.*0x80000000)^.*$"); + if (pattern.matcher(e.getMessage()).matches()) { + //如果发现输入源的是图片 就切换成图片实例 + this.mImpl = this.mImageImpl; + runnable.run(); + } else { + e.printStackTrace(); + } + } + } } diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/DefaultImageHeaderParser.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/DefaultImageHeaderParser.java new file mode 100644 index 0000000..9d344ce --- /dev/null +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/DefaultImageHeaderParser.java @@ -0,0 +1,474 @@ +package com.dyhdyh.compat.mmrc.image; + + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; + +/** + * A class for parsing the exif orientation and other data from an image header. + * + * @see + */ +public final class DefaultImageHeaderParser implements ImageHeaderParser { + // Due to https://code.google.com/p/android/issues/detail?id=97751. + // TAG needs to be under 23 chars, so "Default" > "Dflt". + private static final String TAG = "DfltImageHeaderParser"; + + private static final int GIF_HEADER = 0x474946; + private static final int PNG_HEADER = 0x89504E47; + static final int EXIF_MAGIC_NUMBER = 0xFFD8; + // "MM". + private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D; + // "II". + private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949; + private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0"; + static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES = + JPEG_EXIF_SEGMENT_PREAMBLE.getBytes(Charset.forName("UTF-8")); + private static final int SEGMENT_SOS = 0xDA; + private static final int MARKER_EOI = 0xD9; + static final int SEGMENT_START_ID = 0xFF; + static final int EXIF_SEGMENT_TYPE = 0xE1; + private static final int ORIENTATION_TAG_TYPE = 0x0112; + private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8}; + // WebP-related + // "RIFF" + private static final int RIFF_HEADER = 0x52494646; + // "WEBP" + private static final int WEBP_HEADER = 0x57454250; + // "VP8" null. + private static final int VP8_HEADER = 0x56503800; + private static final int VP8_HEADER_MASK = 0xFFFFFF00; + private static final int VP8_HEADER_TYPE_MASK = 0x000000FF; + // 'X' + private static final int VP8_HEADER_TYPE_EXTENDED = 0x00000058; + // 'L' + private static final int VP8_HEADER_TYPE_LOSSLESS = 0x0000004C; + private static final int WEBP_EXTENDED_ALPHA_FLAG = 1 << 4; + private static final int WEBP_LOSSLESS_ALPHA_FLAG = 1 << 3; + + public ImageType getType(InputStream is) throws IOException { + return getType(new StreamReader(is)); + } + + @Override + public ImageType getType(ByteBuffer byteBuffer) throws IOException { + return getType(new ByteBufferReader(byteBuffer)); + } + + public int getOrientation(InputStream is) + throws IOException { + return getOrientation(new StreamReader(is)); + } + + private ImageType getType(Reader reader) throws IOException { + final int firstTwoBytes = reader.getUInt16(); + + // JPEG. + if (firstTwoBytes == EXIF_MAGIC_NUMBER) { + return ImageType.JPEG; + } + + final int firstFourBytes = (firstTwoBytes << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); + // PNG. + if (firstFourBytes == PNG_HEADER) { + // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha + // -color-type + reader.skip(25 - 4); + int alpha = reader.getByte(); + // A RGB indexed PNG can also have transparency. Better safe than sorry! + return alpha >= 3 ? ImageType.PNG_A : ImageType.PNG; + } + + // GIF from first 3 bytes. + if (firstFourBytes >> 8 == GIF_HEADER) { + return ImageType.GIF; + } + + // WebP (reads up to 21 bytes). See https://developers.google.com/speed/webp/docs/riff_container + // for details. + if (firstFourBytes != RIFF_HEADER) { + return ImageType.UNKNOWN; + } + // Bytes 4 - 7 contain length information. Skip these. + reader.skip(4); + final int thirdFourBytes = + (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); + if (thirdFourBytes != WEBP_HEADER) { + return ImageType.UNKNOWN; + } + final int fourthFourBytes = + (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF); + if ((fourthFourBytes & VP8_HEADER_MASK) != VP8_HEADER) { + return ImageType.UNKNOWN; + } + if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_EXTENDED) { + // Skip some more length bytes and check for transparency/alpha flag. + reader.skip(4); + return (reader.getByte() & WEBP_EXTENDED_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP; + } + if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) { + // See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt + // for more info. + reader.skip(4); + return (reader.getByte() & WEBP_LOSSLESS_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP; + } + return ImageType.WEBP; + } + + /** + * Parse the orientation from the image header. If it doesn't handle this image type (or this is + * not an image) it will return a default value rather than throwing an exception. + * + * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't + * contain an orientation + */ + private int getOrientation(Reader reader) throws IOException { + final int magicNumber = reader.getUInt16(); + + if (!handles(magicNumber)) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Parser doesn't handle magic number: " + magicNumber); + } + return UNKNOWN_ORIENTATION; + } else { + int exifSegmentLength = moveToExifSegmentAndGetLength(reader); + if (exifSegmentLength == -1) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Failed to parse exif segment length, or exif segment not found"); + } + return UNKNOWN_ORIENTATION; + } + + byte[] exifData = new byte[exifSegmentLength]; + return parseExifSegment(reader, exifData, exifSegmentLength); + } + } + + private int parseExifSegment(Reader reader, byte[] tempArray, int exifSegmentLength) + throws IOException { + int read = reader.read(tempArray, exifSegmentLength); + if (read != exifSegmentLength) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Unable to read exif segment data" + + ", length: " + exifSegmentLength + + ", actually read: " + read); + } + return UNKNOWN_ORIENTATION; + } + + boolean hasJpegExifPreamble = hasJpegExifPreamble(tempArray, exifSegmentLength); + if (hasJpegExifPreamble) { + return parseExifSegment(new RandomAccessReader(tempArray, exifSegmentLength)); + } else { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Missing jpeg exif preamble"); + } + return UNKNOWN_ORIENTATION; + } + } + + private boolean hasJpegExifPreamble(byte[] exifData, int exifSegmentLength) { + boolean result = + exifData != null && exifSegmentLength > JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; + if (result) { + for (int i = 0; i < JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; i++) { + if (exifData[i] != JPEG_EXIF_SEGMENT_PREAMBLE_BYTES[i]) { + result = false; + break; + } + } + } + return result; + } + + /** + * Moves reader to the start of the exif segment and returns the length of the exif segment or + * {@code -1} if no exif segment is found. + */ + private int moveToExifSegmentAndGetLength(Reader reader) throws IOException { + while (true) { + short segmentId = reader.getUInt8(); + if (segmentId != SEGMENT_START_ID) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Unknown segmentId=" + segmentId); + } + return -1; + } + + short segmentType = reader.getUInt8(); + if (segmentType == SEGMENT_SOS) { + return -1; + } else if (segmentType == MARKER_EOI) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Found MARKER_EOI in exif segment"); + } + return -1; + } + + // Segment length includes bytes for segment length. + int segmentLength = reader.getUInt16() - 2; + if (segmentType != EXIF_SEGMENT_TYPE) { + long skipped = reader.skip(segmentLength); + if (skipped != segmentLength) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Unable to skip enough data" + + ", type: " + segmentType + + ", wanted to skip: " + segmentLength + + ", but actually skipped: " + skipped); + } + return -1; + } + } else { + return segmentLength; + } + } + } + + private static int parseExifSegment(RandomAccessReader segmentData) { + final int headerOffsetSize = JPEG_EXIF_SEGMENT_PREAMBLE.length(); + + short byteOrderIdentifier = segmentData.getInt16(headerOffsetSize); + final ByteOrder byteOrder; + switch (byteOrderIdentifier) { + case MOTOROLA_TIFF_MAGIC_NUMBER: + byteOrder = ByteOrder.BIG_ENDIAN; + break; + case INTEL_TIFF_MAGIC_NUMBER: + byteOrder = ByteOrder.LITTLE_ENDIAN; + break; + default: + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Unknown endianness = " + byteOrderIdentifier); + } + byteOrder = ByteOrder.BIG_ENDIAN; + break; + } + + segmentData.order(byteOrder); + + int firstIfdOffset = segmentData.getInt32(headerOffsetSize + 4) + headerOffsetSize; + int tagCount = segmentData.getInt16(firstIfdOffset); + for (int i = 0; i < tagCount; i++) { + final int tagOffset = calcTagOffset(firstIfdOffset, i); + + final int tagType = segmentData.getInt16(tagOffset); + // We only want orientation. + if (tagType != ORIENTATION_TAG_TYPE) { + continue; + } + + final int formatCode = segmentData.getInt16(tagOffset + 2); + // 12 is max format code. + if (formatCode < 1 || formatCode > 12) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Got invalid format code = " + formatCode); + } + continue; + } + + final int componentCount = segmentData.getInt32(tagOffset + 4); + if (componentCount < 0) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Negative tiff component count"); + } + continue; + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Got tagIndex=" + i + " tagType=" + tagType + " formatCode=" + formatCode + + " componentCount=" + componentCount); + } + + final int byteCount = componentCount + BYTES_PER_FORMAT[formatCode]; + if (byteCount > 4) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Got byte count > 4, not orientation, continuing, formatCode=" + formatCode); + } + continue; + } + + final int tagValueOffset = tagOffset + 8; + if (tagValueOffset < 0 || tagValueOffset > segmentData.length()) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Illegal tagValueOffset=" + tagValueOffset + " tagType=" + tagType); + } + continue; + } + + if (byteCount < 0 || tagValueOffset + byteCount > segmentData.length()) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Illegal number of bytes for TI tag data tagType=" + tagType); + } + continue; + } + + //assume componentCount == 1 && fmtCode == 3 + return segmentData.getInt16(tagValueOffset); + } + + return -1; + } + + private static int calcTagOffset(int ifdOffset, int tagIndex) { + return ifdOffset + 2 + 12 * tagIndex; + } + + private static boolean handles(int imageMagicNumber) { + return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER + || imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER + || imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER; + } + + private static final class RandomAccessReader { + private final ByteBuffer data; + + RandomAccessReader(byte[] data, int length) { + this.data = (ByteBuffer) ByteBuffer.wrap(data) + .order(ByteOrder.BIG_ENDIAN) + .limit(length); + } + + void order(ByteOrder byteOrder) { + this.data.order(byteOrder); + } + + int length() { + return data.remaining(); + } + + int getInt32(int offset) { + return isAvailable(offset, 4) ? data.getInt(offset) : -1; + } + + short getInt16(int offset) { + return isAvailable(offset, 2) ? data.getShort(offset) : -1; + } + + private boolean isAvailable(int offset, int byteSize) { + return data.remaining() - offset >= byteSize; + } + } + + private interface Reader { + int getUInt16() throws IOException; + + short getUInt8() throws IOException; + + long skip(long total) throws IOException; + + int read(byte[] buffer, int byteCount) throws IOException; + + int getByte() throws IOException; + } + + private static final class ByteBufferReader implements Reader { + + private final ByteBuffer byteBuffer; + + ByteBufferReader(ByteBuffer byteBuffer) { + this.byteBuffer = byteBuffer; + byteBuffer.order(ByteOrder.BIG_ENDIAN); + } + + @Override + public int getUInt16() { + return (getByte() << 8 & 0xFF00) | (getByte() & 0xFF); + } + + @Override + public short getUInt8() { + return (short) (getByte() & 0xFF); + } + + @Override + public long skip(long total) { + int toSkip = (int) Math.min(byteBuffer.remaining(), total); + byteBuffer.position(byteBuffer.position() + toSkip); + return toSkip; + } + + @Override + public int read(byte[] buffer, int byteCount) { + int toRead = Math.min(byteCount, byteBuffer.remaining()); + if (toRead == 0) { + return -1; + } + byteBuffer.get(buffer, 0 /*dstOffset*/, toRead); + return toRead; + } + + @Override + public int getByte() { + if (byteBuffer.remaining() < 1) { + return -1; + } + return byteBuffer.get(); + } + } + + private static final class StreamReader implements Reader { + private final InputStream is; + + // Motorola / big endian byte order. + StreamReader(InputStream is) { + this.is = is; + } + + @Override + public int getUInt16() throws IOException { + return (is.read() << 8 & 0xFF00) | (is.read() & 0xFF); + } + + @Override + public short getUInt8() throws IOException { + return (short) (is.read() & 0xFF); + } + + @Override + public long skip(long total) throws IOException { + if (total < 0) { + return 0; + } + + long toSkip = total; + while (toSkip > 0) { + long skipped = is.skip(toSkip); + if (skipped > 0) { + toSkip -= skipped; + } else { + // Skip has no specific contract as to what happens when you reach the end of + // the stream. To differentiate between temporarily not having more data and + // having finished the stream, we read a single byte when we fail to skip any + // amount of data. + int testEofByte = is.read(); + if (testEofByte == -1) { + break; + } else { + toSkip--; + } + } + } + return total - toSkip; + } + + @Override + public int read(byte[] buffer, int byteCount) throws IOException { + int toRead = byteCount; + int read; + while (toRead > 0 && ((read = is.read(buffer, byteCount - toRead, toRead)) != -1)) { + toRead -= read; + } + return byteCount - toRead; + } + + @Override + public int getByte() throws IOException { + return is.read(); + } + } +} \ No newline at end of file diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/ImageHeaderParser.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/ImageHeaderParser.java new file mode 100644 index 0000000..f71873b --- /dev/null +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/ImageHeaderParser.java @@ -0,0 +1,32 @@ +package com.dyhdyh.compat.mmrc.image; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * Interface for the ImageHeaderParser. + * * @see + */ +public interface ImageHeaderParser { + /** + * A constant indicating we were unable to parse the orientation from the image either because no + * exif segment containing orientation data existed, or because of an I/O error attempting to read + * the exif segment. + */ + int UNKNOWN_ORIENTATION = -1; + + ImageType getType(InputStream is) throws IOException; + + ImageType getType(ByteBuffer byteBuffer) throws IOException; + + /** + * Parse the orientation from the image header. If it doesn't handle this image type (or this is + * not an image) it will return a default value rather than throwing an exception. + * + * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't + * contain an orientation + */ + int getOrientation(InputStream is) throws IOException; + +} \ No newline at end of file diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/ImageType.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/ImageType.java new file mode 100644 index 0000000..68892d5 --- /dev/null +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/image/ImageType.java @@ -0,0 +1,31 @@ +package com.dyhdyh.compat.mmrc.image; + +/** + * The format of the image data including whether or not the image may include transparent + * pixels. + */ +public enum ImageType { + GIF(true), + JPEG(false), + RAW(false), + /** PNG type with alpha. */ + PNG_A(true), + /** PNG type without alpha. */ + PNG(false), + /** WebP type with alpha. */ + WEBP_A(true), + /** WebP type without alpha. */ + WEBP(false), + /** Unrecognized type. */ + UNKNOWN(false); + + private final boolean hasAlpha; + + ImageType(boolean hasAlpha) { + this.hasAlpha = hasAlpha; + } + + public boolean hasAlpha() { + return hasAlpha; + } +} diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/FFmpegMediaMetadataRetrieverImpl.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/FFmpegMediaMetadataRetrieverImpl.java index 26de557..3de90fa 100644 --- a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/FFmpegMediaMetadataRetrieverImpl.java +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/FFmpegMediaMetadataRetrieverImpl.java @@ -3,10 +3,15 @@ import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; +import android.text.TextUtils; import com.dyhdyh.compat.mmrc.IMediaMetadataRetriever; +import com.dyhdyh.compat.mmrc.transform.BitmapRotateTransform; +import com.dyhdyh.compat.mmrc.transform.MetadataTransform; +import java.io.File; import java.io.FileDescriptor; +import java.io.FileNotFoundException; import java.util.Map; import wseemann.media.FFmpegMediaMetadataRetriever; @@ -19,68 +24,162 @@ public class FFmpegMediaMetadataRetrieverImpl implements IMediaMetadataRetriever { private FFmpegMediaMetadataRetriever mRetriever; + //原生实例 + private MediaMetadataRetrieverImpl mAndroidRetriever; + + + /** + * 默认启用备用实例 + */ public FFmpegMediaMetadataRetrieverImpl() { + this(true); + } + + /** + * 是否启用备用实例 + * + * @param enableBackupImpl + */ + public FFmpegMediaMetadataRetrieverImpl(boolean enableBackupImpl) { this.mRetriever = new FFmpegMediaMetadataRetriever(); + if (enableBackupImpl) { + //如果启用备用实例 当ffmpeg获取数据失败的时候 会用备用实例再获取一次 + this.mAndroidRetriever = new MediaMetadataRetrieverImpl(); + } } @Override - public void setDataSource(String path) { - this.mRetriever.setDataSource(path); + public void setDataSource(File inputFile) throws FileNotFoundException { + this.mRetriever.setDataSource(inputFile.getAbsolutePath()); + + if (mAndroidRetriever != null) { + this.mAndroidRetriever.setDataSource(inputFile); + } } @Override public void setDataSource(Context context, Uri uri) { this.mRetriever.setDataSource(context, uri); + + if (mAndroidRetriever != null) { + this.mAndroidRetriever.setDataSource(context, uri); + } } @Override public void setDataSource(String uri, Map headers) { this.mRetriever.setDataSource(uri, headers); + + if (mAndroidRetriever != null) { + this.mAndroidRetriever.setDataSource(uri, headers); + } } @Override public void setDataSource(FileDescriptor fd, long offset, long length) { this.mRetriever.setDataSource(fd, offset, length); + + if (mAndroidRetriever != null) { + this.mAndroidRetriever.setDataSource(fd, offset, length); + } } @Override public void setDataSource(FileDescriptor fd) { this.mRetriever.setDataSource(fd); + + if (mAndroidRetriever != null) { + this.mAndroidRetriever.setDataSource(fd); + } } @Override public Bitmap getFrameAtTime() { - return this.mRetriever.getFrameAtTime(); + final Bitmap frameAtTime = this.mRetriever.getFrameAtTime(); + + if (frameAtTime == null && mAndroidRetriever != null) { + return this.mAndroidRetriever.getFrameAtTime(); + } + return makeRotateBitmap(frameAtTime); } @Override public Bitmap getFrameAtTime(long timeUs, int option) { - return this.mRetriever.getFrameAtTime(timeUs, option); + final Bitmap frameAtTime = this.mRetriever.getFrameAtTime(timeUs, option); + + if (frameAtTime == null && mAndroidRetriever != null) { + return this.mAndroidRetriever.getFrameAtTime(timeUs, option); + } + return makeRotateBitmap(frameAtTime); } @Override public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) { - return this.mRetriever.getScaledFrameAtTime(timeUs, width, height); + final Bitmap scaledFrameAtTime = this.mRetriever.getScaledFrameAtTime(timeUs, width, height); + + if (scaledFrameAtTime == null && mAndroidRetriever != null) { + return this.mAndroidRetriever.getScaledFrameAtTime(timeUs, width, height); + } + return makeRotateBitmap(scaledFrameAtTime); } @Override public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) { - return this.mRetriever.getScaledFrameAtTime(timeUs, option, width, height); + final Bitmap scaledFrameAtTime = this.mRetriever.getScaledFrameAtTime(timeUs, option, width, height); + if (scaledFrameAtTime == null && mAndroidRetriever != null) { + return this.mAndroidRetriever.getScaledFrameAtTime(timeUs, option, width, height); + } + return makeRotateBitmap(scaledFrameAtTime); } @Override public byte[] getEmbeddedPicture() { - return this.mRetriever.getEmbeddedPicture(); + final byte[] picture = this.mRetriever.getEmbeddedPicture(); + if (picture == null && mAndroidRetriever != null) { + return this.mAndroidRetriever.getEmbeddedPicture(); + } + return picture; } @Override - public String extractMetadata(String keyCode) { - return this.mRetriever.extractMetadata(keyCode); + public String extractMetadata(int keyCode) { + String keyCodeString = MetadataTransform.transform(getClass(), keyCode); + if (TextUtils.isEmpty(keyCodeString)) { + return null; + } + final String metadata = this.mRetriever.extractMetadata(keyCodeString); + + if (metadata == null && mAndroidRetriever != null) { + return this.mAndroidRetriever.extractMetadata(keyCode); + } + return metadata; } @Override public void release() { this.mRetriever.release(); + + if (mAndroidRetriever != null) { + this.mAndroidRetriever.release(); + } + } + + private Bitmap makeRotateBitmap(Bitmap bitmap) { + if (bitmap == null) { + return null; + } + final String rotationString = mRetriever.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + if (!TextUtils.isEmpty(rotationString)) { + try { + final int rotation = Integer.parseInt(rotationString); + if (rotation != 0) { + return BitmapRotateTransform.transform(bitmap, rotation); + } + } catch (NumberFormatException ignored) { + + } + } + return bitmap; } } \ No newline at end of file diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/ImageMediaMetadataRetrieverImpl.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/ImageMediaMetadataRetrieverImpl.java new file mode 100644 index 0000000..7899ece --- /dev/null +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/ImageMediaMetadataRetrieverImpl.java @@ -0,0 +1,319 @@ +package com.dyhdyh.compat.mmrc.impl; + +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Movie; +import android.media.ExifInterface; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.text.TextUtils; + +import com.dyhdyh.compat.mmrc.IMediaMetadataRetriever; +import com.dyhdyh.compat.mmrc.image.DefaultImageHeaderParser; +import com.dyhdyh.compat.mmrc.image.ImageType; +import com.dyhdyh.compat.mmrc.transform.BitmapRotateTransform; +import com.dyhdyh.compat.mmrc.transform.MetadataTransform; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; +import java.util.Map; + +/** + * 图片的解析 + * + * @author dengyuhan + * created 2018/10/15 19:36 + */ +public class ImageMediaMetadataRetrieverImpl implements IMediaMetadataRetriever { + + public static final int METADATA_KEY_WIDTH = MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH; + public static final int METADATA_KEY_HEIGHT = MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT; + public static final int METADATA_KEY_ROTATION = MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION; + public static final int METADATA_KEY_DURATION = MediaMetadataRetriever.METADATA_KEY_DURATION; + + private DefaultImageHeaderParser mImageHeaderParser; + + private File mInputFile; + private ImageType mImageType; + private Movie mMovie; + private BitmapFactory.Options mOptions; + + public ImageMediaMetadataRetrieverImpl() { + mImageHeaderParser = new DefaultImageHeaderParser(); + } + + @Override + public void setDataSource(File inputFile) throws FileNotFoundException { + mInputFile = inputFile; + mImageType = null; + mMovie = null; + mOptions = null; + } + + @Override + public void setDataSource(String uri, Map headers) { + try { + final URL url = new URL(uri); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + Iterator> entries = headers.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = entries.next(); + connection.addRequestProperty(entry.getKey(), entry.getValue()); + } + connection.connect(); + + String fileUrl = url.getFile(); + String filename = fileUrl.substring(fileUrl.lastIndexOf(File.separator) + File.separator.length(), fileUrl.length()); + File outputFile = new File(Environment.getDownloadCacheDirectory(), filename); + + BufferedInputStream ins = new BufferedInputStream(connection.getInputStream()); + inputStream2File(ins, outputFile); + + setDataSource(outputFile); + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + } + + @Override + public void setDataSource(FileDescriptor fd, long offset, long length) { + + } + + @Override + public void setDataSource(FileDescriptor fd) { + try { + FileInputStream in = new FileInputStream(fd); + File outputFile = new File(Environment.getDownloadCacheDirectory(), String.valueOf(System.currentTimeMillis())); + inputStream2File(in, outputFile); + + setDataSource(outputFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void setDataSource(Context context, Uri uri) { + try { + String[] projection = {MediaStore.Images.Media.DATA}; + Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); + if (cursor.moveToFirst()) { + String filepath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); + setDataSource(new File(filepath)); + } + cursor.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public Bitmap getFrameAtTime() { + return getScaledFrameAtTime(0, -1, -1); + } + + @Override + public Bitmap getFrameAtTime(long timeUs, int option) { + return getScaledFrameAtTime(timeUs, option, -1, -1); + } + + @Override + public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) { + return getScaledFrameAtTime(timeUs, Bitmap.Config.ARGB_8888.ordinal(), width, height); + } + + @Override + public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) { + if (ImageType.GIF == getImageType()) { + final Movie movie = getMovie(); + movie.setTime((int) (timeUs * 1000)); + Bitmap bitmap = Bitmap.createBitmap(movie.width(), movie.height(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + movie.draw(canvas, 0, 0); + return bitmap; + } else { + //原图 + Bitmap atTime = BitmapFactory.decodeFile(mInputFile.getAbsolutePath()); + if (atTime == null) { + return null; + } + //如果需要缩放 + if (width > 0 && height > 0) { + atTime = Bitmap.createScaledBitmap(atTime, width, height, true); + } + //如果需要旋转 + if (ImageType.JPEG == getImageType()) { + final int rotation = getJPGRotate(); + if (rotation != 0) { + return BitmapRotateTransform.transform(atTime, rotation); + } + } + return atTime; + } + } + + @Override + public byte[] getEmbeddedPicture() { + try { + final FileInputStream inputStream = new FileInputStream(mInputFile); + ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); + byte[] buff = new byte[1024]; + int buffer; + while ((buffer = inputStream.read(buff, 0, 100)) > 0) { + swapStream.write(buff, 0, buffer); + } + return swapStream.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public String extractMetadata(int keyCode) { + try { + String keyCodeString = MetadataTransform.transform(getClass(), keyCode); + if (TextUtils.isEmpty(keyCodeString)) { + return null; + } + int keyCodeInt = Integer.parseInt(keyCodeString); + if (METADATA_KEY_WIDTH == keyCodeInt) { + return String.valueOf(getOptions().outWidth); + } else if (METADATA_KEY_HEIGHT == keyCodeInt) { + return String.valueOf(getOptions().outHeight); + } else if (isImageType(ImageType.GIF, ImageType.WEBP) && METADATA_KEY_DURATION == keyCodeInt) { + //gif和webp才有时长 + return String.valueOf(getMovie().duration()); + } else if (isImageType(ImageType.JPEG) && METADATA_KEY_ROTATION == keyCodeInt) { + //jpg才有Exif + return String.valueOf(getJPGRotate()); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取jpg旋转角度 + * + * @return + */ + private int getJPGRotate() { + try { + final int orientation = mImageHeaderParser.getOrientation(new FileInputStream(mInputFile)); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + case ExifInterface.ORIENTATION_ROTATE_270: + return 270; + } + } catch (IOException e) { + e.printStackTrace(); + } + return 0; + } + + @Override + public void release() { + + } + + private Movie getMovie() { + try { + if (mMovie == null) { + mMovie = Movie.decodeStream(new FileInputStream(mInputFile)); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + return mMovie; + } + + /** + * 获取输入源的图片类型 + * + * @return + */ + public ImageType getImageType() { + try { + if (mImageType == null) { + return mImageHeaderParser.getType(new FileInputStream(mInputFile)); + } + } catch (IOException e) { + e.printStackTrace(); + } + if (mImageType == null) { + mImageType = ImageType.UNKNOWN; + } + return mImageType; + } + + /** + * 获取宽高信息 + * + * @return + */ + private BitmapFactory.Options getOptions() { + if (mOptions == null) { + mOptions = new BitmapFactory.Options(); + mOptions.inJustDecodeBounds = true; + BitmapFactory.decodeFile(mInputFile.getAbsolutePath(), mOptions); + return mOptions; + } + return mOptions; + } + + /** + * 判断输入源是否传入的type其中一种 + * + * @param targetImageType + * @return + */ + public boolean isImageType(ImageType... targetImageType) { + final ImageType imageType = getImageType(); + for (ImageType type : targetImageType) { + if (imageType == type) { + return true; + } + } + return false; + } + + + private void inputStream2File(InputStream is, File outputFile) throws IOException { + OutputStream os = new FileOutputStream(outputFile); + int bytesRead; + byte[] buffer = new byte[8192]; + while ((bytesRead = is.read(buffer, 0, 8192)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.close(); + is.close(); + } + +} diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/MediaMetadataRetrieverImpl.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/MediaMetadataRetrieverImpl.java index d70e546..6a24645 100644 --- a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/MediaMetadataRetrieverImpl.java +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/impl/MediaMetadataRetrieverImpl.java @@ -4,10 +4,14 @@ import android.graphics.Bitmap; import android.media.MediaMetadataRetriever; import android.net.Uri; +import android.text.TextUtils; import com.dyhdyh.compat.mmrc.IMediaMetadataRetriever; +import com.dyhdyh.compat.mmrc.transform.MetadataTransform; +import java.io.File; import java.io.FileDescriptor; +import java.io.FileNotFoundException; import java.util.Map; /** @@ -23,8 +27,8 @@ public MediaMetadataRetrieverImpl() { } @Override - public void setDataSource(String path) { - this.mRetriever.setDataSource(path); + public void setDataSource(File inputFile) throws FileNotFoundException { + this.mRetriever.setDataSource(inputFile.getAbsolutePath()); } @Override @@ -47,6 +51,7 @@ public void setDataSource(FileDescriptor fd) { this.mRetriever.setDataSource(fd); } + @Override public Bitmap getFrameAtTime() { return this.mRetriever.getFrameAtTime(); @@ -63,7 +68,8 @@ public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) { if (atTime == null) { return null; } - return Bitmap.createScaledBitmap(atTime, width, height, true); + final int[] size = makeRotateSize(width, height); + return Bitmap.createScaledBitmap(atTime, size[0], size[1], true); } @Override @@ -72,7 +78,8 @@ public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int heigh if (atTime == null) { return null; } - return Bitmap.createScaledBitmap(atTime, width, height, true); + final int[] size = makeRotateSize(width, height); + return Bitmap.createScaledBitmap(atTime, size[0], size[1], true); } @Override @@ -81,12 +88,37 @@ public byte[] getEmbeddedPicture() { } @Override - public String extractMetadata(String keyCode) { - return this.mRetriever.extractMetadata(Integer.parseInt(keyCode)); + public String extractMetadata(int keyCode) { + String keyCodeString = MetadataTransform.transform(getClass(), keyCode); + if (TextUtils.isEmpty(keyCodeString)) { + return null; + } + return this.mRetriever.extractMetadata(Integer.parseInt(keyCodeString)); } @Override public void release() { this.mRetriever.release(); } + + + private int[] makeRotateSize(int sourceWidth, int sourceHeight) { + final String rotationString = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + if (!TextUtils.isEmpty(rotationString)) { + try { + final int rotation = Integer.parseInt(rotationString); + if (isVertical(rotation)) { + return new int[]{sourceHeight, sourceWidth}; + } + } catch (NumberFormatException ignored) { + + } + } + return new int[]{sourceWidth, sourceHeight}; + } + + private boolean isVertical(float rotate) { + float abs = Math.abs(rotate); + return abs == 90 || abs == 270; + } } \ No newline at end of file diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataKey.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataKey.java index 1df7c3f..f931abb 100644 --- a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataKey.java +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataKey.java @@ -5,27 +5,43 @@ * created 2017/5/27 10:51 */ public class MetadataKey { - private String ffmpegMetadatakey; - private String metadatakey; + private String ffmpegMetadataKey; + private String metadataKey; + private String imageMetadataKey; - public MetadataKey(String ffmpegMetadatakey, String metadatakey) { - this.ffmpegMetadatakey = ffmpegMetadatakey; - this.metadatakey = metadatakey; + public MetadataKey(String ffmpegMetadataKey, String metadataKey) { + this.ffmpegMetadataKey = ffmpegMetadataKey; + this.metadataKey = metadataKey; + this.imageMetadataKey = metadataKey; } - public String getFfmpegMetadatakey() { - return ffmpegMetadatakey; + public MetadataKey(String ffmpegMetadataKey, String metadataKey, String imageMetadataKey) { + this.ffmpegMetadataKey = ffmpegMetadataKey; + this.metadataKey = metadataKey; + this.imageMetadataKey = imageMetadataKey; } - public void setFfmpegMetadatakey(String ffmpegMetadatakey) { - this.ffmpegMetadatakey = ffmpegMetadatakey; + public String getFFmpegMetadataKey() { + return ffmpegMetadataKey; } - public String getMetadatakey() { - return metadatakey; + public void setFFmpegMetadataKey(String ffmpegMetadataKey) { + this.ffmpegMetadataKey = ffmpegMetadataKey; } - public void setMetadatakey(String metadatakey) { - this.metadatakey = metadatakey; + public String getMetadataKey() { + return metadataKey; + } + + public void setMetadataKey(String metadataKey) { + this.metadataKey = metadataKey; + } + + public String getImageMetadataKey() { + return imageMetadataKey; + } + + public void setImageMetadataKey(String imageMetadataKey) { + this.imageMetadataKey = imageMetadataKey; } } diff --git a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataTransform.java b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataTransform.java index 88de595..f1cb67e 100644 --- a/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataTransform.java +++ b/media-metadata-retriever-compat/src/main/java/com/dyhdyh/compat/mmrc/transform/MetadataTransform.java @@ -6,6 +6,7 @@ import com.dyhdyh.compat.mmrc.IMediaMetadataRetriever; import com.dyhdyh.compat.mmrc.MediaMetadataRetrieverCompat; import com.dyhdyh.compat.mmrc.impl.FFmpegMediaMetadataRetrieverImpl; +import com.dyhdyh.compat.mmrc.impl.ImageMediaMetadataRetrieverImpl; import com.dyhdyh.compat.mmrc.impl.MediaMetadataRetrieverImpl; import wseemann.media.FFmpegMediaMetadataRetriever; @@ -45,7 +46,8 @@ public class MetadataTransform { METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_DURATION, new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_DURATION, - String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION))); + String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), + String.valueOf(ImageMediaMetadataRetrieverImpl.METADATA_KEY_DURATION))); METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_NUM_TRACKS, new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_TRACK, @@ -59,17 +61,20 @@ public class MetadataTransform { new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_DISC, String.valueOf(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER))); - METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_WIDTH, + METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_WIDTH, new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH, - String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH))); + String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH), + String.valueOf(ImageMediaMetadataRetrieverImpl.METADATA_KEY_WIDTH))); - METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_HEIGHT, + METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_HEIGHT, new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT, - String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT))); + String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT), + String.valueOf(ImageMediaMetadataRetrieverImpl.METADATA_KEY_HEIGHT))); - METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_ROTATION, + METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_ROTATION, new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION, - String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION))); + String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION), + String.valueOf(ImageMediaMetadataRetrieverImpl.METADATA_KEY_ROTATION))); METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_CAPTURE_FRAMERATE, new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_FRAMERATE, @@ -77,12 +82,14 @@ public class MetadataTransform { } - public static String transform(Class clazz, int metadataKeyCode) { - MetadataKey metadataKey = METADATA_KEYS.get(metadataKeyCode); + public static String transform(Class clazz, int compatMetadataKeyCode) { + MetadataKey metadataKey = METADATA_KEYS.get(compatMetadataKeyCode); if (clazz.getName().equals(FFmpegMediaMetadataRetrieverImpl.class.getName())) { - return metadataKey.getFfmpegMetadatakey(); + return metadataKey.getFFmpegMetadataKey(); } else if (clazz.getName().equals(MediaMetadataRetrieverImpl.class.getName())) { - return metadataKey.getMetadatakey(); + return metadataKey.getMetadataKey(); + } else if (clazz.getName().equals(ImageMediaMetadataRetrieverImpl.class.getName())) { + return metadataKey.getImageMetadataKey(); } return null; } diff --git a/screenshot/example-download.png b/screenshot/example-download.png deleted file mode 100644 index 8505abb..0000000 Binary files a/screenshot/example-download.png and /dev/null differ diff --git a/screenshot/example-download_1.0.8.png b/screenshot/example-download_1.0.8.png new file mode 100644 index 0000000..731f047 Binary files /dev/null and b/screenshot/example-download_1.0.8.png differ diff --git a/screenshot/screenshot.gif b/screenshot/screenshot.gif new file mode 100644 index 0000000..27e0c93 Binary files /dev/null and b/screenshot/screenshot.gif differ diff --git a/screenshot/screenshot_android.gif b/screenshot/screenshot_android.gif deleted file mode 100644 index 00a4cb3..0000000 Binary files a/screenshot/screenshot_android.gif and /dev/null differ diff --git a/screenshot/screenshot_auto.gif b/screenshot/screenshot_auto.gif deleted file mode 100644 index 23fdf1d..0000000 Binary files a/screenshot/screenshot_auto.gif and /dev/null differ diff --git a/screenshot/screenshot_ffmpeg.gif b/screenshot/screenshot_ffmpeg.gif deleted file mode 100644 index b58dd98..0000000 Binary files a/screenshot/screenshot_ffmpeg.gif and /dev/null differ