diff --git a/run/image-processing/README.md b/run/image-processing/README.md index ad145c4f90..2402703bbc 100644 --- a/run/image-processing/README.md +++ b/run/image-processing/README.md @@ -1,6 +1,6 @@ # Cloud Run Image Processing Sample -This sample service applies [Cloud Storage](https://cloud.google.com/storage/docs)-triggered image processing with [Cloud Vision API](https://cloud.google.com/vision/docs) analysis and ImageMagick transformation. +This sample service applies [Cloud Storage](https://cloud.google.com/storage/docs)-triggered image processing with [Cloud Vision API](https://cloud.google.com/vision/docs) analysis and sharp transformation. Use it with the [Image Processing with Cloud Run tutorial](http://cloud.google.com/run/docs/tutorials/image-processing). @@ -9,7 +9,7 @@ For more details on how to work with this sample read the [Google Cloud Run Node ## Dependencies * **express**: Web server framework -* **[gm](https://github.com/aheckmann/gm#readme)**: ImageMagick integration library. +* **[sharp](https://sharp.pixelplumbing.com/)**: High-performance Node.js image processing library. * **@google-cloud/storage**: Google Cloud Storage client library. * **@google-cloud/vision**: Cloud Vision API client library. diff --git a/run/image-processing/image.js b/run/image-processing/image.js index 13f940de90..1d44212153 100644 --- a/run/image-processing/image.js +++ b/run/image-processing/image.js @@ -15,9 +15,8 @@ 'use strict'; // [START cloudrun_imageproc_handler_setup] -const gm = require('gm').subClass({imageMagick: true}); -const fs = require('fs'); -const {promisify} = require('util'); +const fs = require('fs').promises; +const sharp = require('sharp'); const path = require('path'); const vision = require('@google-cloud/vision'); @@ -34,6 +33,13 @@ exports.blurOffensiveImages = async event => { // This event represents the triggering Cloud Storage object. const object = event; + if (object.bucket === BLURRED_BUCKET_NAME) { + console.log( + 'Event triggered by the blurred bucket; skip to avoid recursion' + ); + return; + } + const file = storage.bucket(object.bucket).file(object.name); const filePath = `gs://${object.bucket}/${object.name}`; @@ -61,9 +67,10 @@ exports.blurOffensiveImages = async event => { // [END cloudrun_imageproc_handler_analyze] // [START cloudrun_imageproc_handler_blur] -// Blurs the given file using ImageMagick, and uploads it to another bucket. +// Blurs the given file using sharp, and uploads it to another bucket. const blurImage = async (file, blurredBucketName) => { const tempLocalPath = `/tmp/${path.parse(file.name).base}`; + const tempLocalBlurredPath = `/tmp/blurred-${path.parse(file.name).base}`; // Download file from bucket. try { @@ -74,19 +81,14 @@ const blurImage = async (file, blurredBucketName) => { throw new Error(`File download failed: ${err}`); } - await new Promise((resolve, reject) => { - gm(tempLocalPath) - .blur(0, 16) - .write(tempLocalPath, (err, stdout) => { - if (err) { - console.error('Failed to blur image.', err); - reject(err); - } else { - console.log(`Blurred image: ${file.name}`); - resolve(stdout); - } - }); - }); + try { + await sharp(tempLocalPath).blur(16).toFile(tempLocalBlurredPath); + + console.log(`Blurred image: ${file.name}`); + } catch (err) { + console.error('Failed to blur image.', err); + throw err; + } // Upload result to a different bucket, to avoid re-triggering this function. const blurredBucket = storage.bucket(blurredBucketName); @@ -94,14 +96,16 @@ const blurImage = async (file, blurredBucketName) => { // Upload the Blurred image back into the bucket. const gcsPath = `gs://${blurredBucketName}/${file.name}`; try { - await blurredBucket.upload(tempLocalPath, {destination: file.name}); + await blurredBucket.upload(tempLocalBlurredPath, {destination: file.name}); console.log(`Uploaded blurred image to: ${gcsPath}`); } catch (err) { throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`); + } finally { + // Delete the temporary file. + await Promise.allSettled([ + fs.unlink(tempLocalPath), + fs.unlink(tempLocalBlurredPath), + ]); } - - // Delete the temporary file. - const unlink = promisify(fs.unlink); - return unlink(tempLocalPath); }; // [END cloudrun_imageproc_handler_blur] diff --git a/run/image-processing/package.json b/run/image-processing/package.json index d46a057c9b..8b062ebf5b 100644 --- a/run/image-processing/package.json +++ b/run/image-processing/package.json @@ -22,7 +22,7 @@ "@google-cloud/storage": "^7.0.0", "@google-cloud/vision": "^4.0.0", "express": "^4.16.4", - "gm": "^1.23.1" + "sharp": "^0.34.5" }, "devDependencies": { "c8": "^10.0.0",