Airtable image compression

Hi all,

Anyone have any tips for compressing image uploads from Softr into Airtable? My website loads slowly and also concerned about airtable limits.

I’ve seen a couple of online tutorials using 3rd parties but wondering if anyone has experience and advice.

Thanks,
Josh

I don’t know if it can help you, but miniExtensions have a solution for that:

Bulk Image Compression on Airtable - miniExtensions for Airtable

Hey @JoshR,

This one should work as well https://tinypng.com/ :blush:

I just compressed all my images in Airtable for my project climesumer.com and used the script from this tutorial: How to Compress Airtable Attachments with TinyPNG

 
// Modify these to your own values.
let tinyPngApiKey = 'TINYPNG API KEY';
let airtableAttachmentSource = 'image';
let airtableAttachmentDestination = 'image';
let airtableColumnToLog = 'name';

// Don't change these unless you know what you're doing.
let table = base.getTable(cursor.activeTableId);
let view = table.getView(cursor.activeViewId);
let queryResult = await view.selectRecordsAsync();

for (let record of queryResult.records) {
  let attachments = record.getCellValue(airtableAttachmentSource);
  let compressedImageUrls = [];

  if (attachments && attachments.length > 0) {
    // Iterate through each attachment in the field.
    for (let [i, attachment] of attachments.entries()) {
      let recordAttachmentUrl = attachment['url'];

      console.log(`Compressing ${record.getCellValue(airtableColumnToLog)} (Image ${i + 1})`);

      let request = await remoteFetchAsync('https://api.tinify.com/shrink', {
        body: JSON.stringify({'source': {'url': recordAttachmentUrl}}),
        headers: {
          Authorization: 'Basic ' + btoa('api:' + tinyPngApiKey),
          'Content-Type': 'application/json'
        },
        method: 'POST'
      })

      const json = await request.json();

      // Checks that the API didn't fail.
      if (request.status == 201) {
        let percentReduced = Math.round((1 - json.output.ratio) * 100);
        let kbReduced = (json.input.size - json.output.size) / 1024;

        console.log('Panda just saved you ' + percentReduced + '% (' + Math.round(kbReduced) + 'KB).');

        // Add the compressed image URL to the array.
        compressedImageUrls.push({ url: json['output']['url'] });
      }
    }

    // Update the record with all the compressed image URLs.
    await table.updateRecordAsync(record.id, {
      [airtableAttachmentDestination]: compressedImageUrls,
    });
  }
}

I also modified this script to work in an Airtable automation for one single record only.
You need to make sure though that the automation doesn’t loop itself. I did it with two automations that update a helper field after to stop the loop.

// Modify these to your own values.
let tinyPngApiKey = '6dr6PF1tvsvJ6RP9q4Zbt55NK1RF9cNK';

//Table settings:
let airtableAttachmentSource = 'image';
let airtableAttachmentDestination = 'image';
let airtableColumnToLog = 'name';

// SCRIPT SETTINGS add Input variables on the left
const inputConfig = input.config();
let recordId = inputConfig.recordId;
let tableName = inputConfig.tableName;

// Custom btoa function
function btoa(input) {
    let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
    let str = String(input);
    let output = '';

    for (let block = 0, charCode, i = 0, map = chars;
        str.charAt(i | 0) || (map = '=', i % 1);
        output += map.charAt(63 & block >> 8 - i % 1 * 8)) {

        charCode = str.charCodeAt(i += 3/4);

        if (charCode > 0xFF) {
            throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
        }

        block = block << 8 | charCode;
    }

    return output;
}



if (!recordId || !tableName) {
    throw new Error("Missing recordId or tableName in input config.");
}

let table = base.getTable(tableName);

// Fetch the specific record by its ID.
let record = await table.selectRecordAsync(recordId);

if (record) {
    let attachments = record.getCellValue(airtableAttachmentSource);
    let compressedImageUrls = [];

    if (attachments && attachments.length > 0) {
        // Iterate through each attachment in the field.
        for (let [i, attachment] of attachments.entries()) {
            let recordAttachmentUrl = attachment['url'];

            console.log(`Compressing ${record.getCellValue(airtableColumnToLog)} (Image ${i + 1})`);

            let request = {
                body: JSON.stringify({'source': {'url': recordAttachmentUrl}}),
                headers: {
                    Authorization: 'Basic ' + btoa('api:' + tinyPngApiKey),
                    'Content-Type': 'application/json'
                },
                method: 'POST'
            };

            try {
                let response = await fetch('https://api.tinify.com/shrink', request);
                const json = await response.json();

                // Checks that the API didn't fail.
                if (response.status == 201) {
                    let percentReduced = Math.round((1 - json.output.ratio) * 100);
                    let kbReduced = (json.input.size - json.output.size) / 1024;

                    console.log('Panda just saved you ' + percentReduced + '% (' + Math.round(kbReduced) + 'KB).');

                    // Add the compressed image URL to the array.
                    compressedImageUrls.push({ url: json['output']['url'] });
                }
            } catch (error) {
                console.error('Error during fetch:', error);
            }
        }

        // Update the record with all the compressed image URLs.
        await table.updateRecordAsync(record.id, {
            [airtableAttachmentDestination]: compressedImageUrls,
        });
    }
} else {
    console.log(`Record with ID ${recordId} not found.`);
}