diff --git a/README.md b/README.md index 442316b9..29e0b4f4 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ of Ashampoo Photos, which, in turn, is driven by user community feedback. ## Installation ``` -implementation("com.ashampoo:kim:0.9.1") +implementation("com.ashampoo:kim:0.9.2") ``` ## Sample usages @@ -142,17 +142,14 @@ val newBytes = Kim.updateThumbnail( * Inability to update EXIF, IPTC and XMP in JPG files simultaneously. * HEIC files are only tested for iPhone SE 3 & Samsung Galaxy S21. -## Regarding HEIC +### Regarding missing HEIC metadata -When handling ISO base media file format (ISOBMFF) files like HEIC, -our adherence to the EIC/ISO 14496-12 specification. - -For example we intentionally omit certain features specified in the HEIC -standard, such as image size ("ispe") and rotation ("irot"), to steer clear -of potential legal complications. - -In future updates, we plan to integrate special values from other -patent-free ISOBMFF-based formats as we broaden support for additional file types. +In the processing of HEIC files, we handle them as standard ISO base +media file format (ISOBMFF) files, adhering rigorously to the +EIC/ISO 14496-12 specification. To preempt potential legal issues, +we intentionally omit certain features outlined in the HEIC specification, +notably the image size ("ispe") and image rotation ("irot") boxes. +This precautionary approach extends to AVIF images, as they repurpose these same boxes. ## Contributions diff --git a/build.gradle.kts b/build.gradle.kts index 1f1b3ea0..994fd55e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ repositories { val productName = "Ashampoo Kim" val ktorVersion: String = "2.3.7" -val xmpCoreVersion: String = "0.3" +val xmpCoreVersion: String = "1.0.0" val dateTimeVersion: String = "0.5.0" val testRessourcesVersion: String = "0.4.0" val ioCoreVersion: String = "0.3.0" diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/ImageFormatMagicNumbers.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/ImageFormatMagicNumbers.kt index 9f9457b0..244d93c9 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/ImageFormatMagicNumbers.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/ImageFormatMagicNumbers.kt @@ -110,6 +110,14 @@ object ImageFormatMagicNumbers { null, null, null, null ).plus("ftyphevx".encodeToByteArray().toList()) + val jxlCodeStream: List = byteListOf( + 0xFF, 0x0A + ) + + val jxlContainer: List = byteListOf( + 0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A + ) + private fun byteListOf(vararg ints: Int?): List = ints.map { it?.toByte() } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/ImageParser.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/ImageParser.kt index eff244a1..ca23f785 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/ImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/ImageParser.kt @@ -16,7 +16,7 @@ package com.ashampoo.kim.format import com.ashampoo.kim.common.ImageReadException -import com.ashampoo.kim.format.isobmff.ISOBMFFImageParser +import com.ashampoo.kim.format.bmff.BaseMediaFileFormatImageParser import com.ashampoo.kim.format.jpeg.JpegImageParser import com.ashampoo.kim.format.png.PngImageParser import com.ashampoo.kim.format.raf.RafImageParser @@ -47,7 +47,8 @@ fun interface ImageParser { ImageFormat.RAF -> RafImageParser - ImageFormat.HEIC -> ISOBMFFImageParser + ImageFormat.HEIC -> BaseMediaFileFormatImageParser + // ImageFormat.JXL -> BaseMediaFileFormatImageParser else -> null } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/ISOBMFFConstants.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BMFFConstants.kt similarity index 93% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/ISOBMFFConstants.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BMFFConstants.kt index 63722985..e517c373 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/ISOBMFFConstants.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BMFFConstants.kt @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff +package com.ashampoo.kim.format.bmff import com.ashampoo.kim.common.ByteOrder -object ISOBMFFConstants { +object BMFFConstants { val BMFF_BYTE_ORDER = ByteOrder.BIG_ENDIAN diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/ISOBMFFImageParser.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt similarity index 93% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/ISOBMFFImageParser.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt index f3870ee8..8866eb57 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/ISOBMFFImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt @@ -13,30 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff +package com.ashampoo.kim.format.bmff import com.ashampoo.kim.common.ImageReadException import com.ashampoo.kim.common.MetadataOffset import com.ashampoo.kim.common.MetadataType import com.ashampoo.kim.format.ImageMetadata import com.ashampoo.kim.format.ImageParser -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.BMFF_BYTE_ORDER -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.TIFF_HEADER_OFFSET_BYTE_COUNT -import com.ashampoo.kim.format.isobmff.boxes.MetaBox +import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BMFFConstants.TIFF_HEADER_OFFSET_BYTE_COUNT +import com.ashampoo.kim.format.bmff.boxes.MetaBox import com.ashampoo.kim.format.tiff.TiffReader import com.ashampoo.kim.input.ByteArrayByteReader import com.ashampoo.kim.input.ByteReader import com.ashampoo.kim.input.PositionTrackingByteReader import com.ashampoo.kim.input.PositionTrackingByteReaderDecorator - /** * Reads containers that follow the ISO base media file format * as defined in ISO/IEC 14496-12. Examples for these are MP4, HEIC & JPEG XL. * * https://en.wikipedia.org/wiki/ISO_base_media_file_format */ -object ISOBMFFImageParser : ImageParser { +object BaseMediaFileFormatImageParser : ImageParser { override fun parseMetadata(byteReader: ByteReader): ImageMetadata = parseMetadata(PositionTrackingByteReaderDecorator(byteReader)) @@ -47,7 +46,8 @@ object ISOBMFFImageParser : ImageParser { val allBoxes = BoxReader.readBoxes( byteReader = copyByteReader, - stopAfterMetaBox = true + stopAfterMetaBox = true, + offsetShift = 0 ) val metaBox = allBoxes.find { it.type == BoxType.META } as? MetaBox diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/BoxReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt similarity index 62% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/BoxReader.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt index 6cc3f4f7..89929d0c 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/BoxReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt @@ -14,18 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff - -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.BMFF_BYTE_ORDER -import com.ashampoo.kim.format.isobmff.boxes.Box -import com.ashampoo.kim.format.isobmff.boxes.FileTypeBox -import com.ashampoo.kim.format.isobmff.boxes.HandlerReferenceBox -import com.ashampoo.kim.format.isobmff.boxes.ItemInfoEntryBox -import com.ashampoo.kim.format.isobmff.boxes.ItemInformationBox -import com.ashampoo.kim.format.isobmff.boxes.ItemLocationBox -import com.ashampoo.kim.format.isobmff.boxes.MediaDataBox -import com.ashampoo.kim.format.isobmff.boxes.MetaBox -import com.ashampoo.kim.format.isobmff.boxes.PrimaryItemBox +package com.ashampoo.kim.format.bmff + +import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.boxes.Box +import com.ashampoo.kim.format.bmff.boxes.FileTypeBox +import com.ashampoo.kim.format.bmff.boxes.HandlerReferenceBox +import com.ashampoo.kim.format.bmff.boxes.ItemInfoEntryBox +import com.ashampoo.kim.format.bmff.boxes.ItemInformationBox +import com.ashampoo.kim.format.bmff.boxes.ItemLocationBox +import com.ashampoo.kim.format.bmff.boxes.MediaDataBox +import com.ashampoo.kim.format.bmff.boxes.MetaBox +import com.ashampoo.kim.format.bmff.boxes.PrimaryItemBox import com.ashampoo.kim.input.PositionTrackingByteReader /** @@ -41,7 +41,8 @@ object BoxReader { */ fun readBoxes( byteReader: PositionTrackingByteReader, - stopAfterMetaBox: Boolean = false + stopAfterMetaBox: Boolean = false, + offsetShift: Long = 0 ): List { val boxes = mutableListOf() @@ -52,7 +53,7 @@ object BoxReader { * Check if there are enough bytes for another box. * If so, we at least need the 8 header bytes. */ - if (byteReader.available < ISOBMFFConstants.BOX_HEADER_LENGTH) + if (byteReader.available < BMFFConstants.BOX_HEADER_LENGTH) break val offset: Long = byteReader.position.toLong() @@ -62,7 +63,7 @@ object BoxReader { byteReader.read4BytesAsInt("length", BMFF_BYTE_ORDER).toLong() val type = BoxType.of( - byteReader.readBytes("type", ISOBMFFConstants.TPYE_LENGTH) + byteReader.readBytes("type", BMFFConstants.TPYE_LENGTH) ) val actualLength: Long = when (length) { @@ -83,16 +84,18 @@ object BoxReader { val bytes = byteReader.readBytes("data", remainingBytesToReadInThisBox) + val globalOffset = offset + offsetShift + val box = when (type) { - BoxType.FTYP -> FileTypeBox(offset, actualLength, bytes) - BoxType.META -> MetaBox(offset, actualLength, bytes) - BoxType.HDLR -> HandlerReferenceBox(offset, actualLength, bytes) - BoxType.IINF -> ItemInformationBox(offset, actualLength, bytes) - BoxType.INFE -> ItemInfoEntryBox(offset, actualLength, bytes) - BoxType.ILOC -> ItemLocationBox(offset, actualLength, bytes) - BoxType.PITM -> PrimaryItemBox(offset, actualLength, bytes) - BoxType.MDAT -> MediaDataBox(offset, actualLength, bytes) - else -> Box(offset, type, actualLength, bytes) + BoxType.FTYP -> FileTypeBox(globalOffset, actualLength, bytes) + BoxType.META -> MetaBox(globalOffset, actualLength, bytes) + BoxType.HDLR -> HandlerReferenceBox(globalOffset, actualLength, bytes) + BoxType.IINF -> ItemInformationBox(globalOffset, actualLength, bytes) + BoxType.INFE -> ItemInfoEntryBox(globalOffset, actualLength, bytes) + BoxType.ILOC -> ItemLocationBox(globalOffset, actualLength, bytes) + BoxType.PITM -> PrimaryItemBox(globalOffset, actualLength, bytes) + BoxType.MDAT -> MediaDataBox(globalOffset, actualLength, bytes) + else -> Box(globalOffset, type, actualLength, bytes) } boxes.add(box) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/BoxType.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxType.kt similarity index 96% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/BoxType.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxType.kt index b6e92f23..9fdc687f 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/BoxType.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxType.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff +package com.ashampoo.kim.format.bmff import com.ashampoo.kim.common.toFourCCTypeString @@ -96,7 +96,7 @@ data class BoxType internal constructor( @Suppress("MagicNumber") fun of(typeBytes: ByteArray): BoxType { - require(typeBytes.size == ISOBMFFConstants.TPYE_LENGTH) { + require(typeBytes.size == BMFFConstants.TPYE_LENGTH) { "BoxType must be always 4 bytes!" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/CopyByteReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/CopyByteReader.kt similarity index 97% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/CopyByteReader.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/CopyByteReader.kt index e2dc7eb3..c78c9e8c 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/CopyByteReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/CopyByteReader.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff +package com.ashampoo.kim.format.bmff import com.ashampoo.kim.input.PositionTrackingByteReader import com.ashampoo.kim.output.ByteArrayByteWriter diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/Extent.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/Extent.kt similarity index 95% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/Extent.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/Extent.kt index ed3c4517..8a6fa4a3 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/Extent.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/Extent.kt @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff +package com.ashampoo.kim.format.bmff data class Extent( val itemId: Int, diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/Box.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/Box.kt similarity index 90% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/Box.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/Box.kt index a6e52c3a..233f83d8 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/Box.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/Box.kt @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes -import com.ashampoo.kim.format.isobmff.BoxType +import com.ashampoo.kim.format.bmff.BoxType open class Box( val offset: Long, diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/BoxContainer.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/BoxContainer.kt new file mode 100644 index 00000000..c5a4871d --- /dev/null +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/BoxContainer.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Ashampoo GmbH & Co. KG + * Copyright 2002-2023 Drew Noakes and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ashampoo.kim.format.bmff.boxes + +interface BoxContainer { + + val boxes: List + + companion object { + + fun findAllBoxesRecursive(boxes: List): List { + + val allBoxes = mutableListOf() + + for (box in boxes) { + + allBoxes.add(box) + + if (box is BoxContainer) + allBoxes.addAll(findAllBoxesRecursive(box.boxes)) + } + + return allBoxes + } + } +} diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/FileTypeBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/FileTypeBox.kt similarity index 84% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/FileTypeBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/FileTypeBox.kt index 22fd61c8..e7b60839 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/FileTypeBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/FileTypeBox.kt @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes import com.ashampoo.kim.common.toFourCCTypeString -import com.ashampoo.kim.format.isobmff.BoxType -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BMFFConstants +import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BoxType import com.ashampoo.kim.input.ByteArrayByteReader class FileTypeBox( @@ -34,9 +34,6 @@ class FileTypeBox( val compatibleBrands: List - override fun toString(): String = - "$type major=$majorBrand minor=$minorBrand compatible=$compatibleBrands" - init { val byteReader = ByteArrayByteReader(payload) @@ -56,11 +53,14 @@ class FileTypeBox( repeat(brandCount) { brands.add( byteReader - .read4BytesAsInt("brand $it", ISOBMFFConstants.BMFF_BYTE_ORDER) + .read4BytesAsInt("brand $it", BMFFConstants.BMFF_BYTE_ORDER) .toFourCCTypeString() ) } compatibleBrands = brands } + + override fun toString(): String = + "$type major=$majorBrand minor=$minorBrand compatible=$compatibleBrands" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/HandlerReferenceBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/HandlerReferenceBox.kt similarity index 94% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/HandlerReferenceBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/HandlerReferenceBox.kt index 25639586..c211abec 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/HandlerReferenceBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/HandlerReferenceBox.kt @@ -14,10 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes import com.ashampoo.kim.common.toHex -import com.ashampoo.kim.format.isobmff.BoxType +import com.ashampoo.kim.format.bmff.BoxType import com.ashampoo.kim.input.ByteArrayByteReader class HandlerReferenceBox( @@ -34,13 +34,6 @@ class HandlerReferenceBox( val name: String - override fun toString(): String = - "$type " + - "version=$version " + - "flags=${flags.toHex()} " + - "handlerType=$handlerType " + - "name=$name" - init { val byteReader = ByteArrayByteReader(payload) @@ -57,4 +50,11 @@ class HandlerReferenceBox( name = byteReader.readNullTerminatedString("name") } + + override fun toString(): String = + "$type " + + "version=$version " + + "flags=${flags.toHex()} " + + "handlerType=$handlerType " + + "name=$name" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemInfoEntryBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemInfoEntryBox.kt similarity index 93% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemInfoEntryBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemInfoEntryBox.kt index 071762ab..f8318555 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemInfoEntryBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemInfoEntryBox.kt @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes import com.ashampoo.kim.common.toFourCCTypeString import com.ashampoo.kim.common.toHex -import com.ashampoo.kim.format.isobmff.BoxType -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BoxType import com.ashampoo.kim.input.ByteArrayByteReader class ItemInfoEntryBox( @@ -40,15 +40,6 @@ class ItemInfoEntryBox( val itemName: String - override fun toString(): String = - "$type " + - "version=$version " + - "flags=${flags.toHex()} " + - "itemId=$itemId " + - "itemProtectionIndex=$itemProtectionIndex " + - "itemType=${itemType.toFourCCTypeString()} " + - "itemName=$itemName" - init { val byteReader = ByteArrayByteReader(payload) @@ -76,4 +67,13 @@ class ItemInfoEntryBox( /* Item name was always empty in test files. */ itemName = byteReader.readNullTerminatedString("itemName") } + + override fun toString(): String = + "$type " + + "version=$version " + + "flags=${flags.toHex()} " + + "itemId=$itemId " + + "itemProtectionIndex=$itemProtectionIndex " + + "itemType=${itemType.toFourCCTypeString()} " + + "itemName=$itemName" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemInformationBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemInformationBox.kt similarity index 76% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemInformationBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemInformationBox.kt index c7e80209..ae10c485 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemInformationBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemInformationBox.kt @@ -14,19 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes import com.ashampoo.kim.common.toHex -import com.ashampoo.kim.format.isobmff.BoxReader -import com.ashampoo.kim.format.isobmff.BoxType -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BoxReader +import com.ashampoo.kim.format.bmff.BoxType import com.ashampoo.kim.input.ByteArrayByteReader class ItemInformationBox( offset: Long, length: Long, payload: ByteArray -) : Box(offset, BoxType.IINF, length, payload) { +) : Box(offset, BoxType.IINF, length, payload), BoxContainer { val version: Int @@ -36,8 +36,7 @@ class ItemInformationBox( val map: Map - override fun toString(): String = - "$type version=$version flags=${flags.toHex()} ($entryCount entries)" + override val boxes: List init { @@ -52,7 +51,11 @@ class ItemInformationBox( else entryCount = byteReader.read4BytesAsInt("entryCount", BMFF_BYTE_ORDER) - val boxes = BoxReader.readBoxes(byteReader) + boxes = BoxReader.readBoxes( + byteReader = byteReader, + stopAfterMetaBox = false, + offsetShift = offset + 4 + 2 + if (version == 0) 2 else 4 + ) val map = mutableMapOf() @@ -65,4 +68,7 @@ class ItemInformationBox( this.map = map } + + override fun toString(): String = + "$type version=$version flags=${flags.toHex()} ($entryCount entries)" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemLocationBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemLocationBox.kt similarity index 95% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemLocationBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemLocationBox.kt index 4a69648f..d7f74132 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/ItemLocationBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/ItemLocationBox.kt @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes -import com.ashampoo.kim.format.isobmff.BoxType -import com.ashampoo.kim.format.isobmff.Extent -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BoxType +import com.ashampoo.kim.format.bmff.Extent import com.ashampoo.kim.input.ByteArrayByteReader class ItemLocationBox( @@ -61,15 +61,6 @@ class ItemLocationBox( val extents: List - override fun toString(): String = - "$type " + - "offsetSize=$offsetSize " + - "lengthSize=$lengthSize " + - "baseOffsetSize=$baseOffsetSize " + - "indexSize=$indexSize " + - "itemCount=$itemCount " + - "extents=$extents" - init { val byteReader = ByteArrayByteReader(payload) @@ -155,4 +146,13 @@ class ItemLocationBox( this.extents = extents } + + override fun toString(): String = + "$type " + + "offsetSize=$offsetSize " + + "lengthSize=$lengthSize " + + "baseOffsetSize=$baseOffsetSize " + + "indexSize=$indexSize " + + "itemCount=$itemCount " + + "extents=$extents" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/MediaDataBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/MediaDataBox.kt similarity index 91% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/MediaDataBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/MediaDataBox.kt index 628872c2..f032d9bd 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/MediaDataBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/MediaDataBox.kt @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes -import com.ashampoo.kim.format.isobmff.BoxType +import com.ashampoo.kim.format.bmff.BoxType /** * The Media Data Box contains all the actual data. diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/MetaBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/MetaBox.kt similarity index 84% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/MetaBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/MetaBox.kt index 5e0be1c4..1e5ae04a 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/MetaBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/MetaBox.kt @@ -14,14 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes import com.ashampoo.kim.common.MetadataOffset import com.ashampoo.kim.common.MetadataType import com.ashampoo.kim.common.toHex -import com.ashampoo.kim.format.isobmff.BoxReader -import com.ashampoo.kim.format.isobmff.BoxType -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants +import com.ashampoo.kim.format.bmff.BMFFConstants +import com.ashampoo.kim.format.bmff.BoxReader +import com.ashampoo.kim.format.bmff.BoxType import com.ashampoo.kim.input.ByteArrayByteReader /** @@ -31,7 +31,7 @@ class MetaBox( offset: Long, length: Long, payload: ByteArray -) : Box(offset, BoxType.META, length, payload) { +) : Box(offset, BoxType.META, length, payload), BoxContainer { val version: Int @@ -43,7 +43,28 @@ class MetaBox( val itemInfoBox: ItemInformationBox val itemLocationBox: ItemLocationBox - val boxes: List + override val boxes: List + + init { + + val byteReader = ByteArrayByteReader(payload) + + version = byteReader.readByteAsInt() + + flags = byteReader.readBytes("flags", 3) + + boxes = BoxReader.readBoxes( + byteReader = byteReader, + stopAfterMetaBox = false, + offsetShift = offset + 8 + ) + + /* Find & set mandatory boxes. */ + handlerReferenceBox = boxes.find { it.type == BoxType.HDLR } as HandlerReferenceBox + primaryItemBox = boxes.find { it.type == BoxType.PITM } as PrimaryItemBox + itemInfoBox = boxes.find { it.type == BoxType.IINF } as ItemInformationBox + itemLocationBox = boxes.find { it.type == BoxType.ILOC } as ItemLocationBox + } fun findMetadataOffsets(): List { @@ -55,7 +76,7 @@ class MetaBox( when (itemInfo.itemType) { - ISOBMFFConstants.ITEM_TYPE_EXIF -> + BMFFConstants.ITEM_TYPE_EXIF -> offsets.add( MetadataOffset( type = MetadataType.EXIF, @@ -64,7 +85,7 @@ class MetaBox( ) ) - ISOBMFFConstants.ITEM_TYPE_MIME -> + BMFFConstants.ITEM_TYPE_MIME -> offsets.add( MetadataOffset( type = MetadataType.XMP, @@ -83,21 +104,4 @@ class MetaBox( override fun toString(): String = "$type Box version=$version flags=${flags.toHex()} boxes=${boxes.map { it.type }}" - - init { - - val byteReader = ByteArrayByteReader(payload) - - version = byteReader.readByteAsInt() - - flags = byteReader.readBytes("flags", 3) - - boxes = BoxReader.readBoxes(byteReader) - - /* Find & set mandatory boxes. */ - handlerReferenceBox = boxes.find { it.type == BoxType.HDLR } as HandlerReferenceBox - primaryItemBox = boxes.find { it.type == BoxType.PITM } as PrimaryItemBox - itemInfoBox = boxes.find { it.type == BoxType.IINF } as ItemInformationBox - itemLocationBox = boxes.find { it.type == BoxType.ILOC } as ItemLocationBox - } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/PrimaryItemBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/PrimaryItemBox.kt similarity index 89% rename from src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/PrimaryItemBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/PrimaryItemBox.kt index a71f4a82..195afc3b 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/isobmff/boxes/PrimaryItemBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/boxes/PrimaryItemBox.kt @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ashampoo.kim.format.isobmff.boxes +package com.ashampoo.kim.format.bmff.boxes import com.ashampoo.kim.common.toHex -import com.ashampoo.kim.format.isobmff.BoxType -import com.ashampoo.kim.format.isobmff.ISOBMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BoxType import com.ashampoo.kim.input.ByteArrayByteReader class PrimaryItemBox( @@ -33,12 +33,6 @@ class PrimaryItemBox( val itemId: Int - override fun toString(): String = - "$type " + - "version=$version " + - "flags=${flags.toHex()} " + - "itemId=$itemId" - init { val byteReader = ByteArrayByteReader(payload) @@ -52,4 +46,10 @@ class PrimaryItemBox( else itemId = byteReader.read4BytesAsInt("itemId", BMFF_BYTE_ORDER) } + + override fun toString(): String = + "$type " + + "version=$version " + + "flags=${flags.toHex()} " + + "itemId=$itemId" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt index bc1fcbdc..ec6a0fc1 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt @@ -81,9 +81,8 @@ object XmpWriter { is MetadataUpdate.Keywords -> setKeywords(update.keywords) -// is MetadataUpdate.Faces -> { -// TODO How do write the fields? -// } + is MetadataUpdate.Faces -> + setFaces(update.faces, update.widthPx, update.heightPx) is MetadataUpdate.Persons -> setPersonsInImage(update.personsInImage) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/model/ImageFormat.kt b/src/commonMain/kotlin/com/ashampoo/kim/model/ImageFormat.kt index b01c5184..814aac88 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/model/ImageFormat.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/model/ImageFormat.kt @@ -137,8 +137,6 @@ enum class ImageFormat( * * If the byte array is less than REQUIRED_HEADER_BYTE_COUNT_FOR_DETECTION * (for example empty) than the detection returns null. - * - * Note: Can NOT detect HEIC! */ @JvmStatic fun detect(bytes: ByteArray): ImageFormat? { @@ -171,6 +169,9 @@ enum class ImageFormat( /* Check TIFF after the RAW files. */ bytes.startsWith(ImageFormatMagicNumbers.tiffLittleEndian) -> ImageFormat.TIFF bytes.startsWith(ImageFormatMagicNumbers.tiffBigEndian) -> ImageFormat.TIFF + /* Check JXL */ + bytes.startsWith(ImageFormatMagicNumbers.jxlCodeStream) -> ImageFormat.JXL + bytes.startsWith(ImageFormatMagicNumbers.jxlContainer) -> ImageFormat.JXL /* Check HEIC variants */ bytes.startsWithNullable(ImageFormatMagicNumbers.heic) -> ImageFormat.HEIC bytes.startsWithNullable(ImageFormatMagicNumbers.mif1) -> ImageFormat.HEIC diff --git a/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt b/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt index 7a0ff5af..df24eb78 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt @@ -15,33 +15,63 @@ */ package com.ashampoo.kim.model +import com.ashampoo.xmp.XMPRegionArea + /** * Represents possible updates that can be performed. */ sealed interface MetadataUpdate { - /** In a perfect world every file has an orientation flag. So we don't want NULLs here. */ - data class Orientation(val tiffOrientation: TiffOrientation) : MetadataUpdate + /** + * In a perfect world every file has an orientation flag. So we don't want NULLs here. + */ + data class Orientation( + val tiffOrientation: TiffOrientation + ) : MetadataUpdate - /** New taken date in millis or NULL to remove it (if the date is wrong and/or not known). */ - data class TakenDate(val takenDate: Long?) : MetadataUpdate + /** + * New taken date in millis or NULL to remove it (if the date is wrong and/or not known). + */ + data class TakenDate( + val takenDate: Long? + ) : MetadataUpdate - /** New GPS coordinates or NULL to remove it (if the location is wrong and/or not known) */ - data class GpsCoordinates(val gpsCoordinates: com.ashampoo.kim.model.GpsCoordinates?) : MetadataUpdate + /** + * New GPS coordinates or NULL to remove it (if the location is wrong and/or not known) + */ + data class GpsCoordinates( + val gpsCoordinates: com.ashampoo.kim.model.GpsCoordinates? + ) : MetadataUpdate - /** Can't be NULL. Should be UNRATED instead. */ - data class Rating(val photoRating: PhotoRating) : MetadataUpdate + /** + * Set a new Rating. + * Can't be NULL and should be UNRATED instead. + */ + data class Rating( + val photoRating: PhotoRating + ) : MetadataUpdate - /** List of new keywords to set. An empty list removes all keywords. */ - data class Keywords(val keywords: Set) : MetadataUpdate + /** + * List of new keywords to set. An empty list removes all keywords. + */ + data class Keywords( + val keywords: Set + ) : MetadataUpdate -// /** -// * List of new faces to set. An empty map removes all faces. -// * *Note*: Not supported right now! -// */ -// data class Faces(val faces: Map) : MetadataUpdate + /** + * List of new faces to set. An empty map removes all faces. + */ + data class Faces( + val faces: Map, + val widthPx: Int, + val heightPx: Int + ) : MetadataUpdate - /** List of new faces to set. An empty list removes all faces. */ - data class Persons(val personsInImage: Set) : MetadataUpdate + /** + * List of new faces to set. An empty list removes all faces. + */ + data class Persons( + val personsInImage: Set + ) : MetadataUpdate } diff --git a/src/commonTest/kotlin/com/ashampoo/kim/XmpExtractionTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/XmpExtractionTest.kt index 5e1fd399..11fe0b97 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/XmpExtractionTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/XmpExtractionTest.kt @@ -37,7 +37,7 @@ class XmpExtractionTest { // TODO Support these files as they have XMP val indicesUnsupported = setOf( - 59, 72, 73, 74, 75, 76 + 59, 72, 74, 75, 78, 79, 80 ) /** diff --git a/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt new file mode 100644 index 00000000..1583d980 --- /dev/null +++ b/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Ashampoo GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.ashampoo.kim.format.bmff + +import com.ashampoo.kim.format.bmff.boxes.BoxContainer +import com.ashampoo.kim.input.ByteArrayByteReader +import com.ashampoo.kim.input.PositionTrackingByteReaderDecorator +import com.ashampoo.kim.testdata.KimTestData +import kotlin.test.Test +import kotlin.test.assertEquals + +class BoxReaderTest { + + @Test + fun testExtractMetadataBytes() { + + val bytes = KimTestData.getBytesOf(KimTestData.HEIC_TEST_IMAGE_INDEX) + + val byteReader = PositionTrackingByteReaderDecorator(ByteArrayByteReader(bytes)) + + val boxes = BoxReader.readBoxes( + byteReader = byteReader, + stopAfterMetaBox = false + ) + + val allBoxes = BoxContainer.findAllBoxesRecursive(boxes) + + assertEquals(0, allBoxes.first { it.type == BoxType.FTYP }.offset) + assertEquals(36, allBoxes.first { it.type == BoxType.META }.offset) + assertEquals(48, allBoxes.first { it.type == BoxType.HDLR }.offset) + assertEquals(118, allBoxes.first { it.type == BoxType.PITM }.offset) + assertEquals(132, allBoxes.first { it.type == BoxType.IINF }.offset) + assertEquals(146, allBoxes.first { it.type == BoxType.INFE }.offset) + assertEquals(2572, allBoxes.first { it.type == BoxType.ILOC }.offset) + assertEquals(3404, allBoxes.first { it.type == BoxType.MDAT }.offset) + } +} diff --git a/src/commonTest/kotlin/com/ashampoo/kim/model/ImageFormatTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/model/ImageFormatTest.kt index 7affd487..8639bd37 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/model/ImageFormatTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/model/ImageFormatTest.kt @@ -57,6 +57,9 @@ class ImageFormatTest { index == KimTestData.HEIC_TEST_IMAGE_FROM_JPG_USING_GIMP_INDEX -> ImageFormat.HEIC index == KimTestData.HEIC_TEST_IMAGE_FROM_JPG_USING_APPLE_INDEX -> ImageFormat.HEIC index == KimTestData.HEIC_TEST_IMAGE_FROM_SAMSUNG_INDEX -> ImageFormat.HEIC + index == KimTestData.JXL_NAKED_BYTESTREAM_UNCOMPRESSED_INDEX -> ImageFormat.JXL + index == KimTestData.JXL_CONTAINER_UNCOMPRESSED_INDEX -> ImageFormat.JXL + index == KimTestData.JXL_CONTAINER_COMPRESSED_INDEX -> ImageFormat.JXL else -> null } diff --git a/src/commonTest/kotlin/com/ashampoo/kim/testdata/KimTestData.kt b/src/commonTest/kotlin/com/ashampoo/kim/testdata/KimTestData.kt index f3713b3d..97954331 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/testdata/KimTestData.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/testdata/KimTestData.kt @@ -30,7 +30,7 @@ object KimTestData { private const val RESOURCE_PATH: String = "src/commonTest/resources/com/ashampoo/kim/testdata" - const val TEST_PHOTO_COUNT: Int = 77 + const val TEST_PHOTO_COUNT: Int = 80 const val HIGHEST_JPEG_INDEX: Int = 50 const val PNG_TEST_IMAGE_INDEX: Int = 51 @@ -60,6 +60,9 @@ object KimTestData { const val HEIC_TEST_IMAGE_FROM_JPG_USING_GIMP_INDEX: Int = 75 const val HEIC_TEST_IMAGE_FROM_JPG_USING_APPLE_INDEX: Int = 76 const val HEIC_TEST_IMAGE_FROM_SAMSUNG_INDEX: Int = 77 + const val JXL_NAKED_BYTESTREAM_UNCOMPRESSED_INDEX: Int = 78 + const val JXL_CONTAINER_UNCOMPRESSED_INDEX: Int = 79 + const val JXL_CONTAINER_COMPRESSED_INDEX: Int = 80 @Suppress("MagicNumber") val photoIdsWithExifThumbnail: Set = setOf( @@ -116,6 +119,9 @@ object KimTestData { DNG_RW2_TEST_IMAGE_INDEX -> "dng" DNG_ORF_TEST_IMAGE_INDEX -> "dng" HIF_TEST_IMAGE_INDEX -> "hif" + JXL_NAKED_BYTESTREAM_UNCOMPRESSED_INDEX -> "jxl" + JXL_CONTAINER_UNCOMPRESSED_INDEX -> "jxl" + JXL_CONTAINER_COMPRESSED_INDEX -> "jxl" else -> "jpg" } diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/full/README.txt b/src/commonTest/resources/com/ashampoo/kim/testdata/full/README.txt index f9bb6d76..d3ec4f01 100644 --- a/src/commonTest/resources/com/ashampoo/kim/testdata/full/README.txt +++ b/src/commonTest/resources/com/ashampoo/kim/testdata/full/README.txt @@ -15,3 +15,7 @@ photo_74.heic = resaved photo_60.heic using GIMP (metadata lost) pnoto_75.heic = photo_1.jpg converted to HEIC using GIMP (metadata lost) pnoto_76.heic = photo_1.jpg converted to HEIC using Apple Preview photo_77.heic = Samsung Galaxy S21 5G Ultra OOC-HEIC + +photo_78.jxl = photo_6.jpg -> JXL using cjxl without container & without metadata compression +photo_79.jxl = photo_6.jpg -> JXL using cjxl with container & without metadata compression +photo_80.jxl = photo_6.jpg -> JXL using cjxl with container & with metadata compression diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_78.jxl b/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_78.jxl new file mode 100644 index 00000000..3bb38d7c Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_78.jxl differ diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_79.jxl b/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_79.jxl new file mode 100644 index 00000000..3bb38d7c Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_79.jxl differ diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_80.jxl b/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_80.jxl new file mode 100644 index 00000000..e0f6ab4c Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/testdata/full/photo_80.jxl differ diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/metadata.csv b/src/commonTest/resources/com/ashampoo/kim/testdata/metadata.csv index 4f3e1331..849e987d 100644 --- a/src/commonTest/resources/com/ashampoo/kim/testdata/metadata.csv +++ b/src/commonTest/resources/com/ashampoo/kim/testdata/metadata.csv @@ -75,3 +75,6 @@ photo_74.heic;null;null;null;null;null;null;null;null;null;null;null;null;null;n photo_75.heic;null;null;null;null;null;null;null;null;null;null;null;null;null;null;null;[] photo_76.heic;null;null;STANDARD;1563088871470;55.911316666666664;36.963480555555556;Canon;Canon EOS 70D;null;EF-S55-250mm f/4-5.6 IS STM;250;0.0025;7.1;250.0;0;[dxfoto, published] photo_77.heic;null;null;ROTATE_RIGHT;1696689965871;null;null;samsung;SM-G998B;null;null;640;0.016666666666666666;1.8;6.7;null;[] +photo_78.jxl;null;null;null;null;null;null;null;null;null;null;null;null;null;null;null;[] +photo_79.jxl;null;null;null;null;null;null;null;null;null;null;null;null;null;null;null;[] +photo_80.jxl;null;null;null;null;null;null;null;null;null;null;null;null;null;null;null;[] diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_78.txt b/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_78.txt new file mode 100644 index 00000000..f7a4e293 --- /dev/null +++ b/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_78.txt @@ -0,0 +1,2 @@ +File format : JXL +Resolution : null diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_79.txt b/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_79.txt new file mode 100644 index 00000000..f7a4e293 --- /dev/null +++ b/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_79.txt @@ -0,0 +1,2 @@ +File format : JXL +Resolution : null diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_80.txt b/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_80.txt new file mode 100644 index 00000000..f7a4e293 --- /dev/null +++ b/src/commonTest/resources/com/ashampoo/kim/testdata/txt/photo_80.txt @@ -0,0 +1,2 @@ +File format : JXL +Resolution : null diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/xmp/photo_73.xmp b/src/commonTest/resources/com/ashampoo/kim/testdata/xmp/photo_73.xmp new file mode 100644 index 00000000..ee104e3e --- /dev/null +++ b/src/commonTest/resources/com/ashampoo/kim/testdata/xmp/photo_73.xmp @@ -0,0 +1,23 @@ + + + + + + + bird + cat + dog + + + + + + \ No newline at end of file diff --git a/src/commonTest/resources/com/ashampoo/kim/testdata/xmp/photo_76.xmp b/src/commonTest/resources/com/ashampoo/kim/testdata/xmp/photo_76.xmp new file mode 100644 index 00000000..6bd07dbb Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/testdata/xmp/photo_76.xmp differ