Preserving EXIF Data in Appfarm: Is It Possible?

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?

Hi!

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:


In my case, all the important information like GPS position, SubjectDistance and OriginalDate was lost.

I’m currently using an approach where I use a coded component when photos are uploaded. I’m using this library: https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js

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.

Hi!

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 :wink:

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.