debug: ExifInterface metadata list
This commit is contained in:
parent
b9ffc2d5f0
commit
bbc9d69ba5
4 changed files with 238 additions and 28 deletions
|
@ -12,6 +12,7 @@ import android.util.Log;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
|
||||
import com.adobe.internal.xmp.XMPException;
|
||||
import com.adobe.internal.xmp.XMPIterator;
|
||||
|
@ -190,6 +191,9 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
case "getContentResolverMetadata":
|
||||
new Thread(() -> getContentResolverMetadata(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "getExifInterfaceMetadata":
|
||||
new Thread(() -> getExifInterfaceMetadata(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "getEmbeddedPictures":
|
||||
new Thread(() -> getEmbeddedPictures(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
|
@ -533,6 +537,27 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private void getExifInterfaceMetadata(MethodCall call, MethodChannel.Result result) {
|
||||
String uriString = call.argument("uri");
|
||||
if (uriString == null) {
|
||||
result.error("getExifInterfaceMetadata-args", "failed because of missing arguments", null);
|
||||
return;
|
||||
}
|
||||
|
||||
try (InputStream is = StorageUtils.openInputStream(context, Uri.parse(uriString))) {
|
||||
ExifInterface exif = new ExifInterface(is);
|
||||
Map<String, Object> metadataMap = new HashMap<>();
|
||||
for (String tag : MetadataHelper.ExifInterfaceTags) {
|
||||
if (exif.hasAttribute(tag)) {
|
||||
metadataMap.put(tag, exif.getAttribute(tag));
|
||||
}
|
||||
}
|
||||
result.success(metadataMap);
|
||||
} catch (IOException e) {
|
||||
result.error("getExifInterfaceMetadata-failure", "failed to get exif for uri=" + uriString, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void getEmbeddedPictures(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Uri uri = Uri.parse(call.argument("uri"));
|
||||
List<byte[]> pictures = new ArrayList<>();
|
||||
|
@ -544,7 +569,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
pictures.add(picture);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.error("getVideoEmbeddedPictures-failure", "failed to get embedded picture for uri=" + uri, e);
|
||||
result.error("getVideoEmbeddedPictures-failure", "failed to get embedded picture for uri=" + uri, e.getMessage());
|
||||
} finally {
|
||||
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
|
||||
retriever.release();
|
||||
|
|
|
@ -1,20 +1,183 @@
|
|||
package deckers.thibault.aves.utils
|
||||
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import java.text.DateFormat
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object MetadataHelper {
|
||||
// list of known tags from ExifInterface as of androidx.exifinterface:exifinterface:1.3.0
|
||||
@JvmField
|
||||
val ExifInterfaceTags = arrayOf(
|
||||
ExifInterface.TAG_APERTURE_VALUE,
|
||||
ExifInterface.TAG_ARTIST,
|
||||
ExifInterface.TAG_BITS_PER_SAMPLE,
|
||||
ExifInterface.TAG_BODY_SERIAL_NUMBER,
|
||||
ExifInterface.TAG_BRIGHTNESS_VALUE,
|
||||
ExifInterface.TAG_CAMERA_OWNER_NAME,
|
||||
ExifInterface.TAG_CFA_PATTERN,
|
||||
ExifInterface.TAG_COLOR_SPACE,
|
||||
ExifInterface.TAG_COMPONENTS_CONFIGURATION,
|
||||
ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
|
||||
ExifInterface.TAG_COMPRESSION,
|
||||
ExifInterface.TAG_CONTRAST,
|
||||
ExifInterface.TAG_COPYRIGHT,
|
||||
ExifInterface.TAG_CUSTOM_RENDERED,
|
||||
ExifInterface.TAG_DATETIME,
|
||||
ExifInterface.TAG_DATETIME_DIGITIZED,
|
||||
ExifInterface.TAG_DATETIME_ORIGINAL,
|
||||
ExifInterface.TAG_DEFAULT_CROP_SIZE,
|
||||
ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
|
||||
ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
|
||||
ExifInterface.TAG_DNG_VERSION,
|
||||
ExifInterface.TAG_EXIF_VERSION,
|
||||
ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
|
||||
ExifInterface.TAG_EXPOSURE_INDEX,
|
||||
ExifInterface.TAG_EXPOSURE_MODE,
|
||||
ExifInterface.TAG_EXPOSURE_PROGRAM,
|
||||
ExifInterface.TAG_EXPOSURE_TIME,
|
||||
ExifInterface.TAG_FILE_SOURCE,
|
||||
ExifInterface.TAG_FLASH,
|
||||
ExifInterface.TAG_FLASHPIX_VERSION,
|
||||
ExifInterface.TAG_FLASH_ENERGY,
|
||||
ExifInterface.TAG_FOCAL_LENGTH,
|
||||
ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM,
|
||||
ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
|
||||
ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
|
||||
ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
|
||||
ExifInterface.TAG_F_NUMBER,
|
||||
ExifInterface.TAG_GAIN_CONTROL,
|
||||
ExifInterface.TAG_GAMMA,
|
||||
ExifInterface.TAG_GPS_ALTITUDE,
|
||||
ExifInterface.TAG_GPS_ALTITUDE_REF,
|
||||
ExifInterface.TAG_GPS_AREA_INFORMATION,
|
||||
ExifInterface.TAG_GPS_DATESTAMP,
|
||||
ExifInterface.TAG_GPS_DEST_BEARING,
|
||||
ExifInterface.TAG_GPS_DEST_BEARING_REF,
|
||||
ExifInterface.TAG_GPS_DEST_DISTANCE,
|
||||
ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
|
||||
ExifInterface.TAG_GPS_DEST_LATITUDE,
|
||||
ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
|
||||
ExifInterface.TAG_GPS_DEST_LONGITUDE,
|
||||
ExifInterface.TAG_GPS_DEST_LONGITUDE_REF,
|
||||
ExifInterface.TAG_GPS_DIFFERENTIAL,
|
||||
ExifInterface.TAG_GPS_DOP,
|
||||
ExifInterface.TAG_GPS_H_POSITIONING_ERROR,
|
||||
ExifInterface.TAG_GPS_IMG_DIRECTION,
|
||||
ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
|
||||
ExifInterface.TAG_GPS_LATITUDE,
|
||||
ExifInterface.TAG_GPS_LATITUDE_REF,
|
||||
ExifInterface.TAG_GPS_LONGITUDE,
|
||||
ExifInterface.TAG_GPS_LONGITUDE_REF,
|
||||
ExifInterface.TAG_GPS_MAP_DATUM,
|
||||
ExifInterface.TAG_GPS_MEASURE_MODE,
|
||||
ExifInterface.TAG_GPS_PROCESSING_METHOD,
|
||||
ExifInterface.TAG_GPS_SATELLITES,
|
||||
ExifInterface.TAG_GPS_SPEED,
|
||||
ExifInterface.TAG_GPS_SPEED_REF,
|
||||
ExifInterface.TAG_GPS_STATUS,
|
||||
ExifInterface.TAG_GPS_TIMESTAMP,
|
||||
ExifInterface.TAG_GPS_TRACK,
|
||||
ExifInterface.TAG_GPS_TRACK_REF,
|
||||
ExifInterface.TAG_GPS_VERSION_ID,
|
||||
ExifInterface.TAG_IMAGE_DESCRIPTION,
|
||||
ExifInterface.TAG_IMAGE_LENGTH,
|
||||
ExifInterface.TAG_IMAGE_UNIQUE_ID,
|
||||
ExifInterface.TAG_IMAGE_WIDTH,
|
||||
ExifInterface.TAG_INTEROPERABILITY_INDEX,
|
||||
ExifInterface.TAG_ISO_SPEED,
|
||||
ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY,
|
||||
ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ,
|
||||
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
|
||||
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
|
||||
ExifInterface.TAG_LENS_MAKE,
|
||||
ExifInterface.TAG_LENS_MODEL,
|
||||
ExifInterface.TAG_LENS_SERIAL_NUMBER,
|
||||
ExifInterface.TAG_LENS_SPECIFICATION,
|
||||
ExifInterface.TAG_LIGHT_SOURCE,
|
||||
ExifInterface.TAG_MAKE,
|
||||
ExifInterface.TAG_MAKER_NOTE,
|
||||
ExifInterface.TAG_MAX_APERTURE_VALUE,
|
||||
ExifInterface.TAG_METERING_MODE,
|
||||
ExifInterface.TAG_MODEL,
|
||||
ExifInterface.TAG_NEW_SUBFILE_TYPE,
|
||||
ExifInterface.TAG_OECF,
|
||||
ExifInterface.TAG_OFFSET_TIME,
|
||||
ExifInterface.TAG_OFFSET_TIME_DIGITIZED,
|
||||
ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
|
||||
ExifInterface.TAG_ORF_ASPECT_FRAME,
|
||||
ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH,
|
||||
ExifInterface.TAG_ORF_PREVIEW_IMAGE_START,
|
||||
ExifInterface.TAG_ORF_THUMBNAIL_IMAGE,
|
||||
ExifInterface.TAG_ORIENTATION,
|
||||
ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
|
||||
ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
|
||||
ExifInterface.TAG_PIXEL_X_DIMENSION,
|
||||
ExifInterface.TAG_PIXEL_Y_DIMENSION,
|
||||
ExifInterface.TAG_PLANAR_CONFIGURATION,
|
||||
ExifInterface.TAG_PRIMARY_CHROMATICITIES,
|
||||
ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX,
|
||||
ExifInterface.TAG_REFERENCE_BLACK_WHITE,
|
||||
ExifInterface.TAG_RELATED_SOUND_FILE,
|
||||
ExifInterface.TAG_RESOLUTION_UNIT,
|
||||
ExifInterface.TAG_ROWS_PER_STRIP,
|
||||
ExifInterface.TAG_RW2_ISO,
|
||||
ExifInterface.TAG_RW2_JPG_FROM_RAW,
|
||||
ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER,
|
||||
ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER,
|
||||
ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER,
|
||||
ExifInterface.TAG_RW2_SENSOR_TOP_BORDER,
|
||||
ExifInterface.TAG_SAMPLES_PER_PIXEL,
|
||||
ExifInterface.TAG_SATURATION,
|
||||
ExifInterface.TAG_SCENE_CAPTURE_TYPE,
|
||||
ExifInterface.TAG_SCENE_TYPE,
|
||||
ExifInterface.TAG_SENSING_METHOD,
|
||||
ExifInterface.TAG_SENSITIVITY_TYPE,
|
||||
ExifInterface.TAG_SHARPNESS,
|
||||
ExifInterface.TAG_SHUTTER_SPEED_VALUE,
|
||||
ExifInterface.TAG_SOFTWARE,
|
||||
ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
|
||||
ExifInterface.TAG_SPECTRAL_SENSITIVITY,
|
||||
ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY,
|
||||
ExifInterface.TAG_STRIP_BYTE_COUNTS,
|
||||
ExifInterface.TAG_STRIP_OFFSETS,
|
||||
ExifInterface.TAG_SUBFILE_TYPE,
|
||||
ExifInterface.TAG_SUBJECT_AREA,
|
||||
ExifInterface.TAG_SUBJECT_DISTANCE,
|
||||
ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
|
||||
ExifInterface.TAG_SUBJECT_LOCATION,
|
||||
ExifInterface.TAG_SUBSEC_TIME,
|
||||
ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
|
||||
ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
|
||||
ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH,
|
||||
ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH,
|
||||
ExifInterface.TAG_TRANSFER_FUNCTION,
|
||||
ExifInterface.TAG_USER_COMMENT,
|
||||
ExifInterface.TAG_WHITE_BALANCE,
|
||||
ExifInterface.TAG_WHITE_POINT,
|
||||
ExifInterface.TAG_XMP,
|
||||
ExifInterface.TAG_X_RESOLUTION,
|
||||
ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
|
||||
ExifInterface.TAG_Y_CB_CR_POSITIONING,
|
||||
ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
|
||||
ExifInterface.TAG_Y_RESOLUTION,
|
||||
)
|
||||
|
||||
// interpret EXIF code to angle (0, 90, 180 or 270 degrees)
|
||||
@JvmStatic
|
||||
fun getOrientationDegreesForExifCode(exifOrientation: Int): Int = when (exifOrientation) {
|
||||
ExifInterface.ORIENTATION_ROTATE_180 -> 180
|
||||
ExifInterface.ORIENTATION_ROTATE_90 -> 90
|
||||
ExifInterface.ORIENTATION_ROTATE_270 -> 270
|
||||
else -> 0 // all other orientations (regular, flipped...) default to an angle of 0 degree
|
||||
fun getRotationDegreesForExifCode(exifOrientation: Int): Int = when (exifOrientation) {
|
||||
ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSVERSE -> 90
|
||||
ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180
|
||||
ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSPOSE -> 270
|
||||
else -> 0
|
||||
}
|
||||
|
||||
// interpret EXIF code to whether the image is flipped
|
||||
@JvmStatic
|
||||
fun isFlippedForExifCode(exifOrientation: Int): Boolean = when (exifOrientation) {
|
||||
ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_TRANSPOSE -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
// yyyyMMddTHHmmss(.sss)?(Z|+/-hhmm)?
|
||||
|
|
|
@ -89,6 +89,19 @@ class MetadataService {
|
|||
return {};
|
||||
}
|
||||
|
||||
static Future<Map> getExifInterfaceMetadata(ImageEntry entry) async {
|
||||
try {
|
||||
// return map with all data available from the ExifInterface library
|
||||
final result = await platform.invokeMethod('getExifInterfaceMetadata', <String, dynamic>{
|
||||
'uri': entry.uri,
|
||||
}) as Map;
|
||||
return result;
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('getExifInterfaceMetadata failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static Future<List<Uint8List>> getEmbeddedPictures(String uri) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getEmbeddedPictures', <String, dynamic>{
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:aves/widgets/common/icons.dart';
|
|||
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||
import 'package:aves/widgets/settings/settings_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -28,7 +29,7 @@ class _FullscreenDebugPageState extends State<FullscreenDebugPage> {
|
|||
Future<DateMetadata> _dbDateLoader;
|
||||
Future<CatalogMetadata> _dbMetadataLoader;
|
||||
Future<AddressDetails> _dbAddressLoader;
|
||||
Future<Map> _contentResolverMetadataLoader;
|
||||
Future<Map> _contentResolverMetadataLoader, _exifInterfaceMetadataLoader;
|
||||
|
||||
ImageEntry get entry => widget.entry;
|
||||
|
||||
|
@ -259,13 +260,7 @@ class _FullscreenDebugPageState extends State<FullscreenDebugPage> {
|
|||
static const millisecondTimestampKeys = ['datetaken', 'datetime'];
|
||||
|
||||
Widget _buildContentResolverTabView() {
|
||||
return ListView(
|
||||
padding: EdgeInsets.all(16),
|
||||
children: [
|
||||
Text('Content Resolver (Media Store):'),
|
||||
FutureBuilder<Map>(
|
||||
future: _contentResolverMetadataLoader,
|
||||
builder: (context, snapshot) {
|
||||
Widget builder(BuildContext context, AsyncSnapshot<Map> snapshot) {
|
||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
|
||||
final data = SplayTreeMap.of(snapshot.data.map((k, v) {
|
||||
|
@ -283,7 +278,20 @@ class _FullscreenDebugPageState extends State<FullscreenDebugPage> {
|
|||
return MapEntry(key, value);
|
||||
}));
|
||||
return InfoRowGroup(data);
|
||||
},
|
||||
}
|
||||
|
||||
return ListView(
|
||||
padding: EdgeInsets.all(16),
|
||||
children: [
|
||||
SectionTitle('Content Resolver (Media Store)'),
|
||||
FutureBuilder<Map>(
|
||||
future: _contentResolverMetadataLoader,
|
||||
builder: builder,
|
||||
),
|
||||
SectionTitle('Exif Interface'),
|
||||
FutureBuilder<Map>(
|
||||
future: _exifInterfaceMetadataLoader,
|
||||
builder: builder,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -294,6 +302,7 @@ class _FullscreenDebugPageState extends State<FullscreenDebugPage> {
|
|||
_dbMetadataLoader = metadataDb.loadMetadataEntries().then((values) => values.firstWhere((row) => row.contentId == contentId, orElse: () => null));
|
||||
_dbAddressLoader = metadataDb.loadAddresses().then((values) => values.firstWhere((row) => row.contentId == contentId, orElse: () => null));
|
||||
_contentResolverMetadataLoader = MetadataService.getContentResolverMetadata(entry);
|
||||
_exifInterfaceMetadataLoader = MetadataService.getExifInterfaceMetadata(entry);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue