It seems that Appfarm is removing EXIF data from photos when the photo is stored as a file on the server. I can read the EXIF data from the image when it’s a runtime object, but as soon as I save the file and retrieve it, the information appears to be lost. I’ve experimented with various settings, such as compression, but the issue doesn’t seem to be related to that.
Is there any way to preserve EXIF data when storing photos in Appfarm? I would appreciate having built-in support for EXIF data as an optional feature for images.
Has anyone managed to preserve this data in Appfarm?
I believe the EXIF metadata is removed if you use Compress image or Max Image Size with the upload (Create File Object). I just tested with a jpeg with some metadata, uploaded to Appfarm with Create File Object, and downloaded with Open URL:
With Compression: The metadata was removed
Without Compression: The metadata was intact
There are external libraries to be used if you want to access this EXIF metadata with javascript inside your application. If you need to compress images, I also believe you may e.g. use coded component and HTML5 to extract the EXIF data before the upload, store it, and apply it on download (not 100% sure, but looks like a possibility).
You are correct that some EXIF data survive when you turn off compression. But most information is still lost. Comparing the same image before and after save (no compression) gives me this:
I then extract the data I need and store on the file object. This is the current code in case anyone else need to do the same:
function displayImageAndExif(fileUrl) {
const img = new Image(); // Create a new image object
img.src = fileUrl;
console.log("Attempting to load image from URL:", fileUrl); // Debug info for the URL
img.onload = function() {
console.log("Image loaded successfully.");
console.log("Image dimensions:", img.width, "x", img.height); // Display image dimensions
console.log("Image source:", img.src); // Display the image source URL
EXIF.getData(img, function() {
const allMetaData = EXIF.getAllTags(this);
let dateTimeOriginal = allMetaData.DateTimeOriginal || null;
let latitude = null;
let longitude = null;
// Convert Latitude and Longitude to decimal format
if (allMetaData.GPSLatitude && allMetaData.GPSLongitude) {
latitude = convertDMSToDecimal(allMetaData.GPSLatitude, allMetaData.GPSLatitudeRef);
longitude = convertDMSToDecimal(allMetaData.GPSLongitude, allMetaData.GPSLongitudeRef);
}
// Format DateTimeOriginal to ISO 8601 format (yyyy-MM-ddTHH:mm:ss.SSSZ)
if (dateTimeOriginal) {
dateTimeOriginal = formatDateTimeToISO(dateTimeOriginal);
}
// Output EXIF data to the console and update the respective Appfarm data fields
if (dateTimeOriginal) {
console.log("DateTimeOriginal:", dateTimeOriginal);
exifextractor.data.dateTimeOriginal.set(dateTimeOriginal);
} else {
console.log("DateTimeOriginal not found.");
exifextractor.data.dateTimeOriginal.set('');
}
if (latitude && longitude) {
console.log("Latitude:", latitude, "Longitude:", longitude);
exifextractor.data.latitude.set(latitude);
exifextractor.data.longitude.set(longitude);
} else {
console.log("Latitude and/or Longitude not found.");
exifextractor.data.latitude.set('');
exifextractor.data.longitude.set('');
}
});
};
img.onerror = function() {
console.error("Failed to load image. Please check the URL.");
};
}
// Helper function to convert DMS to Decimal format
function convertDMSToDecimal(dms, ref) {
const degrees = dms[0];
const minutes = dms[1];
const seconds = dms[2];
let decimal = degrees + minutes / 60 + seconds / 3600;
if (ref === "S" || ref === "W") {
decimal = -decimal;
}
return decimal;
}
// Helper function to format DateTimeOriginal to ISO 8601 format
function formatDateTimeToISO(dateTime) {
const [date, time] = dateTime.split(" ");
const formattedDate = date.replace(/:/g, "-"); // Convert yyyy:mm:dd to yyyy-mm-dd
const formattedTime = time + ".000Z"; // Add milliseconds and Zulu time zone
return formattedDate + "T" + formattedTime;
}
// Listen for changes in the file URL
exifextractor.data.fileContent.on("change", () => {
const fileUrl = exifextractor.data.fileContent.get(); // Get the updated image URL
if (fileUrl) {
displayImageAndExif(fileUrl); // Call the function to display the image and extract EXIF data
}
});
I would personally prefer if Appfarm could offer a way to preserve all of EXIF data even when using compression.
Strange. Not sure why, but in my case - uploading a jpg of 5.4MB taken with a mobile phone - the EXIF metadata are the same (see attached image). I used the following tool to analyze both:
But, if you are able to solve this with coded component - go for it
BEFORE:
FileName Example photo.jpg
FileSize 5.4 MB
FileModifyDate 2024:10:11 08:47:40+0000
FileAccessDate 2024:10:11 08:47:40+0000
FileInodeChangeDate 2024:10:11 08:47:40+0000
FilePermissions -rw-rw-r--
FileType JPEG
FileTypeExtension jpg
MIMEType image/jpeg
ExifByteOrder Big-endian (Motorola, MM)
ImageWidth 4000
ImageHeight 3000
EncodingProcess Baseline DCT, Huffman coding
BitsPerSample 8
ColorComponents 3
YCbCrSubSampling YCbCr4:2:0 (2 2)
ImageDescription Kristians example photo
Make samsung
Model Galaxy S24 Ultra
Orientation Rotate 90 CW
XResolution 72
YResolution 72
ResolutionUnit inches
Software S928BXXU3AXH7
ModifyDate 2024:10:10 20:09:40+0000
Artist Kristian Mella
YCbCrPositioning Centered
ExposureTime 1/100
FNumber 1.7
ExposureProgram Program AE
ISO 1000
ExifVersion 0220
DateTimeOriginal 2024:10:10 20:09:40+0000
CreateDate 2024:10:10 20:09:40+0000
OffsetTime +02:00
OffsetTimeOriginal +02:00
ShutterSpeedValue 1
ApertureValue 1.7
ExposureCompensation 0
MaxApertureValue 1.7
MeteringMode Center-weighted average
Flash No Flash
FocalLength 6.3 mm
SubSecTime 868
SubSecTimeOriginal 868
SubSecTimeDigitized 868
FlashpixVersion 0100
ColorSpace Uncalibrated
ExifImageWidth 4000
ExifImageHeight 3000
ExposureMode Auto
WhiteBalance Auto
DigitalZoomRatio 1
FocalLengthIn35mmFormat 23 mm
SceneCaptureType Standard
ImageUniqueID HK0XLQE00VM
GPSLatitudeRef North
GPSLatitude +59.9021866000
GPSLongitudeRef East
GPSLongitude +10.5365771000
GPSAltitudeRef Above Sea Level
GPSAltitude 80 m Above Sea Level
XPTitle Kristians example photo
XPAuthor Kristian Mella
XPSubject Testing metadata
Padding (Binary data 268 bytes, use -b option to extract)
Compression JPEG (old-style)
ThumbnailOffset 1772
ThumbnailLength 23080
ThumbnailImage (Binary data 23080 bytes, use -b option to extract)
ProfileCMMType
ProfileVersion 4.3.0
ProfileClass Display Device Profile
ColorSpaceData RGB
ProfileConnectionSpace XYZ
ProfileDateTime 2022:07:01 00:00:00+0000
ProfileFileSignature acsp
PrimaryPlatform Unknown (SEC)
CMMFlags Not Embedded, Independent
DeviceManufacturer Unknown (SEC)
DeviceModel
DeviceAttributes Reflective, Glossy, Positive, Color
RenderingIntent Perceptual
ConnectionSpaceIlluminant 0.9642 1 0.82491
ProfileCreator Unknown (SEC)
ProfileID 0
ProfileDescription DCI-P3 D65 Gamut with sRGB Transfer
ProfileCopyright Copyright (c) 2022 Samsung Electronics Co., Ltd.
MediaWhitePoint 0.9642 1 0.82491
ChromaticAdaptation 1.04781 0.02289 -0.05013 0.02954 0.99048 -0.01704 -0.00923 0.01505 0.75214
RedMatrixColumn 0.51508 0.24117 -0.00105
GreenMatrixColumn 0.29195 0.69223 0.04189
BlueMatrixColumn 0.15718 0.06659 0.78455
RedTRC (Binary data 32 bytes, use -b option to extract)
GreenTRC (Binary data 32 bytes, use -b option to extract)
BlueTRC (Binary data 32 bytes, use -b option to extract)
XMPToolkit Adobe XMP Core 5.1.2
Version 1
DirectoryItemSemantic Primary
DirectoryItemMime image/jpeg
DirectoryItemLength 40637
About uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b
Creator Kristian Mella
Title Kristians example photo
Description Kristians example photo
MPFVersion 0100
NumberOfImages 2
MPImageFlags (none)
MPImageFormat JPEG
MPImageType Undefined
MPImageLength 40637
MPImageStart 5404096
DependentImage1EntryNumber 0
DependentImage2EntryNumber 0
MPImage2 (Binary data 40637 bytes, use -b option to extract)
Aperture 1.7
ImageSize 4000x3000
Megapixels 12
ScaleFactor35efl 3.7
ShutterSpeed 1/100
SubSecCreateDate 2024:10:10 20:09:40+0000
SubSecDateTimeOriginal 2024:10:10 20:09:40+0200
SubSecModifyDate 2024:10:10 20:09:40+0200
CircleOfConfusion 0.008 mm
FOV 76.1 deg
FocalLength35efl 6.3 mm (35 mm equivalent: 23.0 mm)
GPSPosition +59.9021866000, +10.5365771000
HyperfocalDistance 2.84 m
LightValue 4.9
AFTER:
FileName Example photo (4).jpg
FileSize 5.4 MB
FileModifyDate 2024:10:11 08:48:17+0000
FileAccessDate 2024:10:11 08:48:16+0000
FileInodeChangeDate 2024:10:11 08:48:17+0000
FilePermissions -rw-rw-r--
FileType JPEG
FileTypeExtension jpg
MIMEType image/jpeg
ExifByteOrder Big-endian (Motorola, MM)
ImageWidth 4000
ImageHeight 3000
EncodingProcess Baseline DCT, Huffman coding
BitsPerSample 8
ColorComponents 3
YCbCrSubSampling YCbCr4:2:0 (2 2)
ImageDescription Kristians example photo
Make samsung
Model Galaxy S24 Ultra
Orientation Rotate 90 CW
XResolution 72
YResolution 72
ResolutionUnit inches
Software S928BXXU3AXH7
ModifyDate 2024:10:10 20:09:40+0000
Artist Kristian Mella
YCbCrPositioning Centered
ExposureTime 1/100
FNumber 1.7
ExposureProgram Program AE
ISO 1000
ExifVersion 0220
DateTimeOriginal 2024:10:10 20:09:40+0000
CreateDate 2024:10:10 20:09:40+0000
OffsetTime +02:00
OffsetTimeOriginal +02:00
ShutterSpeedValue 1
ApertureValue 1.7
ExposureCompensation 0
MaxApertureValue 1.7
MeteringMode Center-weighted average
Flash No Flash
FocalLength 6.3 mm
SubSecTime 868
SubSecTimeOriginal 868
SubSecTimeDigitized 868
FlashpixVersion 0100
ColorSpace Uncalibrated
ExifImageWidth 4000
ExifImageHeight 3000
ExposureMode Auto
WhiteBalance Auto
DigitalZoomRatio 1
FocalLengthIn35mmFormat 23 mm
SceneCaptureType Standard
ImageUniqueID HK0XLQE00VM
GPSLatitudeRef North
GPSLatitude +59.9021866000
GPSLongitudeRef East
GPSLongitude +10.5365771000
GPSAltitudeRef Above Sea Level
GPSAltitude 80 m Above Sea Level
XPTitle Kristians example photo
XPAuthor Kristian Mella
XPSubject Testing metadata
Padding (Binary data 268 bytes, use -b option to extract)
Compression JPEG (old-style)
ThumbnailOffset 1772
ThumbnailLength 23080
ThumbnailImage (Binary data 23080 bytes, use -b option to extract)
ProfileCMMType
ProfileVersion 4.3.0
ProfileClass Display Device Profile
ColorSpaceData RGB
ProfileConnectionSpace XYZ
ProfileDateTime 2022:07:01 00:00:00+0000
ProfileFileSignature acsp
PrimaryPlatform Unknown (SEC)
CMMFlags Not Embedded, Independent
DeviceManufacturer Unknown (SEC)
DeviceModel
DeviceAttributes Reflective, Glossy, Positive, Color
RenderingIntent Perceptual
ConnectionSpaceIlluminant 0.9642 1 0.82491
ProfileCreator Unknown (SEC)
ProfileID 0
ProfileDescription DCI-P3 D65 Gamut with sRGB Transfer
ProfileCopyright Copyright (c) 2022 Samsung Electronics Co., Ltd.
MediaWhitePoint 0.9642 1 0.82491
ChromaticAdaptation 1.04781 0.02289 -0.05013 0.02954 0.99048 -0.01704 -0.00923 0.01505 0.75214
RedMatrixColumn 0.51508 0.24117 -0.00105
GreenMatrixColumn 0.29195 0.69223 0.04189
BlueMatrixColumn 0.15718 0.06659 0.78455
RedTRC (Binary data 32 bytes, use -b option to extract)
GreenTRC (Binary data 32 bytes, use -b option to extract)
BlueTRC (Binary data 32 bytes, use -b option to extract)
XMPToolkit Adobe XMP Core 5.1.2
Version 1
DirectoryItemSemantic Primary
DirectoryItemMime image/jpeg
DirectoryItemLength 40637
About uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b
Creator Kristian Mella
Title Kristians example photo
Description Kristians example photo
MPFVersion 0100
NumberOfImages 2
MPImageFlags (none)
MPImageFormat JPEG
MPImageType Undefined
MPImageLength 40637
MPImageStart 5404096
DependentImage1EntryNumber 0
DependentImage2EntryNumber 0
MPImage2 (Binary data 40637 bytes, use -b option to extract)
Aperture 1.7
ImageSize 4000x3000
Megapixels 12
ScaleFactor35efl 3.7
ShutterSpeed 1/100
SubSecCreateDate 2024:10:10 20:09:40+0000
SubSecDateTimeOriginal 2024:10:10 20:09:40+0200
SubSecModifyDate 2024:10:10 20:09:40+0200
CircleOfConfusion 0.008 mm
FOV 76.1 deg
FocalLength35efl 6.3 mm (35 mm equivalent: 23.0 mm)
GPSPosition +59.9021866000, +10.5365771000
HyperfocalDistance 2.84 m
LightValue 4.9
Debugging this issue is tricky because the results vary depending on how the UI is set up. It appears that enabling “Responsive image” affects the EXIF data displayed in the UI. The current challenge is to find a way to extract EXIF data while also storing the images in a compressed format. These two tasks don’t seem compatible at the moment. I can extract EXIF data from a file object if compression isn’t applied, but I haven’t found a way to copy that image to another file and compress it afterward. Given the high resolution of phone cameras today, I’m hesitant to store all photos at full size.
As a follow-up question; Apart from taking up more space, will storing the images in full-size have a performance impact as well, if I use Responsive Image when showing the image in the UI?
Responsive images make the image resize “runtime and on the fly” server side before the image is served to the client/browser. I.e. if the photo is stored with very high resolution (e.g. 50 Mpixels / 50 Mbytes) it may be resized to 0.6 Mbytes on small devices (not a precise number, just to illustrate). So the benefit of responsive images should be high for high-res photos.
The stored image itself is not tampered with, but if you e.g. use the Image UI Component with “Responsive image”, and the user right clicks the image in the client and downloads the image, then the metadata will be removed, since that image is compressed. However, if you use the “Open URL” action node, you will download the original file with its metadata intact.