Skip to content

Commit

Permalink
IptcWriter: Followed recommendations to always write UTF8 IPTC. (#8)
Browse files Browse the repository at this point in the history
There are some discussion about this topic and it looks like that it's
at least recommended to include the charset flag as otherwise readers
have to guess the right charset. Writing always UTF-8 per default should
be a good choice.
  • Loading branch information
StefanOltmann authored Jun 26, 2023
1 parent 2733041 commit ec406a2
Show file tree
Hide file tree
Showing 51 changed files with 23 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,5 @@ object IptcConstants {
const val IPTC_RECORD_TAG_MARKER = 0x1c
const val IPTC_ENVELOPE_RECORD_NUMBER = 0x01
const val IPTC_APPLICATION_2_RECORD_NUMBER = 0x02
const val IPTC_RECORD_VERSION_VALUE = 4
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@ package com.ashampoo.kim.format.jpeg.iptc
import com.ashampoo.kim.common.BinaryFileParser
import com.ashampoo.kim.common.ByteOrder
import com.ashampoo.kim.common.ImageReadException
import com.ashampoo.kim.common.quadsToByteArray
import com.ashampoo.kim.common.slice
import com.ashampoo.kim.common.startsWith
import com.ashampoo.kim.common.toHex
import com.ashampoo.kim.common.toInt
import com.ashampoo.kim.common.toSingleNumberHexes
import com.ashampoo.kim.common.toUInt16
import com.ashampoo.kim.common.toUInt8
import com.ashampoo.kim.format.jpeg.JpegConstants
Expand All @@ -49,8 +46,9 @@ object IptcParser : BinaryFileParser() {

val DEFAULT_CHARSET = Charsets.ISO_8859_1

const val ENV_TAG_CODED_CHARACTER_SET = 90
const val CODED_CHARACTER_SET_IPTC_CODE = 90

/* "ESC % G" as bytes */
val UTF8_CHARACTER_ESCAPE_SEQUENCE =
byteArrayOf('\u001B'.code.toByte(), '%'.code.toByte(), 'G'.code.toByte())

Expand Down Expand Up @@ -123,7 +121,7 @@ object IptcParser : BinaryFileParser() {
index += recordSize

if (recordNumber == IptcConstants.IPTC_ENVELOPE_RECORD_NUMBER &&
recordType == ENV_TAG_CODED_CHARACTER_SET
recordType == CODED_CHARACTER_SET_IPTC_CODE
) {
charset = findCharset(recordData)
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ import com.ashampoo.kim.format.jpeg.iptc.IptcParser.APP13_BYTE_ORDER
import com.ashampoo.kim.output.BigEndianBinaryByteWriter
import com.ashampoo.kim.output.BinaryByteWriter
import com.ashampoo.kim.output.ByteArrayByteWriter
import io.ktor.utils.io.charsets.Charsets
import io.ktor.utils.io.core.String
import io.ktor.utils.io.core.toByteArray

object IptcWriter {

@Suppress("ThrowsCount")
fun writePhotoshopApp13Segment(data: IptcMetadata): ByteArray {

val os = ByteArrayByteWriter()
Expand Down Expand Up @@ -76,53 +75,37 @@ object IptcWriter {
return os.toByteArray()
}

/* Writes the IPTC block in UTF-8 */
fun writeIPTCBlock(records: List<IptcRecord>): ByteArray {

var charset = IptcParser.DEFAULT_CHARSET

/*
* Check if the default ISO charset will work.
* Otherwise we need to switch to UTF8.
*/
for (record in records) {

val recordData = record.value.toByteArray(charset)

val reEncodedString = String(recordData, charset = charset)

if (reEncodedString != record.value) {
charset = Charsets.UTF_8
break
}
}

val byteWriter = ByteArrayByteWriter()

val binaryWriter = BinaryByteWriter.createBinaryByteWriter(byteWriter, APP13_BYTE_ORDER)

if (charset != IptcParser.DEFAULT_CHARSET) {

binaryWriter.write(IptcConstants.IPTC_RECORD_TAG_MARKER)
binaryWriter.write(IptcConstants.IPTC_ENVELOPE_RECORD_NUMBER)
binaryWriter.write(IptcParser.ENV_TAG_CODED_CHARACTER_SET)
binaryWriter.write2Bytes(IptcParser.UTF8_CHARACTER_ESCAPE_SEQUENCE.size)
binaryWriter.write(IptcParser.UTF8_CHARACTER_ESCAPE_SEQUENCE)
}
/*
* Set the envelope for UTF-8
*
* It's recommended to use UTF-8 as a default to prevent issues with umlauts.
* And it's more consistent to always write data the same way.
*/
binaryWriter.write(IptcConstants.IPTC_RECORD_TAG_MARKER)
binaryWriter.write(IptcConstants.IPTC_ENVELOPE_RECORD_NUMBER)
binaryWriter.write(IptcParser.CODED_CHARACTER_SET_IPTC_CODE)
binaryWriter.write2Bytes(IptcParser.UTF8_CHARACTER_ESCAPE_SEQUENCE.size)
binaryWriter.write(IptcParser.UTF8_CHARACTER_ESCAPE_SEQUENCE)

/* First, right record version record */
binaryWriter.write(IptcConstants.IPTC_RECORD_TAG_MARKER)
binaryWriter.write(IptcConstants.IPTC_APPLICATION_2_RECORD_NUMBER)
binaryWriter.write(IptcTypes.RECORD_VERSION.type)
binaryWriter.write2Bytes(2) // record version record size
binaryWriter.write2Bytes(2) // record version value
binaryWriter.write2Bytes(IptcConstants.IPTC_RECORD_VERSION_VALUE)

// make a copy of the list.
val sortedRecords: List<IptcRecord> = records.sortedWith(IptcRecord.comparator)

// write the list.
for ((iptcType, value) in sortedRecords) {

/* Ignore the record version */
/* Ignore the record version, because we already wrote it. */
if (iptcType === IptcTypes.RECORD_VERSION)
continue

Expand All @@ -134,7 +117,8 @@ object IptcWriter {

binaryWriter.write(iptcType.type)

val recordData = value.toByteArray(charset)
val recordData = value.toByteArray()

binaryWriter.write2Bytes(recordData.size)
binaryWriter.write(recordData)
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ class JpegRewriterTest {
val newRecords = mutableListOf<IptcRecord>()
newRecords.addAll(oldRecords)

newRecords.add(IptcRecord(IptcTypes.KEYWORDS, "new_keyword"))
/* Write a new keyword including umlauts */
newRecords.add(IptcRecord(IptcTypes.KEYWORDS, "Umlauts: äöüß"))

val newPhotoshopData = IptcMetadata(
newRecords,
Expand Down

0 comments on commit ec406a2

Please sign in to comment.