info: fixed PNG exif/iptc raw profile extraction
This commit is contained in:
parent
30d875f1cf
commit
acdb7afa6d
2 changed files with 42 additions and 23 deletions
|
@ -207,7 +207,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
|||
val dirs = extractPngProfile(key, valueString)
|
||||
if (dirs?.any() == true) {
|
||||
dirs.forEach { profileDir ->
|
||||
val profileDirName = profileDir.name
|
||||
val profileDirName = "${dir.name}/${profileDir.name}"
|
||||
val profileDirMap = metadataMap[profileDirName] ?: HashMap()
|
||||
metadataMap[profileDirName] = profileDirMap
|
||||
profileDirMap.putAll(profileDir.tags.map { Pair(it.tagName, it.description) })
|
||||
|
|
|
@ -1,25 +1,34 @@
|
|||
package deckers.thibault.aves.metadata
|
||||
|
||||
import android.util.Log
|
||||
import com.drew.lang.ByteArrayReader
|
||||
import com.drew.lang.Rational
|
||||
import com.drew.lang.SequentialByteArrayReader
|
||||
import com.drew.metadata.Directory
|
||||
import com.drew.metadata.exif.ExifIFD0Directory
|
||||
import com.drew.metadata.exif.ExifReader
|
||||
import com.drew.metadata.iptc.IptcReader
|
||||
import com.drew.metadata.png.PngDirectory
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
object MetadataExtractorHelper {
|
||||
private val LOG_TAG = LogUtils.createTag<MetadataExtractorHelper>()
|
||||
|
||||
const val PNG_ITXT_DIR_NAME = "PNG-iTXt"
|
||||
private const val PNG_TEXT_DIR_NAME = "PNG-tEXt"
|
||||
const val PNG_TIME_DIR_NAME = "PNG-tIME"
|
||||
private const val PNG_ZTXT_DIR_NAME = "PNG-zTXt"
|
||||
private const val PNG_RAW_PROFILE_EXIF = "Raw profile type exif"
|
||||
private const val PNG_RAW_PROFILE_IPTC = "Raw profile type iptc"
|
||||
|
||||
val PNG_LAST_MODIFICATION_TIME_FORMAT = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.ROOT)
|
||||
|
||||
// Pattern to extract profile name, length, and text data
|
||||
// of raw profiles (EXIF, IPTC, etc.) in PNG `zTXt` chunks
|
||||
// e.g. "iptc [...] 114 [...] 3842494d040400[...]"
|
||||
// e.g. "exif [...] 134 [...] 4578696600004949[...]"
|
||||
private val PNG_RAW_PROFILE_PATTERN = Regex("^\\n(.*?)\\n\\s*(\\d+)\\n(.*)", RegexOption.DOT_MATCHES_ALL)
|
||||
|
||||
// extensions
|
||||
|
@ -77,22 +86,29 @@ object MetadataExtractorHelper {
|
|||
fun Directory.isPngTextDir(): Boolean = this is PngDirectory && setOf(PNG_ITXT_DIR_NAME, PNG_TEXT_DIR_NAME, PNG_ZTXT_DIR_NAME).contains(this.name)
|
||||
|
||||
fun extractPngProfile(key: String, valueString: String): Iterable<Directory>? {
|
||||
when (key) {
|
||||
"Raw profile type iptc" -> {
|
||||
val match = PNG_RAW_PROFILE_PATTERN.matchEntire(valueString)
|
||||
if (match != null) {
|
||||
val dataString = match.groupValues[3]
|
||||
val hexString = dataString.replace(Regex("[\\r\\n]"), "")
|
||||
val dataBytes = hexStringToByteArray(hexString)
|
||||
if (dataBytes != null) {
|
||||
val start = dataBytes.indexOf(Metadata.IPTC_MARKER_BYTE)
|
||||
if (start != -1) {
|
||||
val segmentBytes = dataBytes.copyOfRange(fromIndex = start, toIndex = dataBytes.size)
|
||||
val metadata = com.drew.metadata.Metadata()
|
||||
IptcReader().extract(SequentialByteArrayReader(segmentBytes), metadata, segmentBytes.size.toLong())
|
||||
return metadata.directories
|
||||
if (key == PNG_RAW_PROFILE_EXIF || key == PNG_RAW_PROFILE_IPTC) {
|
||||
val match = PNG_RAW_PROFILE_PATTERN.matchEntire(valueString)
|
||||
if (match != null) {
|
||||
val dataString = match.groupValues[3]
|
||||
val hexString = dataString.replace(Regex("[\\r\\n]"), "")
|
||||
val dataBytes = hexString.decodeHex()
|
||||
if (dataBytes != null) {
|
||||
val metadata = com.drew.metadata.Metadata()
|
||||
when (key) {
|
||||
PNG_RAW_PROFILE_EXIF -> {
|
||||
if (ExifReader.startsWithJpegExifPreamble(dataBytes)) {
|
||||
ExifReader().extract(ByteArrayReader(dataBytes), metadata, ExifReader.JPEG_SEGMENT_PREAMBLE.length)
|
||||
}
|
||||
}
|
||||
PNG_RAW_PROFILE_IPTC -> {
|
||||
val start = dataBytes.indexOf(Metadata.IPTC_MARKER_BYTE)
|
||||
if (start != -1) {
|
||||
val segmentBytes = dataBytes.copyOfRange(fromIndex = start, toIndex = dataBytes.size)
|
||||
IptcReader().extract(SequentialByteArrayReader(segmentBytes), metadata, segmentBytes.size.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
return metadata.directories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,15 +117,18 @@ object MetadataExtractorHelper {
|
|||
|
||||
// convenience methods
|
||||
|
||||
private fun hexStringToByteArray(hexString: String): ByteArray? {
|
||||
if (hexString.length % 2 != 0) return null
|
||||
private fun String.decodeHex(): ByteArray? {
|
||||
if (length % 2 != 0) return null
|
||||
|
||||
val dataBytes = ByteArray(hexString.length / 2)
|
||||
var i = 0
|
||||
while (i < hexString.length) {
|
||||
dataBytes[i / 2] = hexString.substring(i, i + 2).toByte(16)
|
||||
i += 2
|
||||
try {
|
||||
val byteIterator = chunkedSequence(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.iterator()
|
||||
|
||||
return ByteArray(length / 2) { byteIterator.next() }
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.w(LOG_TAG, "failed to decode hex string=$this", e)
|
||||
}
|
||||
return dataBytes
|
||||
return null
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue