diff --git a/README.md b/README.md index 5574baff5..aa53288b9 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,13 @@ Demonstrates how to automatically convert images that are uploaded to Firebase S Uses a Firebase Storage trigger. -### [Extract Image MEtadata](/exif-images) +### [Automatically Moderate Images](/convert-images) + +Demonstrates how to automatically moderate offensive images that are uploaded to Firebase Storage by using the Google Cloud API to detect offensive images and ImageMagick to blur these images. + +Uses a Firebase Storage trigger. + +### [Extract Image Metadata](/exif-images) Demonstrates how to automatically extract image's metadata using ImageMagick for images that are uploaded to Firebase Storage. diff --git a/moderate-images/README.md b/moderate-images/README.md new file mode 100644 index 000000000..cf1b57330 --- /dev/null +++ b/moderate-images/README.md @@ -0,0 +1,36 @@ +# Automatically Moderate Images + +This sample demonstrates how to automatically moderate offensive images uploaded to Firebase Storage. It uses The Google Cloud Vision API to detect if the image contains adult or violent content and if so uses ImageMagick to blur the image. + + +## Functions Code + +See file [functions/index.js](functions/index.js) for the moderation code. + +The detection of adult and violent content in an image is done using The [Google Cloud Vision API](https://cloud.google.com/vision/). +The image blurring is performed using ImageMagick which is installed by default on all Firebase Functions instances. The image is first downloaded locally from the Firebase Storage bucket to the `tmp` folder using the [google-cloud](https://github.com/GoogleCloudPlatform/google-cloud-node) SDK. + +The dependencies are listed in [functions/package.json](functions/package.json). + + +## Trigger rules + +The function triggers on upload of any file to the Firebase Functions bucket. + + +## Setting up the sample + +Create a Firebase project on the [Firebase Console](https://console.firebase.google.com). +Enable Billing on your project by switching to the Blaze or Candle plan then visit the **Storage** tab. + +Replace the placeholder `FIREBASE_STORAGE_BUCKET_NAME` with the name of the Firebase Storage bucket which can be found in the **Storage** tab of your Firebase project's console. It is typically of the form `.appspot.com`. + +In your [Google Cloud Console](https://console.cloud.google.com/apis/api/vision.googleapis.com/overview?project=_) enable the **Google Cloud Vision API**. + + +## Deploy and test + +To test the sample: + + - Deploy your project using `firebase deploy` + - Go to the Firebase Console **Storage** tab and upload an image that contains adult or violent content. After a short time the image will be replaced by a blurred version of itself. diff --git a/moderate-images/firebase.json b/moderate-images/firebase.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/moderate-images/firebase.json @@ -0,0 +1 @@ +{} diff --git a/moderate-images/functions/index.js b/moderate-images/functions/index.js new file mode 100644 index 000000000..ca0bab5ba --- /dev/null +++ b/moderate-images/functions/index.js @@ -0,0 +1,81 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for t`he specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const functions = require('firebase-functions'); +const mkdirp = require('mkdirp-promise'); +const gcs = require('@google-cloud/storage')(); +const vision = require('@google-cloud/vision')(); +const exec = require('child-process-promise').exec; +// TODO(DEVELOPER): Replace the placeholder below with the name of the Firebase Storage bucket. +const FIREBASE_STORAGE_BUCKET_NAME = 'FIREBASE_STORAGE_BUCKET_NAME'; +const LOCAL_TMP_FOLDER = '/tmp'; + +/** + * When an image is uploaded we check if it is flagged as Adult or Violence by the Cloud Vision + * API and if it is we blur it using ImageMagick. + */ +exports.blurOffensiveImages = functions.cloud.storage(FIREBASE_STORAGE_BUCKET_NAME).onChange(event => { + const file = gcs.bucket(event.data.bucket).file(event.data.name); + + // Exit if this is a move or deletion event. + if (event.data.resourceState === 'not_exists') { + return console.log('This is a deletion event.'); + } + + // Check the image content using the Cloud Vision API. + return vision.detectSafeSearch(file).then(data => { + const safeSearch = data[0]; + console.log('SafeSearch results on image', safeSearch); + + if (safeSearch.adult || safeSearch.violence) { + return blurImage(event.data.name, event.data.bucket, event.data.metadata); + } + }); +}); + +/** + * Blurs the given image located in the given bucket using ImageMagick. + */ +function blurImage(filePath, bucketName, metadata) { + const filePathSplit = filePath.split('/'); + filePathSplit.pop(); + const fileDir = filePathSplit.join('/'); + const tempLocalDir = `${LOCAL_TMP_FOLDER}/${fileDir}`; + const tempLocalFile = `${LOCAL_TMP_FOLDER}/${filePath}`; + + // Create the temp directory where the storage file will be downloaded. + return mkdirp(tempLocalDir).then(() => { + // Download file from bucket. + const bucket = gcs.bucket(bucketName); + return bucket.file(filePath).download({ + destination: tempLocalFile + }).then(() => { + console.log('The file has been downloaded to', tempLocalFile); + // Blur the image using ImageMagick. + return exec(`convert ${tempLocalFile} -channel RGBA -blur 0x8 ${tempLocalFile}`).then(() => { + console.log('Blurred image created at', tempLocalFile); + // Uploading the Blurred image. + return bucket.upload(tempLocalFile, { + destination: filePath, + metadata: {metadata: metadata} // Keeping custom metadata. + }).then(() => { + console.log('Blurred image uploaded to Storage at', filePath); + }); + }); + }); + }); +} diff --git a/moderate-images/functions/package.json b/moderate-images/functions/package.json new file mode 100644 index 000000000..539e1cddc --- /dev/null +++ b/moderate-images/functions/package.json @@ -0,0 +1,13 @@ +{ + "name": "moderate-image-function", + "description": "Offensive Image blurring using Firebase Function.", + "dependencies": { + "@google-cloud/storage": "^0.4.0", + "@google-cloud/vision": "^0.5.0", + "child-process-promise": "^2.2.0", + "firebase": "^3.6.0", + "firebase-functions": "https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-preview.latest.tar.gz", + "mkdirp": "^0.5.1", + "mkdirp-promise": "^4.0.0" + } +}