From 9469ee1d6f4061b7e7cb82473624b4739dc2ea18 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Mon, 9 May 2022 22:54:00 +0800 Subject: [PATCH] Retain EXIF in enhanced files --- plugin/android/build.gradle | 1 + .../nc_photos/plugin/ImageProcessorService.kt | 152 +++++++++++++++++- 2 files changed, 147 insertions(+), 6 deletions(-) diff --git a/plugin/android/build.gradle b/plugin/android/build.gradle index f68b539c..ffc3918a 100644 --- a/plugin/android/build.gradle +++ b/plugin/android/build.gradle @@ -54,6 +54,7 @@ dependencies { coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" implementation "androidx.annotation:annotation:1.3.0" implementation "androidx.core:core-ktx:1.7.0" + implementation "androidx.exifinterface:exifinterface:1.3.3" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'org.tensorflow:tensorflow-lite:2.8.0' } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt index fc2c9177..e3c75def 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt @@ -17,6 +17,7 @@ import android.util.Log import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.exifinterface.media.ExifInterface import com.nkming.nc_photos.plugin.image_processor.ZeroDce import java.io.File import java.net.HttpURLConnection @@ -235,6 +236,115 @@ private data class ImageProcessorCommand( private open class ImageProcessorCommandTask(context: Context) : AsyncTask() { companion object { + private val exifTagOfInterests = listOf( + ExifInterface.TAG_IMAGE_DESCRIPTION, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_ORIENTATION, + ExifInterface.TAG_X_RESOLUTION, + ExifInterface.TAG_Y_RESOLUTION, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_ARTIST, + ExifInterface.TAG_COPYRIGHT, + ExifInterface.TAG_EXPOSURE_TIME, + ExifInterface.TAG_F_NUMBER, + ExifInterface.TAG_EXPOSURE_PROGRAM, + ExifInterface.TAG_SPECTRAL_SENSITIVITY, + ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, + ExifInterface.TAG_OECF, + ExifInterface.TAG_SENSITIVITY_TYPE, + ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, + ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, + ExifInterface.TAG_ISO_SPEED, + ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, + ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, + ExifInterface.TAG_EXIF_VERSION, + ExifInterface.TAG_DATETIME_ORIGINAL, + ExifInterface.TAG_DATETIME_DIGITIZED, + ExifInterface.TAG_OFFSET_TIME, + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, + ExifInterface.TAG_SHUTTER_SPEED_VALUE, + ExifInterface.TAG_APERTURE_VALUE, + ExifInterface.TAG_BRIGHTNESS_VALUE, + ExifInterface.TAG_EXPOSURE_BIAS_VALUE, + ExifInterface.TAG_MAX_APERTURE_VALUE, + ExifInterface.TAG_SUBJECT_DISTANCE, + ExifInterface.TAG_METERING_MODE, + ExifInterface.TAG_LIGHT_SOURCE, + ExifInterface.TAG_FLASH, + ExifInterface.TAG_FOCAL_LENGTH, + ExifInterface.TAG_SUBJECT_AREA, + ExifInterface.TAG_MAKER_NOTE, + ExifInterface.TAG_USER_COMMENT, + ExifInterface.TAG_SUBSEC_TIME, + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, + ExifInterface.TAG_FLASHPIX_VERSION, + ExifInterface.TAG_FLASH_ENERGY, + ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, + ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, + ExifInterface.TAG_SUBJECT_LOCATION, + ExifInterface.TAG_EXPOSURE_INDEX, + ExifInterface.TAG_SENSING_METHOD, + ExifInterface.TAG_FILE_SOURCE, + ExifInterface.TAG_SCENE_TYPE, + ExifInterface.TAG_CFA_PATTERN, + ExifInterface.TAG_CUSTOM_RENDERED, + ExifInterface.TAG_EXPOSURE_MODE, + ExifInterface.TAG_WHITE_BALANCE, + ExifInterface.TAG_DIGITAL_ZOOM_RATIO, + ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, + ExifInterface.TAG_SCENE_CAPTURE_TYPE, + ExifInterface.TAG_GAIN_CONTROL, + ExifInterface.TAG_CONTRAST, + ExifInterface.TAG_SATURATION, + ExifInterface.TAG_SHARPNESS, + ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, + ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, + ExifInterface.TAG_IMAGE_UNIQUE_ID, + ExifInterface.TAG_CAMERA_OWNER_NAME, + ExifInterface.TAG_BODY_SERIAL_NUMBER, + ExifInterface.TAG_LENS_SPECIFICATION, + ExifInterface.TAG_LENS_MAKE, + ExifInterface.TAG_LENS_MODEL, + ExifInterface.TAG_GAMMA, + ExifInterface.TAG_GPS_VERSION_ID, + ExifInterface.TAG_GPS_LATITUDE_REF, + ExifInterface.TAG_GPS_LATITUDE, + ExifInterface.TAG_GPS_LONGITUDE_REF, + ExifInterface.TAG_GPS_LONGITUDE, + ExifInterface.TAG_GPS_ALTITUDE_REF, + ExifInterface.TAG_GPS_ALTITUDE, + ExifInterface.TAG_GPS_TIMESTAMP, + ExifInterface.TAG_GPS_SATELLITES, + ExifInterface.TAG_GPS_STATUS, + ExifInterface.TAG_GPS_MEASURE_MODE, + ExifInterface.TAG_GPS_DOP, + ExifInterface.TAG_GPS_SPEED_REF, + ExifInterface.TAG_GPS_SPEED, + ExifInterface.TAG_GPS_TRACK_REF, + ExifInterface.TAG_GPS_TRACK, + ExifInterface.TAG_GPS_IMG_DIRECTION_REF, + ExifInterface.TAG_GPS_IMG_DIRECTION, + ExifInterface.TAG_GPS_MAP_DATUM, + ExifInterface.TAG_GPS_DEST_LATITUDE_REF, + ExifInterface.TAG_GPS_DEST_LATITUDE, + ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, + ExifInterface.TAG_GPS_DEST_LONGITUDE, + ExifInterface.TAG_GPS_DEST_BEARING_REF, + ExifInterface.TAG_GPS_DEST_BEARING, + ExifInterface.TAG_GPS_DEST_DISTANCE_REF, + ExifInterface.TAG_GPS_DEST_DISTANCE, + ExifInterface.TAG_GPS_PROCESSING_METHOD, + ExifInterface.TAG_GPS_AREA_INFORMATION, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_GPS_DIFFERENTIAL, + ExifInterface.TAG_GPS_H_POSITIONING_ERROR, + ) + private const val TAG = "ImageProcessorCommandTask" } @@ -263,7 +373,7 @@ private open class ImageProcessorCommandTask(context: Context) : "Unknown method: ${cmd.method}" ) } - saveBitmap(output, cmd.filename) + saveBitmap(output, cmd.filename, file) } finally { file.delete() } @@ -302,12 +412,42 @@ private open class ImageProcessorCommandTask(context: Context) : } } - private fun saveBitmap(bitmap: Bitmap, filename: String): Uri { - return MediaStoreUtil.writeFileToDownload( - context, { - bitmap.compress(Bitmap.CompressFormat.JPEG, 85, it) - }, filename, "Photos (for Nextcloud)/Enhanced Photos" + private fun saveBitmap( + bitmap: Bitmap, filename: String, srcFile: File + ): Uri { + val outFile = File.createTempFile("out", null, getTempDir(context)) + outFile.outputStream().use { + bitmap.compress(Bitmap.CompressFormat.JPEG, 85, it) + } + + // then copy the EXIF tags + try { + val iExif = ExifInterface(srcFile) + val oExif = ExifInterface(outFile) + copyExif(iExif, oExif) + oExif.saveAttributes() + } catch (e: Throwable) { + Log.e(TAG, "[copyExif] Failed while saving EXIF", e) + } + + // move file to user accessible storage + val uri = MediaStoreUtil.copyFileToDownload( + context, Uri.fromFile(outFile), filename, + "Photos (for Nextcloud)/Enhanced Photos" ) + outFile.delete() + return uri + } + + private fun copyExif(from: ExifInterface, to: ExifInterface) { + // only a subset will be copied over + for (t in exifTagOfInterests) { + try { + from.getAttribute(t)?.let { to.setAttribute(t, it) } + } catch (e: Throwable) { + Log.e(TAG, "[copyExif] Failed while copying tag: $t", e) + } + } } @SuppressLint("StaticFieldLeak")