Kotlin migration (WIP)

This commit is contained in:
Thibault Deckers 2020-10-07 16:00:53 +09:00
parent 60d16a3e17
commit 46df3e98de
6 changed files with 327 additions and 346 deletions

View file

@ -1,41 +0,0 @@
package deckers.thibault.aves.model;
import android.net.Uri;
import androidx.annotation.Nullable;
import java.util.Map;
import deckers.thibault.aves.utils.MimeTypes;
public class AvesImageEntry {
public Uri uri; // content or file URI
public String path; // best effort to get local path
public String mimeType;
@Nullable
public Integer width, height, rotationDegrees;
@Nullable
public Long dateModifiedSecs;
public AvesImageEntry(Map<String, Object> map) {
this.uri = Uri.parse((String) map.get("uri"));
this.path = (String) map.get("path");
this.mimeType = (String) map.get("mimeType");
this.width = (Integer) map.get("width");
this.height = (Integer) map.get("height");
this.rotationDegrees = (Integer) map.get("rotationDegrees");
this.dateModifiedSecs = toLong(map.get("dateModifiedSecs"));
}
public boolean isVideo() {
return mimeType.startsWith(MimeTypes.VIDEO);
}
// convenience method
private static Long toLong(Object o) {
if (o == null) return null;
if (o instanceof Integer) return Long.valueOf((Integer) o);
return (long) o;
}
}

View file

@ -1,292 +0,0 @@
package deckers.thibault.aves.model;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.avi.AviDirectory;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.jpeg.JpegDirectory;
import com.drew.metadata.mp4.Mp4Directory;
import com.drew.metadata.mp4.media.Mp4VideoDirectory;
import com.drew.metadata.photoshop.PsdHeaderDirectory;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import deckers.thibault.aves.utils.MetadataHelper;
import deckers.thibault.aves.utils.MimeTypes;
import deckers.thibault.aves.utils.StorageUtils;
public class SourceImageEntry {
public Uri uri; // content or file URI
public String path; // best effort to get local path
public String sourceMimeType;
@Nullable
public String title;
@Nullable
public Integer width, height, rotationDegrees;
@Nullable
public Boolean isFlipped;
@Nullable
public Long sizeBytes;
@Nullable
public Long dateModifiedSecs;
@Nullable
private Long sourceDateTakenMillis;
@Nullable
private Long durationMillis;
public SourceImageEntry() {
}
public SourceImageEntry(@NonNull Map<String, Object> map) {
this.uri = Uri.parse((String) map.get("uri"));
this.path = (String) map.get("path");
this.sourceMimeType = (String) map.get("sourceMimeType");
this.width = (int) map.get("width");
this.height = (int) map.get("height");
this.rotationDegrees = (int) map.get("rotationDegrees");
this.sizeBytes = toLong(map.get("sizeBytes"));
this.title = (String) map.get("title");
this.dateModifiedSecs = toLong(map.get("dateModifiedSecs"));
this.sourceDateTakenMillis = toLong(map.get("sourceDateTakenMillis"));
this.durationMillis = toLong(map.get("durationMillis"));
}
public Map<String, Object> toMap() {
return new HashMap<String, Object>() {{
put("uri", uri.toString());
put("path", path);
put("sourceMimeType", sourceMimeType);
put("width", width);
put("height", height);
put("rotationDegrees", rotationDegrees != null ? rotationDegrees : 0);
put("isFlipped", isFlipped != null ? isFlipped : false);
put("sizeBytes", sizeBytes);
put("title", title);
put("dateModifiedSecs", dateModifiedSecs);
put("sourceDateTakenMillis", sourceDateTakenMillis);
put("durationMillis", durationMillis);
// only for map export
put("contentId", getContentId());
}};
}
private Long getContentId() {
if (uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
try {
return ContentUris.parseId(uri);
} catch (NumberFormatException | UnsupportedOperationException e) {
// ignore when the ID is not a number
// e.g. content://com.sec.android.app.myfiles.FileProvider/device_storage/20200109_162621.jpg
}
}
return null;
}
public boolean hasSize() {
return width != null && width > 0 && height != null && height > 0;
}
public boolean hasOrientation() {
return rotationDegrees != null;
}
private boolean hasDuration() {
return durationMillis != null && durationMillis > 0;
}
private boolean isImage() {
return sourceMimeType.startsWith(MimeTypes.IMAGE);
}
public boolean isSvg() {
return sourceMimeType.equals(MimeTypes.SVG);
}
private boolean isVideo() {
return sourceMimeType.startsWith(MimeTypes.VIDEO);
}
// metadata retrieval
// expects entry with: uri, mimeType
// finds: width, height, orientation/rotation, date, title, duration
public SourceImageEntry fillPreCatalogMetadata(@NonNull Context context) {
if (isSvg()) return this;
fillByMediaMetadataRetriever(context);
if (hasSize() && hasOrientation() && (!isVideo() || hasDuration())) return this;
fillByMetadataExtractor(context);
if (hasSize()) return this;
fillByBitmapDecode(context);
return this;
}
// expects entry with: uri, mimeType
// finds: width, height, orientation/rotation, date, title, duration
private void fillByMediaMetadataRetriever(@NonNull Context context) {
if (isImage()) return;
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, uri);
if (retriever != null) {
try {
String width = null, height = null, rotation = null, durationMillis = null;
if (isImage()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
}
} else if (isVideo()) {
width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
durationMillis = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
}
if (width != null) {
this.width = Integer.parseInt(width);
}
if (height != null) {
this.height = Integer.parseInt(height);
}
if (rotation != null) {
this.rotationDegrees = Integer.parseInt(rotation);
}
if (durationMillis != null) {
this.durationMillis = Long.parseLong(durationMillis);
}
String dateString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE);
long dateMillis = MetadataHelper.parseVideoMetadataDate(dateString);
// some entries have an invalid default date (19040101T000000.000Z) that is before Epoch time
if (dateMillis > 0) {
this.sourceDateTakenMillis = dateMillis;
}
String title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
if (title != null) {
this.title = title;
}
} catch (Exception e) {
// ignore
} finally {
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
retriever.release();
}
}
}
// expects entry with: uri, mimeType
// finds: width, height, orientation, date
private void fillByMetadataExtractor(@NonNull Context context) {
if (!MimeTypes.isSupportedByMetadataExtractor(sourceMimeType)) return;
try (InputStream is = StorageUtils.openInputStream(context, uri)) {
Metadata metadata = ImageMetadataReader.readMetadata(is);
// do not switch on specific mime types, as the reported mime type could be wrong
// (e.g. PNG registered as JPG)
if (isVideo()) {
for (AviDirectory dir : metadata.getDirectoriesOfType(AviDirectory.class)) {
if (dir.containsTag(AviDirectory.TAG_WIDTH)) {
width = dir.getInt(AviDirectory.TAG_WIDTH);
}
if (dir.containsTag(AviDirectory.TAG_HEIGHT)) {
height = dir.getInt(AviDirectory.TAG_HEIGHT);
}
if (dir.containsTag(AviDirectory.TAG_DURATION)) {
durationMillis = dir.getLong(AviDirectory.TAG_DURATION);
}
}
for (Mp4VideoDirectory dir : metadata.getDirectoriesOfType(Mp4VideoDirectory.class)) {
if (dir.containsTag(Mp4VideoDirectory.TAG_WIDTH)) {
width = dir.getInt(Mp4VideoDirectory.TAG_WIDTH);
}
if (dir.containsTag(Mp4VideoDirectory.TAG_HEIGHT)) {
height = dir.getInt(Mp4VideoDirectory.TAG_HEIGHT);
}
}
for (Mp4Directory dir : metadata.getDirectoriesOfType(Mp4Directory.class)) {
if (dir.containsTag(Mp4Directory.TAG_DURATION)) {
durationMillis = dir.getLong(Mp4Directory.TAG_DURATION);
}
}
} else {
for (JpegDirectory dir : metadata.getDirectoriesOfType(JpegDirectory.class)) {
if (dir.containsTag(JpegDirectory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);
}
if (dir.containsTag(JpegDirectory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
}
}
for (PsdHeaderDirectory dir : metadata.getDirectoriesOfType(PsdHeaderDirectory.class)) {
if (dir.containsTag(PsdHeaderDirectory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(PsdHeaderDirectory.TAG_IMAGE_WIDTH);
}
if (dir.containsTag(PsdHeaderDirectory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(PsdHeaderDirectory.TAG_IMAGE_HEIGHT);
}
}
// EXIF, if defined, should override metadata found in other directories
for (ExifIFD0Directory dir : metadata.getDirectoriesOfType(ExifIFD0Directory.class)) {
if (dir.containsTag(ExifIFD0Directory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(ExifIFD0Directory.TAG_IMAGE_WIDTH);
}
if (dir.containsTag(ExifIFD0Directory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(ExifIFD0Directory.TAG_IMAGE_HEIGHT);
}
if (dir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
int exifOrientation = dir.getInt(ExifIFD0Directory.TAG_ORIENTATION);
rotationDegrees = MetadataHelper.getRotationDegreesForExifCode(exifOrientation);
isFlipped = MetadataHelper.isFlippedForExifCode(exifOrientation);
}
if (dir.containsTag(ExifIFD0Directory.TAG_DATETIME)) {
sourceDateTakenMillis = dir.getDate(ExifIFD0Directory.TAG_DATETIME, null, TimeZone.getDefault()).getTime();
}
}
}
} catch (IOException | ImageProcessingException | MetadataException | NoClassDefFoundError e) {
// ignore
}
}
// expects entry with: uri
// finds: width, height
private void fillByBitmapDecode(@NonNull Context context) {
try (InputStream is = StorageUtils.openInputStream(context, uri)) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
width = options.outWidth;
height = options.outHeight;
} catch (IOException e) {
// ignore
}
}
// convenience method
private static Long toLong(@Nullable Object o) {
if (o == null) return null;
if (o instanceof Integer) return Long.valueOf((Integer) o);
return (long) o;
}
}

View file

@ -10,12 +10,9 @@ import deckers.thibault.aves.model.SourceImageEntry;
class ContentImageProvider extends ImageProvider { class ContentImageProvider extends ImageProvider {
@Override @Override
public void fetchSingle(@NonNull final Context context, @NonNull final Uri uri, @NonNull final String mimeType, @NonNull final ImageOpCallback callback) { public void fetchSingle(@NonNull final Context context, @NonNull final Uri uri, @NonNull final String mimeType, @NonNull final ImageOpCallback callback) {
SourceImageEntry entry = new SourceImageEntry(); SourceImageEntry entry = new SourceImageEntry(uri, mimeType).fillPreCatalogMetadata(context);
entry.uri = uri;
entry.sourceMimeType = mimeType;
entry.fillPreCatalogMetadata(context);
if (entry.hasSize() || entry.isSvg()) { if (entry.getHasSize() || entry.isSvg()) {
callback.onSuccess(entry.toMap()); callback.onSuccess(entry.toMap());
} else { } else {
callback.onFailure(new Exception("entry has no size")); callback.onFailure(new Exception("entry has no size"));

View file

@ -13,19 +13,14 @@ import deckers.thibault.aves.utils.FileUtils;
class FileImageProvider extends ImageProvider { class FileImageProvider extends ImageProvider {
@Override @Override
public void fetchSingle(@NonNull final Context context, @NonNull final Uri uri, @NonNull final String mimeType, @NonNull final ImageOpCallback callback) { public void fetchSingle(@NonNull final Context context, @NonNull final Uri uri, @NonNull final String mimeType, @NonNull final ImageOpCallback callback) {
SourceImageEntry entry = new SourceImageEntry(); SourceImageEntry entry = new SourceImageEntry(uri, mimeType);
entry.uri = uri;
entry.sourceMimeType = mimeType;
String path = FileUtils.getPathFromUri(context, uri); String path = FileUtils.getPathFromUri(context, uri);
if (path != null) { if (path != null) {
try { try {
File file = new File(path); File file = new File(path);
if (file.exists()) { if (file.exists()) {
entry.path = path; entry.initFromFile(path, file.getName(), file.length(), file.lastModified() / 1000);
entry.title = file.getName();
entry.sizeBytes = file.length();
entry.dateModifiedSecs = file.lastModified() / 1000;
} }
} catch (SecurityException e) { } catch (SecurityException e) {
callback.onFailure(e); callback.onFailure(e);
@ -33,7 +28,7 @@ class FileImageProvider extends ImageProvider {
} }
entry.fillPreCatalogMetadata(context); entry.fillPreCatalogMetadata(context);
if (entry.hasSize() || entry.isSvg()) { if (entry.getHasSize() || entry.isSvg()) {
callback.onSuccess(entry.toMap()); callback.onSuccess(entry.toMap());
} else { } else {
callback.onFailure(new Exception("entry has no size")); callback.onFailure(new Exception("entry has no size"));

View file

@ -0,0 +1,38 @@
package deckers.thibault.aves.model
import android.net.Uri
import deckers.thibault.aves.utils.MimeTypes
class AvesImageEntry(map: Map<String?, Any?>) {
@JvmField
val uri: Uri = Uri.parse(map["uri"] as String) // content or file URI
@JvmField
val path = map["path"] as String? // best effort to get local path
@JvmField
val mimeType = map["mimeType"] as String
@JvmField
val width = map["width"] as Int
@JvmField
val height = map["height"] as Int
@JvmField
val rotationDegrees = map["rotationDegrees"] as Int
@JvmField
val dateModifiedSecs = toLong(map["dateModifiedSecs"])
val isVideo: Boolean
get() = mimeType.startsWith(MimeTypes.VIDEO)
companion object {
// convenience method
private fun toLong(o: Any?): Long? = when (o) {
is Int -> o.toLong()
else -> o as? Long
}
}
}

View file

@ -0,0 +1,284 @@
package deckers.thibault.aves.model
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import com.drew.imaging.ImageMetadataReader
import com.drew.metadata.avi.AviDirectory
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.jpeg.JpegDirectory
import com.drew.metadata.mp4.Mp4Directory
import com.drew.metadata.mp4.media.Mp4VideoDirectory
import com.drew.metadata.photoshop.PsdHeaderDirectory
import deckers.thibault.aves.utils.MetadataHelper.getRotationDegreesForExifCode
import deckers.thibault.aves.utils.MetadataHelper.isFlippedForExifCode
import deckers.thibault.aves.utils.MetadataHelper.parseVideoMetadataDate
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor
import deckers.thibault.aves.utils.StorageUtils
import java.io.IOException
import java.util.*
class SourceImageEntry {
val uri: Uri // content or file URI
var path: String? = null // best effort to get local path
private val sourceMimeType: String
var title: String? = null
var width: Int? = null
var height: Int? = null
private var rotationDegrees: Int? = null
private var isFlipped: Boolean? = null
var sizeBytes: Long? = null
var dateModifiedSecs: Long? = null
private var sourceDateTakenMillis: Long? = null
private var durationMillis: Long? = null
constructor(uri: Uri, sourceMimeType: String) {
this.uri = uri
this.sourceMimeType = sourceMimeType
}
constructor(map: Map<String, Any?>) {
uri = Uri.parse(map["uri"] as String)
path = map["path"] as String?
sourceMimeType = map["sourceMimeType"] as String
width = map["width"] as Int
height = map["height"] as Int
rotationDegrees = map["rotationDegrees"] as Int
sizeBytes = toLong(map["sizeBytes"])
title = map["title"] as String?
dateModifiedSecs = toLong(map["dateModifiedSecs"])
sourceDateTakenMillis = toLong(map["sourceDateTakenMillis"])
durationMillis = toLong(map["durationMillis"])
}
fun initFromFile(path: String, title: String, sizeBytes: Long, dateModifiedSecs: Long) {
this.path = path
this.title = title
this.sizeBytes = sizeBytes
this.dateModifiedSecs = dateModifiedSecs
}
fun toMap(): Map<String, Any?> {
return hashMapOf(
"uri" to uri.toString(),
"path" to path,
"sourceMimeType" to sourceMimeType,
"width" to width,
"height" to height,
"rotationDegrees" to (rotationDegrees ?: 0),
"isFlipped" to (isFlipped ?: false),
"sizeBytes" to sizeBytes,
"title" to title,
"dateModifiedSecs" to dateModifiedSecs,
"sourceDateTakenMillis" to sourceDateTakenMillis,
"durationMillis" to durationMillis,
// only for map export
"contentId" to contentId,
)
}
// ignore when the ID is not a number
// e.g. content://com.sec.android.app.myfiles.FileProvider/device_storage/20200109_162621.jpg
private val contentId: Long?
get() {
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
try {
return ContentUris.parseId(uri)
} catch (e: Exception) {
// ignore
}
}
return null
}
val hasSize: Boolean
get() = width ?: 0 > 0 && height ?: 0 > 0
private val hasOrientation: Boolean
get() = rotationDegrees != null
private val hasDuration: Boolean
get() = durationMillis ?: 0 > 0
private val isImage: Boolean
get() = sourceMimeType.startsWith(MimeTypes.IMAGE)
private val isVideo: Boolean
get() = sourceMimeType.startsWith(MimeTypes.VIDEO)
val isSvg: Boolean
get() = sourceMimeType == MimeTypes.SVG
// metadata retrieval
// expects entry with: uri, mimeType
// finds: width, height, orientation/rotation, date, title, duration
fun fillPreCatalogMetadata(context: Context): SourceImageEntry {
if (isSvg) return this
fillByMediaMetadataRetriever(context)
if (hasSize && hasOrientation && (!isVideo || hasDuration)) return this
fillByMetadataExtractor(context)
if (hasSize) return this
fillByBitmapDecode(context)
return this
}
// expects entry with: uri, mimeType
// finds: width, height, orientation/rotation, date, title, duration
private fun fillByMediaMetadataRetriever(context: Context) {
if (isImage) return
val retriever = StorageUtils.openMetadataRetriever(context, uri) ?: return
try {
var width: String? = null
var height: String? = null
var rotationDegrees: String? = null
var durationMillis: String? = null
if (isImage) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)
height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)
rotationDegrees = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)
}
} else if (isVideo) {
width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
rotationDegrees = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
durationMillis = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
}
if (width != null) {
this.width = width.toInt()
}
if (height != null) {
this.height = height.toInt()
}
if (rotationDegrees != null) {
this.rotationDegrees = rotationDegrees.toInt()
}
if (durationMillis != null) {
this.durationMillis = durationMillis.toLong()
}
val dateString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE)
val dateMillis = parseVideoMetadataDate(dateString)
// some entries have an invalid default date (19040101T000000.000Z) that is before Epoch time
if (dateMillis > 0) {
sourceDateTakenMillis = dateMillis
}
val title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
if (title != null) {
this.title = title
}
} catch (e: Exception) {
// ignore
} finally {
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
retriever.release()
}
}
// expects entry with: uri, mimeType
// finds: width, height, orientation, date
private fun fillByMetadataExtractor(context: Context) {
if (!isSupportedByMetadataExtractor(sourceMimeType)) return
try {
StorageUtils.openInputStream(context, uri).use { input ->
val metadata = ImageMetadataReader.readMetadata(input)
// do not switch on specific mime types, as the reported mime type could be wrong
// (e.g. PNG registered as JPG)
if (isVideo) {
for (dir in metadata.getDirectoriesOfType(AviDirectory::class.java)) {
if (dir.containsTag(AviDirectory.TAG_WIDTH)) {
width = dir.getInt(AviDirectory.TAG_WIDTH)
}
if (dir.containsTag(AviDirectory.TAG_HEIGHT)) {
height = dir.getInt(AviDirectory.TAG_HEIGHT)
}
if (dir.containsTag(AviDirectory.TAG_DURATION)) {
durationMillis = dir.getLong(AviDirectory.TAG_DURATION)
}
}
for (dir in metadata.getDirectoriesOfType(Mp4VideoDirectory::class.java)) {
if (dir.containsTag(Mp4VideoDirectory.TAG_WIDTH)) {
width = dir.getInt(Mp4VideoDirectory.TAG_WIDTH)
}
if (dir.containsTag(Mp4VideoDirectory.TAG_HEIGHT)) {
height = dir.getInt(Mp4VideoDirectory.TAG_HEIGHT)
}
}
for (dir in metadata.getDirectoriesOfType(Mp4Directory::class.java)) {
if (dir.containsTag(Mp4Directory.TAG_DURATION)) {
durationMillis = dir.getLong(Mp4Directory.TAG_DURATION)
}
}
} else {
for (dir in metadata.getDirectoriesOfType(JpegDirectory::class.java)) {
if (dir.containsTag(JpegDirectory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH)
}
if (dir.containsTag(JpegDirectory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT)
}
}
for (dir in metadata.getDirectoriesOfType(PsdHeaderDirectory::class.java)) {
if (dir.containsTag(PsdHeaderDirectory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(PsdHeaderDirectory.TAG_IMAGE_WIDTH)
}
if (dir.containsTag(PsdHeaderDirectory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(PsdHeaderDirectory.TAG_IMAGE_HEIGHT)
}
}
// EXIF, if defined, should override metadata found in other directories
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
if (dir.containsTag(ExifIFD0Directory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(ExifIFD0Directory.TAG_IMAGE_WIDTH)
}
if (dir.containsTag(ExifIFD0Directory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(ExifIFD0Directory.TAG_IMAGE_HEIGHT)
}
if (dir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
val exifOrientation = dir.getInt(ExifIFD0Directory.TAG_ORIENTATION)
rotationDegrees = getRotationDegreesForExifCode(exifOrientation)
isFlipped = isFlippedForExifCode(exifOrientation)
}
if (dir.containsTag(ExifIFD0Directory.TAG_DATETIME)) {
sourceDateTakenMillis = dir.getDate(ExifIFD0Directory.TAG_DATETIME, null, TimeZone.getDefault()).time
}
}
}
}
} catch (e: Exception) {
// ignore
} catch (e: NoClassDefFoundError) {
// ignore
}
}
// expects entry with: uri
// finds: width, height
private fun fillByBitmapDecode(context: Context) {
try {
StorageUtils.openInputStream(context, uri).use { input ->
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeStream(input, null, options)
width = options.outWidth
height = options.outHeight
}
} catch (e: IOException) {
// ignore
}
}
companion object {
// convenience method
private fun toLong(o: Any?): Long? = when (o) {
is Int -> o.toLong()
else -> o as? Long
}
}
}