info: parse geotiff keys
This commit is contained in:
parent
036fd40d52
commit
85957348b2
5 changed files with 254 additions and 94 deletions
|
@ -40,12 +40,16 @@ import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeRational
|
|||
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDateMillis
|
||||
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDescription
|
||||
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeInt
|
||||
import deckers.thibault.aves.metadata.Metadata.DIR_DNG
|
||||
import deckers.thibault.aves.metadata.Metadata.DIR_EXIF_GEOTIFF
|
||||
import deckers.thibault.aves.metadata.Metadata.DIR_PNG_TEXTUAL_DATA
|
||||
import deckers.thibault.aves.metadata.Metadata.getRotationDegreesForExifCode
|
||||
import deckers.thibault.aves.metadata.Metadata.isFlippedForExifCode
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_ITXT_DIR_NAME
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_LAST_MODIFICATION_TIME_FORMAT
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_TIME_DIR_NAME
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.containsGeoTiffTags
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.extractGeoKeys
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.extractPngProfile
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getDateDigitizedMillis
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getDateModifiedMillis
|
||||
|
@ -55,7 +59,6 @@ import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeDateMillis
|
|||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeInt
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeRational
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeString
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.isGeoTiff
|
||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.isPngTextDir
|
||||
import deckers.thibault.aves.metadata.XMP.getSafeDateMillis
|
||||
import deckers.thibault.aves.metadata.XMP.getSafeInt
|
||||
|
@ -83,6 +86,7 @@ import kotlinx.coroutines.SupervisorJob
|
|||
import kotlinx.coroutines.launch
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.DecimalFormat
|
||||
import java.text.ParseException
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.roundToLong
|
||||
|
@ -95,6 +99,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
"getAllMetadata" -> ioScope.launch { safe(call, result, ::getAllMetadata) }
|
||||
"getCatalogMetadata" -> ioScope.launch { safe(call, result, ::getCatalogMetadata) }
|
||||
"getOverlayMetadata" -> ioScope.launch { safe(call, result, ::getOverlayMetadata) }
|
||||
"getGeoTiffInfo" -> ioScope.launch { safe(call, result, ::getGeoTiffInfo) }
|
||||
"getMultiPageInfo" -> ioScope.launch { safe(call, result, ::getMultiPageInfo) }
|
||||
"getPanoramaInfo" -> ioScope.launch { safe(call, result, ::getPanoramaInfo) }
|
||||
"getIptc" -> ioScope.launch { safe(call, result, ::getIptc) }
|
||||
|
@ -168,76 +173,95 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
|
||||
// tags
|
||||
val tags = dir.tags
|
||||
if (dir is ExifDirectoryBase) {
|
||||
when {
|
||||
dir.isGeoTiff() -> {
|
||||
// split GeoTIFF tags in their own directory
|
||||
val geoTiffDirMap = metadataMap["GeoTIFF"] ?: HashMap()
|
||||
metadataMap["GeoTIFF"] = geoTiffDirMap
|
||||
val byGeoTiff = tags.groupBy { ExifTags.isGeoTiffTag(it.tagType) }
|
||||
byGeoTiff[true]?.map { exifTagMapper(it) }?.let { geoTiffDirMap.putAll(it) }
|
||||
byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
|
||||
}
|
||||
mimeType == MimeTypes.DNG -> {
|
||||
// split DNG tags in their own directory
|
||||
val dngDirMap = metadataMap["DNG"] ?: HashMap()
|
||||
metadataMap["DNG"] = dngDirMap
|
||||
val byDng = tags.groupBy { ExifTags.isDngTag(it.tagType) }
|
||||
byDng[true]?.map { exifTagMapper(it) }?.let { dngDirMap.putAll(it) }
|
||||
byDng[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
|
||||
}
|
||||
else -> dirMap.putAll(tags.map { exifTagMapper(it) })
|
||||
}
|
||||
} else if (dir.isPngTextDir()) {
|
||||
metadataMap.remove(thisDirName)
|
||||
dirMap = metadataMap[DIR_PNG_TEXTUAL_DATA] ?: HashMap()
|
||||
metadataMap[DIR_PNG_TEXTUAL_DATA] = dirMap
|
||||
|
||||
for (tag in tags) {
|
||||
val tagType = tag.tagType
|
||||
if (tagType == PngDirectory.TAG_TEXTUAL_DATA) {
|
||||
val pairs = dir.getObject(tagType) as List<*>
|
||||
val textPairs = pairs.map { pair ->
|
||||
val kv = pair as KeyValuePair
|
||||
val key = kv.key
|
||||
// `PNG-iTXt` uses UTF-8, contrary to `PNG-tEXt` and `PNG-zTXt` using Latin-1 / ISO-8859-1
|
||||
val charset = if (baseDirName == PNG_ITXT_DIR_NAME) {
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
StandardCharsets.UTF_8
|
||||
} else {
|
||||
Charset.forName("UTF-8")
|
||||
}
|
||||
} else {
|
||||
kv.value.charset
|
||||
}
|
||||
val valueString = String(kv.value.bytes, charset)
|
||||
val dirs = extractPngProfile(key, valueString)
|
||||
if (dirs?.any() == true) {
|
||||
dirs.forEach { profileDir ->
|
||||
val profileDirName = "${dir.name}/${profileDir.name}"
|
||||
val profileDirMap = metadataMap[profileDirName] ?: HashMap()
|
||||
metadataMap[profileDirName] = profileDirMap
|
||||
val profileTags = profileDir.tags
|
||||
if (profileDir is ExifDirectoryBase) {
|
||||
profileDirMap.putAll(profileTags.map { exifTagMapper(it) })
|
||||
} else {
|
||||
profileDirMap.putAll(profileTags.map { Pair(it.tagName, it.description) })
|
||||
when {
|
||||
dir is ExifDirectoryBase -> {
|
||||
when {
|
||||
dir.containsGeoTiffTags() -> {
|
||||
// split GeoTIFF tags in their own directory
|
||||
val geoTiffDirMap = metadataMap[DIR_EXIF_GEOTIFF] ?: HashMap()
|
||||
metadataMap[DIR_EXIF_GEOTIFF] = geoTiffDirMap
|
||||
val byGeoTiff = tags.groupBy { ExifTags.isGeoTiffTag(it.tagType) }
|
||||
byGeoTiff[true]?.flatMap { tag ->
|
||||
when (tag.tagType) {
|
||||
ExifGeoTiffTags.TAG_GEO_KEY_DIRECTORY -> {
|
||||
val geoTiffTags = (dir as ExifIFD0Directory).extractGeoKeys(dir.getIntArray(tag.tagType))
|
||||
geoTiffTags.map { geoTag ->
|
||||
val name = GeoTiffKeys.getTagName(geoTag.key) ?: "0x${geoTag.key.toString(16)}"
|
||||
val value = geoTag.value
|
||||
val description = if (value is DoubleArray) value.joinToString(" ") { doubleFormat.format(it) } else "$value"
|
||||
Pair(name, description)
|
||||
}
|
||||
}
|
||||
// skip `Geo double/ascii params`, as their content is split and presented through various GeoTIFF keys
|
||||
ExifGeoTiffTags.TAG_GEO_DOUBLE_PARAMS,
|
||||
ExifGeoTiffTags.TAG_GEO_ASCII_PARAMS -> ArrayList()
|
||||
else -> listOf(exifTagMapper(tag))
|
||||
}
|
||||
null
|
||||
} else {
|
||||
Pair(key, valueString)
|
||||
}
|
||||
}?.let { geoTiffDirMap.putAll(it) }
|
||||
byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
|
||||
}
|
||||
dirMap.putAll(textPairs.filterNotNull())
|
||||
} else {
|
||||
dirMap[tag.tagName] = tag.description
|
||||
mimeType == MimeTypes.DNG -> {
|
||||
// split DNG tags in their own directory
|
||||
val dngDirMap = metadataMap[DIR_DNG] ?: HashMap()
|
||||
metadataMap[DIR_DNG] = dngDirMap
|
||||
val byDng = tags.groupBy { ExifTags.isDngTag(it.tagType) }
|
||||
byDng[true]?.map { exifTagMapper(it) }?.let { dngDirMap.putAll(it) }
|
||||
byDng[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
|
||||
}
|
||||
else -> dirMap.putAll(tags.map { exifTagMapper(it) })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dirMap.putAll(tags.map { Pair(it.tagName, it.description) })
|
||||
dir.isPngTextDir() -> {
|
||||
metadataMap.remove(thisDirName)
|
||||
dirMap = metadataMap[DIR_PNG_TEXTUAL_DATA] ?: HashMap()
|
||||
metadataMap[DIR_PNG_TEXTUAL_DATA] = dirMap
|
||||
|
||||
for (tag in tags) {
|
||||
val tagType = tag.tagType
|
||||
if (tagType == PngDirectory.TAG_TEXTUAL_DATA) {
|
||||
val pairs = dir.getObject(tagType) as List<*>
|
||||
val textPairs = pairs.map { pair ->
|
||||
val kv = pair as KeyValuePair
|
||||
val key = kv.key
|
||||
// `PNG-iTXt` uses UTF-8, contrary to `PNG-tEXt` and `PNG-zTXt` using Latin-1 / ISO-8859-1
|
||||
val charset = if (baseDirName == PNG_ITXT_DIR_NAME) {
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
StandardCharsets.UTF_8
|
||||
} else {
|
||||
Charset.forName("UTF-8")
|
||||
}
|
||||
} else {
|
||||
kv.value.charset
|
||||
}
|
||||
val valueString = String(kv.value.bytes, charset)
|
||||
val dirs = extractPngProfile(key, valueString)
|
||||
if (dirs?.any() == true) {
|
||||
dirs.forEach { profileDir ->
|
||||
val profileDirName = "${dir.name}/${profileDir.name}"
|
||||
val profileDirMap = metadataMap[profileDirName] ?: HashMap()
|
||||
metadataMap[profileDirName] = profileDirMap
|
||||
val profileTags = profileDir.tags
|
||||
if (profileDir is ExifDirectoryBase) {
|
||||
profileDirMap.putAll(profileTags.map { exifTagMapper(it) })
|
||||
} else {
|
||||
profileDirMap.putAll(profileTags.map { Pair(it.tagName, it.description) })
|
||||
}
|
||||
}
|
||||
null
|
||||
} else {
|
||||
Pair(key, valueString)
|
||||
}
|
||||
}
|
||||
dirMap.putAll(textPairs.filterNotNull())
|
||||
} else {
|
||||
dirMap[tag.tagName] = tag.description
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> dirMap.putAll(tags.map { Pair(it.tagName, it.description) })
|
||||
}
|
||||
|
||||
if (dir is XmpDirectory) {
|
||||
try {
|
||||
for (prop in dir.xmpMeta) {
|
||||
|
@ -300,9 +324,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -557,7 +581,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
MimeTypes.TIFF -> {
|
||||
// identification of GeoTIFF
|
||||
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
|
||||
if (dir.isGeoTiff()) flags = flags or MASK_IS_GEOTIFF
|
||||
if (dir.containsGeoTiffTags()) flags = flags or MASK_IS_GEOTIFF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -570,9 +594,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get catalog metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
Log.w(LOG_TAG, "failed to get catalog metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -696,9 +720,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -722,6 +746,43 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
result.success(metadataMap)
|
||||
}
|
||||
|
||||
private fun getGeoTiffInfo(call: MethodCall, result: MethodChannel.Result) {
|
||||
val mimeType = call.argument<String>("mimeType")
|
||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
||||
if (mimeType == null || uri == null) {
|
||||
result.error("getGeoTiffInfo-args", "failed because of missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
||||
if (canReadWithMetadataExtractor(mimeType)) {
|
||||
try {
|
||||
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
|
||||
val metadata = ImageMetadataReader.readMetadata(input)
|
||||
val fields = HashMap<Int, Any?>()
|
||||
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
|
||||
if (dir.containsGeoTiffTags()) {
|
||||
fields.putAll(dir.tags.map { it.tagType }.filter { ExifTags.isGeoTiffTag(it) }.map {
|
||||
val value = when (it) {
|
||||
ExifGeoTiffTags.TAG_GEO_ASCII_PARAMS -> dir.getString(it)
|
||||
else -> dir.getObject(it)
|
||||
}
|
||||
Pair(it, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
result.success(fields)
|
||||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
}
|
||||
}
|
||||
result.error("getGeoTiffInfo-empty", "failed to get info for mimeType=$mimeType uri=$uri", null)
|
||||
}
|
||||
|
||||
private fun getMultiPageInfo(call: MethodCall, result: MethodChannel.Result) {
|
||||
val mimeType = call.argument<String>("mimeType")
|
||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||
|
@ -774,12 +835,12 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to read XMP", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
Log.w(LOG_TAG, "failed to read XMP", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
}
|
||||
}
|
||||
result.error("getPanoramaInfo-empty", "failed to read XMP for mimeType=$mimeType uri=$uri", null)
|
||||
result.error("getPanoramaInfo-empty", "failed to get info for mimeType=$mimeType uri=$uri", null)
|
||||
}
|
||||
|
||||
private fun getIptc(call: MethodCall, result: MethodChannel.Result) {
|
||||
|
@ -961,9 +1022,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -974,6 +1035,8 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
private val LOG_TAG = LogUtils.createTag<MetadataFetchHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/metadata_fetch"
|
||||
|
||||
private val doubleFormat = DecimalFormat("0.###")
|
||||
|
||||
private val allMetadataRedundantDirNames = setOf(
|
||||
"MP4",
|
||||
"MP4 Metadata",
|
||||
|
|
|
@ -44,12 +44,12 @@ object ExifTags {
|
|||
TAG_GDAL_NO_DATA to "GDAL No Data",
|
||||
).apply {
|
||||
putAll(DngTags.tagNameMap)
|
||||
putAll(GeoTiffTags.tagNameMap)
|
||||
putAll(ExifGeoTiffTags.tagNameMap)
|
||||
}
|
||||
|
||||
fun isDngTag(tag: Int) = DngTags.tags.contains(tag)
|
||||
|
||||
fun isGeoTiffTag(tag: Int) = GeoTiffTags.tags.contains(tag)
|
||||
fun isGeoTiffTag(tag: Int) = ExifGeoTiffTags.tags.contains(tag)
|
||||
|
||||
fun getTagName(tag: Int): String? {
|
||||
return tagNameMap[tag]
|
||||
|
|
|
@ -1,17 +1,61 @@
|
|||
package deckers.thibault.aves.metadata
|
||||
|
||||
object GeoTiffTags {
|
||||
object GeoTiffKeys {
|
||||
// not a standard tag
|
||||
const val GEOTIFF_VERSION = 0
|
||||
|
||||
private const val MODEL_TYPE = 0x0400
|
||||
private const val RASTER_TYPE = 0x0401
|
||||
private const val CITATION = 0x0402
|
||||
private const val GEOG_TYPE = 0x0800
|
||||
private const val GEOG_CITATION = 0x0801
|
||||
private const val GEOG_ANGULAR_UNITS = 0x0806
|
||||
private const val PROJ_CS_TYPE = 0x0c00
|
||||
private const val PROJ_CS_CITATION = 0x0c01
|
||||
private const val PROJECTION = 0x0c02
|
||||
private const val PROJ_COORD_TRANS = 0x0c03
|
||||
private const val PROJ_LINEAR_UNITS = 0x0c04
|
||||
private const val PROJ_STD_PARALLEL_1 = 0x0c06
|
||||
private const val PROJ_NAT_ORIGIN_LONG = 0x0c08
|
||||
private const val PROJ_FALSE_EASTING = 0x0c0a
|
||||
private const val PROJ_FALSE_NORTHING = 0x0c0b
|
||||
|
||||
private val tagNameMap = hashMapOf(
|
||||
GEOTIFF_VERSION to "GeoTIFF Version",
|
||||
MODEL_TYPE to "Model Type",
|
||||
RASTER_TYPE to "Raster Type",
|
||||
CITATION to "Citation",
|
||||
GEOG_TYPE to "Geographic Type",
|
||||
GEOG_CITATION to "Geographic Citation",
|
||||
GEOG_ANGULAR_UNITS to "Geographic Angular Units",
|
||||
PROJ_CS_TYPE to "Projected Coordinate System Type",
|
||||
PROJ_CS_CITATION to "Projected Coordinate System Citation",
|
||||
PROJECTION to "Projection",
|
||||
PROJ_COORD_TRANS to "Projected Coordinate Transform",
|
||||
PROJ_LINEAR_UNITS to "Projection Linear Units",
|
||||
PROJ_STD_PARALLEL_1 to "Projection Standard Parallel 1",
|
||||
PROJ_NAT_ORIGIN_LONG to "Projection Natural Origin Longitude",
|
||||
PROJ_FALSE_EASTING to "Projection False Easting",
|
||||
PROJ_FALSE_NORTHING to "Projection False Northing",
|
||||
)
|
||||
|
||||
fun getTagName(tag: Int): String? {
|
||||
return tagNameMap[tag]
|
||||
}
|
||||
}
|
||||
|
||||
object ExifGeoTiffTags {
|
||||
// ModelPixelScaleTag (optional)
|
||||
// Tag = 33550 (830E.H)
|
||||
// Type = DOUBLE
|
||||
// Count = 3
|
||||
const val TAG_MODEL_PIXEL_SCALE = 0x830e
|
||||
|
||||
// ModelTiepointTag (conditional)
|
||||
// ModelTiePointTag (conditional)
|
||||
// Tag = 33922 (8482.H)
|
||||
// Type = DOUBLE
|
||||
// Count = 6*K, K = number of tiepoints
|
||||
const val TAG_MODEL_TIEPOINT = 0x8482
|
||||
// Count = 6*K, K = number of tie points
|
||||
const val TAG_MODEL_TIE_POINT = 0x8482
|
||||
|
||||
// ModelTransformationTag (conditional)
|
||||
// Tag = 34264 (85D8.H)
|
||||
|
@ -29,22 +73,22 @@ object GeoTiffTags {
|
|||
// Tag = 34736 (87BO.H)
|
||||
// Type = DOUBLE
|
||||
// Count = variable
|
||||
private const val TAG_GEO_DOUBLE_PARAMS = 0x87b0
|
||||
const val TAG_GEO_DOUBLE_PARAMS = 0x87b0
|
||||
|
||||
// GeoAsciiParamsTag (optional)
|
||||
// Tag = 34737 (87B1.H)
|
||||
// Type = ASCII
|
||||
// Count = variable
|
||||
private const val TAG_GEO_ASCII_PARAMS = 0x87b1
|
||||
const val TAG_GEO_ASCII_PARAMS = 0x87b1
|
||||
|
||||
val tagNameMap = hashMapOf(
|
||||
TAG_GEO_ASCII_PARAMS to "Geo Ascii Params",
|
||||
TAG_GEO_DOUBLE_PARAMS to "Geo Double Params",
|
||||
TAG_GEO_KEY_DIRECTORY to "Geo Key Directory",
|
||||
TAG_MODEL_PIXEL_SCALE to "Model Pixel Scale",
|
||||
TAG_MODEL_TIEPOINT to "Model Tiepoint",
|
||||
TAG_MODEL_TIE_POINT to "Model Tie Points",
|
||||
TAG_MODEL_TRANSFORMATION to "Model Transformation",
|
||||
)
|
||||
|
||||
val tags = tagNameMap.keys
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ object Metadata {
|
|||
const val DIR_XMP = "XMP" // from metadata-extractor
|
||||
const val DIR_MEDIA = "Media" // custom
|
||||
const val DIR_COVER_ART = "Cover" // custom
|
||||
const val DIR_DNG = "DNG" // custom
|
||||
const val DIR_EXIF_GEOTIFF = "GeoTIFF" // custom
|
||||
const val DIR_PNG_TEXTUAL_DATA = "PNG Textual Data" // custom
|
||||
|
||||
// types of metadata
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.drew.lang.ByteArrayReader
|
|||
import com.drew.lang.Rational
|
||||
import com.drew.lang.SequentialByteArrayReader
|
||||
import com.drew.metadata.Directory
|
||||
import com.drew.metadata.StringValue
|
||||
import com.drew.metadata.exif.ExifDirectoryBase
|
||||
import com.drew.metadata.exif.ExifIFD0Directory
|
||||
import com.drew.metadata.exif.ExifReader
|
||||
|
@ -93,19 +94,69 @@ object MetadataExtractorHelper {
|
|||
- If the ModelTransformationTag is included in an IFD, then a ModelPixelScaleTag SHALL NOT be included
|
||||
- If the ModelPixelScaleTag is included in an IFD, then a ModelTiepointTag SHALL also be included.
|
||||
*/
|
||||
fun ExifDirectoryBase.isGeoTiff(): Boolean {
|
||||
if (!this.containsTag(GeoTiffTags.TAG_GEO_KEY_DIRECTORY)) return false
|
||||
fun ExifDirectoryBase.containsGeoTiffTags(): Boolean {
|
||||
if (!this.containsTag(ExifGeoTiffTags.TAG_GEO_KEY_DIRECTORY)) return false
|
||||
|
||||
val modelTiepoint = this.containsTag(GeoTiffTags.TAG_MODEL_TIEPOINT)
|
||||
val modelTransformation = this.containsTag(GeoTiffTags.TAG_MODEL_TRANSFORMATION)
|
||||
if (!modelTiepoint && !modelTransformation) return false
|
||||
val modelTiePoints = this.containsTag(ExifGeoTiffTags.TAG_MODEL_TIE_POINT)
|
||||
val modelTransformation = this.containsTag(ExifGeoTiffTags.TAG_MODEL_TRANSFORMATION)
|
||||
if (!modelTiePoints && !modelTransformation) return false
|
||||
|
||||
val modelPixelScale = this.containsTag(GeoTiffTags.TAG_MODEL_PIXEL_SCALE)
|
||||
if ((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiepoint)) return false
|
||||
val modelPixelScale = this.containsTag(ExifGeoTiffTags.TAG_MODEL_PIXEL_SCALE)
|
||||
if ((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiePoints)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO TLAD use `GeoTiffDirectory` from the Java version of `metadata-extractor` when available
|
||||
// adapted from https://github.com/drewnoakes/metadata-extractor-dotnet/blob/master/MetadataExtractor/Formats/Exif/ExifTiffHandler.cs
|
||||
fun ExifIFD0Directory.extractGeoKeys(geoKeys: IntArray): HashMap<Int, Any?> {
|
||||
val fields = HashMap<Int, Any?>()
|
||||
if (geoKeys.size < 4) return fields
|
||||
|
||||
var i = 0
|
||||
val directoryVersion = geoKeys[i++]
|
||||
val revision = geoKeys[i++]
|
||||
val minorRevision = geoKeys[i++]
|
||||
val numberOfKeys = geoKeys[i++]
|
||||
|
||||
fields[GeoTiffKeys.GEOTIFF_VERSION] = "$directoryVersion.$revision.$minorRevision"
|
||||
|
||||
for (j in 0 until numberOfKeys) {
|
||||
val keyId = geoKeys[i++]
|
||||
val tiffTagLocation = geoKeys[i++]
|
||||
val valueCount = geoKeys[i++]
|
||||
val valueOffset = geoKeys[i++]
|
||||
|
||||
try {
|
||||
if (tiffTagLocation == 0) {
|
||||
fields[keyId] = valueOffset
|
||||
} else {
|
||||
val sourceValue = getObject(tiffTagLocation)
|
||||
if (sourceValue is StringValue) {
|
||||
if (valueOffset + valueCount <= sourceValue.bytes.size) {
|
||||
fields[keyId] = String(sourceValue.bytes, valueOffset, valueCount).trimEnd('|')
|
||||
} else {
|
||||
Log.w(LOG_TAG, "GeoTIFF key $keyId with offset $valueOffset and count $valueCount extends beyond length of source value (${sourceValue.bytes.size})")
|
||||
}
|
||||
} else if (sourceValue.javaClass.isArray) {
|
||||
val sourceArray = sourceValue as DoubleArray
|
||||
if (valueOffset + valueCount < sourceArray.size) {
|
||||
fields[keyId] = sourceArray.copyOfRange(valueOffset, valueOffset + valueCount)
|
||||
} else {
|
||||
Log.w(LOG_TAG, "GeoTIFF key $keyId with offset $valueOffset and count $valueCount extends beyond length of source value (${sourceArray.size})")
|
||||
}
|
||||
} else {
|
||||
Log.w(LOG_TAG, "GeoTIFF key $keyId references tag $tiffTagLocation which has unsupported type of ${sourceValue?.javaClass}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "failed to extract GeoTiff fields from keys", e)
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// PNG
|
||||
|
||||
fun Directory.isPngTextDir(): Boolean = this is PngDirectory && setOf(PNG_ITXT_DIR_NAME, PNG_TEXT_DIR_NAME, PNG_ZTXT_DIR_NAME).contains(this.name)
|
||||
|
|
Loading…
Reference in a new issue