Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Don't touch line endings for all files
* -text
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: StefanOltmann
14 changes: 4 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
jobs:
build:
name: Build & Test
runs-on: macos-15
runs-on: macos-26
steps:
- name: Checkout workspace
uses: actions/checkout@v4
Expand All @@ -24,10 +24,10 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17.0.16+8'
- name: Select XCode 16.4
java-version: '17'
- name: Select XCode 26.4
run: |
sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer
sudo xcode-select -s /Applications/Xcode_26.4.app/Contents/Developer
xcodebuild -version
- name: Gradle Cache
uses: actions/cache@v4
Expand Down Expand Up @@ -99,12 +99,6 @@ jobs:
if-no-files-found: error
name: kim.exe
path: build/bin/win/releaseExecutable/kim.exe
- name: Upload kim-macosX64.kexe
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
name: kim-macosX64.kexe
path: build/bin/macosX64/releaseExecutable/kim.kexe
- name: Upload kim-macosArm64.kexe
uses: actions/upload-artifact@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@
/.kotlin/
/build/
/local.properties
/src/commonTest/resources/com/ashampoo/kim/testdata/full/*.html
/src/commonTest/resources/de/stefan_oltmann/kim/testdata/full/*.html
7 changes: 5 additions & 2 deletions .idea/runConfigurations/Check_dependency_updates.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 25 additions & 5 deletions NOTICE.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
Ashampoo Kim - Kotlin Image Metadata
Kim - Kotlin Image Metadata
Copyright 2026 Stefan Oltmann

-----------------------------------------------------------------------

This project is a continuation of the work originally created by
Stefan Oltmann during his employment at Ashampoo GmbH & Co. KG.

Copyright 2025 Ashampoo GmbH & Co. KG
Copyright 2026 Stefan Oltmann

The usage rights to the original contributions (up to August 2025)
are held by Ashampoo GmbH & Co. KG and are licensed under the
Apache License, Version 2.0.

Since 2026, the project is independently maintained and
developed by Stefan Oltmann.

-----------------------------------------------------------------------

This product includes a modified portion of 'Apache Commons Imaging'
developed by the Apache Software Foundation (https://www.apache.org/).
The metadata handling logic, originally implemented in Java, has been rewritten in Kotlin.
The metadata handling logic, originally implemented in Java, has
been rewritten in Kotlin.
Copyright 2007-2023 The Apache Software Foundation

Logic for reading ISOBMFF containers was derived from 'metadata-extractor'
developed by Drew Noakes (https://github.com/drewnoakes/metadata-extractor).
Copyright 2002-2023 Drew Noakes and contributors

-----------------------------------------------------------------------

This product includes images from Unsplash provided under
the Unsplash license (https://unsplash.com/license).
These files are used as test data.

The sample images photo_62.nef, photo_63.arw, photo_64.rw2,
photo_56.orf, photo_72.hif and photo_83.cr3 are taken from
The sample images media_62.nef, media_63.arw, media_64.rw2,
media_56.orf, media_72.hif and media_83.cr3 are taken from
https://github.com/drewnoakes/metadata-extractor-images
under the note "You are free to use these media files however you wish".

The sample image photo_77.heic was contributed by Miroslav Sobotka.
The sample image media_77.heic was contributed by Miroslav Sobotka.
26 changes: 11 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Kim - Kotlin Image Metadata

[![Kotlin](https://img.shields.io/badge/kotlin-2.2.0-blue.svg?logo=kotlin)](httpw://kotlinlang.org)
[![Kotlin](https://img.shields.io/badge/kotlin-2.3.20-blue.svg?logo=kotlin)](httpw://kotlinlang.org)
![JVM](https://img.shields.io/badge/-JVM-gray.svg?style=flat)
![Android](https://img.shields.io/badge/-Android-gray.svg?style=flat)
![iOS](https://img.shields.io/badge/-iOS-gray.svg?style=flat)
Expand All @@ -12,15 +12,14 @@

Kim is a Kotlin Multiplatform library for reading and writing image metadata.

It's part of [Ashampoo Photo Organizer](https://ashampoo.com/photo-organizer).

## Features

* JPG: Read & Write EXIF, IPTC & XMP
* PNG: Read & Write `eXIf` chunk & XMP
+ Also read non-standard EXIF & IPTC from `tEXt`/`zTXt` chunk
* WebP: Read & Write EXIF & XMP
* HEIC / AVIF: Read EXIF & XMP
+ Support for animated AVIF files (AV1 Image Sequence)
* JXL: Read & Write EXIF & XMP of uncompressed files
* TIFF / RAW: Read EXIF & XMP
+ Full support for Adobe DNG, Canon CR2, Canon CR3 & Fujifilm RAF
Expand All @@ -29,17 +28,14 @@ It's part of [Ashampoo Photo Organizer](https://ashampoo.com/photo-organizer).
+ API for preview image extraction of DNG, CR2, CR3, RAF, NEF, ARW & RW2
* GIF: Read & Write XMP
* Handling of XMP content through
[XMP Core for Kotlin Multiplatform](https://github.com/Ashampoo/xmpcore)
[XMP Core for Kotlin Multiplatform](https://github.com/StefanOltmann/xmpcore)
* Convenient `Kim.update()` API to perform updates to the relevant places
+ JPG: Lossless rotation by modifying only one byte (where present)

The future development of features on our part is driven entirely by the needs
of Ashampoo Photo Organizer, which, in turn, is driven by user community feedback.

## Installation

```
implementation("com.ashampoo:kim:0.26.2")
implementation("de.stefan-oltmann:kim:0.29.0")
```

For the targets `wasmJs` & `js` you also need to specify this:
Expand All @@ -53,8 +49,8 @@ implementation(npm("pako", "2.1.0"))
### Read metadata

`Kim.readMetadata()` takes `kotlin.ByteArray` on all platforms and depending on
the platform also `kotlinx.io.files.Path`, Ktor `Source` & `ByteReadChannel`,
`java.io.File`, `java.io.InputStream`, `NSData` and `String` paths.
the platform also `kotlinx.io.files.Path`, `kotlinx.io.Source` (for usage with Ktor) & `ByteReadChannel`,
`java.io.File`, `java.io.InputStream`, `NSData` (iOS) and `String` paths.

```kotlin
val bytes: ByteArray = loadBytes()
Expand All @@ -75,7 +71,7 @@ println("Taken date: $takenDate")

### Create high level summary object

This creates an instance of [PhotoMetadata](src/commonMain/kotlin/com/ashampoo/kim/model/PhotoMetadata.kt).
This creates an instance of [MetadataSummary](src/commonMain/kotlin/de/stefan_oltmann/kim/model/MetadataSummary.kt).
It contains the following:

- Image size
Expand All @@ -95,7 +91,7 @@ It contains the following:
```kotlin
val bytes: ByteArray = loadBytes()

val photoMetadata = Kim.readMetadata(bytes).convertToPhotoMetadata()
val summary = Kim.readMetadata(bytes).convertToSummary()
```

### Change orientation using low level API
Expand Down Expand Up @@ -136,7 +132,7 @@ val newBytes = Kim.update(
)
```

See [AbstractUpdaterTest](src/commonTest/kotlin/com/ashampoo/kim/format/AbstractUpdaterTest.kt) for more samples.
See [AbstractUpdaterTest](src/commonTest/kotlin/de/stefan_oltmann/kim/format/AbstractUpdaterTest.kt) for more samples.

### Update thumbnail using Kim.update() API

Expand All @@ -156,7 +152,7 @@ See the [Java example project](examples/kim-java-sample/src/main/java/Main.java)

## Limitations

* Inability to update EXIF, IPTC and XMP in JPG files simultaneously.
* Inability to update EXIF, IPTC, and XMP in JPG files simultaneously.
* Does not read the image size and orientation for HEIC, AVIF & JPEG XL.
* Does not read brotli compressed metadata of JPEG XL due to missing brotli KMP libs.
* MakerNote support is experimental and limited.
Expand All @@ -175,7 +171,7 @@ This approach extends to AVIF images, as they repurpose the same boxes.

## Contributions

Contributions to Ashampoo Kim are welcome! If you encounter any issues,
Contributions to Kim are welcome! If you encounter any issues,
have suggestions for improvements, or would like to contribute new features,
please feel free to submit a pull request.

Expand Down
40 changes: 16 additions & 24 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ repositories {
mavenCentral()
}

val productName: String = "Ashampoo Kim"
val productName: String = "Kim"

description = productName
group = "com.ashampoo"
group = "de.stefan_oltmann.kim"
description = "Kotlin Image Metadata manipulation library"
version = "0.0.0"

gitVersioning.apply {
Expand Down Expand Up @@ -85,7 +85,7 @@ kotlin {
binaries {
executable(setOf(NativeBuildType.RELEASE)) {
baseName = "kim"
entryPoint = "com.ashampoo.kim.main"
entryPoint = "de.stefan_oltmann.kim.main"
}
staticLib(namePrefix = "", setOf(NativeBuildType.RELEASE)) {
baseName = "kim"
Expand All @@ -96,7 +96,7 @@ kotlin {
linuxX64 {
binaries {
executable(setOf(NativeBuildType.RELEASE)) {
entryPoint = "com.ashampoo.kim.main"
entryPoint = "de.stefan_oltmann.kim.main"
}
staticLib(namePrefix = "", setOf(NativeBuildType.RELEASE)) {
baseName = "kim"
Expand All @@ -107,7 +107,7 @@ kotlin {
linuxArm64 {
binaries {
executable(setOf(NativeBuildType.RELEASE)) {
entryPoint = "com.ashampoo.kim.main"
entryPoint = "de.stefan_oltmann.kim.main"
}
staticLib(namePrefix = "", setOf(NativeBuildType.RELEASE)) {
baseName = "kim"
Expand Down Expand Up @@ -151,7 +151,11 @@ kotlin {
/* Kotlin Test */
implementation(kotlin("test"))

/* Multiplatform file access */
implementation(libs.kotlinx.io.core)

/* Test resources */
implementation(libs.resources)
}
}

Expand All @@ -165,13 +169,12 @@ kotlin {
/* Apple Silicon iOS Simulator */
iosSimulatorArm64(),
/* macOS Devices */
macosX64(),
macosArm64()
).forEach {

it.binaries.executable(setOf(NativeBuildType.RELEASE)) {
baseName = "kim"
entryPoint = "com.ashampoo.kim.main"
entryPoint = "de.stefan_oltmann.kim.main"
}

it.binaries.framework(
Expand Down Expand Up @@ -246,7 +249,6 @@ kotlin {
val iosArm64Main by sourceSets.getting
val iosX64Main by sourceSets.getting
val iosSimulatorArm64Main by sourceSets.getting
val macosX64Main by sourceSets.getting
val macosArm64Main by sourceSets.getting

@Suppress("UnusedPrivateMember") // False positive
Expand All @@ -259,14 +261,12 @@ kotlin {
iosArm64Main.dependsOn(this)
iosX64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
macosX64Main.dependsOn(this)
macosArm64Main.dependsOn(this)
}

val iosArm64Test by sourceSets.getting
val iosX64Test by sourceSets.getting
val iosSimulatorArm64Test by sourceSets.getting
val macosX64Test by sourceSets.getting
val macosArm64Test by sourceSets.getting

@Suppress("UnusedPrivateMember") // False positive
Expand All @@ -277,7 +277,6 @@ kotlin {
iosArm64Test.dependsOn(this)
iosX64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
macosX64Test.dependsOn(this)
macosArm64Test.dependsOn(this)
}

Expand Down Expand Up @@ -324,7 +323,7 @@ tasks.getByPath("build").finalizedBy(writeVersion)
// region Android setup
android {

namespace = "com.ashampoo.kim"
namespace = "de.stefan_oltmann.kim"

compileSdk = libs.versions.android.compile.sdk.get().toInt()
buildToolsVersion = libs.versions.android.build.tools.get()
Expand Down Expand Up @@ -361,7 +360,7 @@ mavenPublishing {
signAllPublications()

coordinates(
groupId = "com.ashampoo",
groupId = "de.stefan-oltmann",
artifactId = "kim",
version = version.toString()
)
Expand All @@ -370,7 +369,7 @@ mavenPublishing {

name = productName
description = "Kotlin Multiplatform library for image metadata manipulation"
url = "https://github.com/Software-Rangers/kim"
url = "https://github.com/StefanOltmann/kim"

licenses {
license {
Expand All @@ -379,25 +378,18 @@ mavenPublishing {
}
}

organization {
name = "Software Rangers GmbH"
url = "https://software-rangers.com/"
}

developers {
developer {
name = "Stefan Oltmann"
url = "https://stefan-oltmann.de/"
organization = "Software Rangers GmbH"
organizationUrl = "https://software-rangers.com/"
roles = listOf("maintainer", "developer")
properties = mapOf("github" to "StefanOltmann")
}
}

scm {
url = "https://github.com/Software-Rangers/kim"
connection = "scm:git:git://github.com/Software-Rangers/kim.git"
url = "https://github.com/StefanOltmann/kim"
connection = "scm:git:git://github.com/StefanOltmann/kim.git"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ naming:
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9_]*)*'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][_A-Z0-9]*'
Expand Down
4 changes: 2 additions & 2 deletions examples/kim-java-sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ plugins {
id 'java'
}

group = 'com.ashampoo'
group = 'de.stefan-oltmann'
version = '1.0-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
implementation 'com.ashampoo:kim:0.18.4'
implementation 'de.stefan-oltmann:kim:0.27.0'
}

// Needed to make it work for the Gradle java plugin
Expand Down
Loading
Loading