5
votes

I am trying to achieve following. But, I have no idea as I am a beginner to firebase.

  1. User is trying to upload a post with image
  2. Image is selected from web browser
  3. When user clicks save button, I am uploading the selected image to firbase storage and getting the download URL.
  4. Inserting the post data with download url to firestore database.

Till this, I have completed. Now, I need to resize the uploaded image.

For that, I am trying to use Cloud Functions, whenever a new entry is added to firestore database, I am calling a Cloud fountion, which can access the download URL of the image, using this download URL, I need to resize image. Please help.

Please let me know, if there are having any better approach to achieve this. (I know there should be :P )

Edit 1

Thank you Frank for your reply.

I have the below cloud function, which will be called for every post is inserted. I am getting the download URL of the image from the eventSnapshot. I need to resize the image in that location. Please help.

exports.resizeImage = functions.firestore
.document('posts/{postId}')
.onCreate(event => {
        var eventSnapshot = event.data.data();
        //In this eventSnapshot I am getting the document, and I can get the download URL from the document
});

I have analyzed the example to create thumbnail, but for that, I need the
storage object, and it will be called only when the storage object is changed. But, I need to do the thumbnail creation when the onWrite called in firestore.

 exports.generateThumbnail = functions.storage.object().onChange((event) => {
  // File and directory paths.
  const filePath = event.data.name;
});

Please let me know, how to do the image resize operation, by detecting the onWrite in firestore and using downLoadURL.

4
It sounds like you're well on your way to building an app, but are having problems with a specific step (resizing of an image). In that case, it's best to focus your question to only that specific problem, show what you've tried. The best way to do this is by creating a minimal, complete, verifiable example, that reproduces the problem without us having to know the rest of your app. If you're struggling on how to get started with resizing the image, have a look at this sample: github.com/firebase/functions-samples/tree/master/…Frank van Puffelen
@FrankvanPuffelen Thank you very much for your comment, I have edited the question. Please help. Thank you in advance.John
@FrankvanPuffelen Your suggestion please....John
Did you ever find a solution that works? I'm using FirebaseDatabase instead of Firestore, but encountering the same issue. I have successfully generated a thumbnail, but getting the download URL stored inside my user data has proved to be a head scratcher.Eric Duffett

4 Answers

2
votes

I, too, have created a document in firestore that I'm using as the backend to a website. I, too, would like to resize the image (to fit on a card) and write the URL back to firestore. I found a solution that uses a different pattern from the standard examples that are triggered by uploading the image to storage.

Basically, I upload the image to storage and then write the URL into Firestore within my app page. I then trigger the function with Firestore's onCreate() trigger.

The function grabs 'image_name' from firestore and uses that to get the file reference in storage. Next, it follows the patterns of the generate-thumbnail firebase example.

The trick was then grabbing the signedUrl and writing it back to firestore in img_src.

In case anyone else finds this useful:

const functions = require('firebase-functions');
const gcs = require('@google-cloud/storage')({keyFilename: 'service-key.json'});
const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');

exports.createCard = functions.firestore
  .document('work_cards/{cardID}')
  .onCreate((snap, context) => {
    const newCardData = snap.data()
    const bucket = gcs.bucket('your-project-id.appspot.com')
    const file = bucket.file(newCardData.image_name)
    const tempFilePath = path.join(os.tmpdir(), file.name);
    const thumbFileName = `thumb_${file.name}`;
    const thumbFilePath = bucket.file(thumbFileName)
    const contentType = file.contentType;
    const metadata = {
      contentType: contentType
    };

    return bucket.file(file.name).download({
        destination: tempFilePath,
      })
      .then(() => {
        return spawn('convert', [tempFilePath, '-thumbnail', '250x250', tempFilePath]);
      })
      .then(() => {
        return bucket.upload(tempFilePath, {
          destination: thumbFilePath,
          metadata: metadata,
        })
      })
      .then(() => {
        return thumbFilePath.getSignedUrl({
        action: 'read',
        expires: '03-09-2491'
        })
      })
      .then(signedUrls => {
        const img_src = signedUrls[0]
        return snap.ref.set({img_src}, {merge: true});
      })
      .then(() => {
        bucket.file(file.name).delete()
        fs.unlinkSync(tempFilePath)
        return
      })
    });
1
votes

Rather than take the URL from Cloud Firestore, you can have Cloud Storage trigger the Cloud Function to resize the image. There is a great example of how to do this, on GitHub.

Firebase SDK for Cloud Functions Quickstart - Cloud Storage trigger

0
votes

I needed to achieve this too, but for me, using the Cloud Storage trigger wasn't a suitable solution as I didn't want to resize everything.

My scenario was that a user would upload a number of images, but they picked one as the thumbnail. I modified Birch's code as a callable function, where the file reference data was passed to it

exports.generateThumbnail = functions.https.onCall((data, context) => {
  const file = bucket.file(`/designs/${context.auth.uid}/${data.design}/${data.image}`)
  const tempFilePath = path.join(os.tmpdir(), `${data.image}`);
  const thumbFileName = `thumb_${data.image}`;
  const thumbFilePath = bucket.file(`/designs/${context.auth.uid}/${data.design}/${thumbFileName}`);

  return bucket.file(file.name).download({
       destination: tempFilePath,
     })
     .then(() => {
       return spawn('convert', [tempFilePath, '-trim','-resize', '190', tempFilePath]);
     })
     .then(() => {
       return bucket.upload(tempFilePath, {
         destination: thumbFilePath,
       })
     })
     .then(() => {
       fs.unlinkSync(tempFilePath)
       return
     })
})
0
votes

I made it by generating and setting thumbnail when our user create or update firestore object.

resizeImage.ts

import * as admin from "firebase-admin";
const mkdirp = require("mkdirp-promise");
import { spawn } from "child-process-promise";
import * as path from "path";
import * as os from "os";
import * as fs from "fs";

export default async function resizeImage(
    filePath: string,
    size: string
): Promise<string> {
    // File and directory paths.
    const fileDir = path.dirname(filePath);
    const fileName = path.basename(filePath);
    const thumbFilePath = path.normalize(
        path.join(fileDir, `thumb_${size}_${fileName}`)
    );
    const tempLocalFile = path.join(os.tmpdir(), filePath);
    const tempLocalDir = path.dirname(tempLocalFile);
    const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);

    // Cloud Storage files.
    const bucket = admin.storage().bucket();
    const file = bucket.file(filePath);
    const thumbFile = bucket.file(thumbFilePath);
    const metadata = {
        contentType: null
        // To enable Client-side caching you can set the Cache-Control headers here. Uncomment below.
        // 'Cache-Control': 'public,max-age=3600',
    };
    await file.getMetadata().then(data => {
        if (data && data[0] && data[0]["contentType"]) {
            metadata["contentType"] = data[0]["contentType"];
        }
    });

    // Create the temp directory where the storage file will be downloaded.
    await mkdirp(tempLocalDir);
    // Download file from bucket.
    await file.download({ destination: tempLocalFile });
    console.log("The file has been downloaded to", tempLocalFile);
    // Generate a thumbnail using ImageMagick.
    await spawn(
        "convert",
        [
            tempLocalFile,
            "-auto-orient",
            "-thumbnail",
            `${size}>`,
            tempLocalThumbFile
        ],
        { capture: ["stdout", "stderr"] }
    );
    console.log("Thumbnail created at", tempLocalThumbFile);
    // Uploading the Thumbnail.
    await bucket.upload(tempLocalThumbFile, {
        destination: thumbFilePath,
        metadata: metadata
    });
    console.log("Thumbnail uploaded to Storage at", thumbFilePath);
    // Once the image has been uploaded delete the local files to free up disk space.
    fs.unlinkSync(tempLocalFile);
    fs.unlinkSync(tempLocalThumbFile);
    return thumbFile
        .getSignedUrl({
            action: "read",
            expires: "03-01-2500"
        })
        .then((urls: string[]) => urls[0]);
}

resizeAvatar.ts

const functions = require("firebase-functions");
import { Change } from "firebase-functions";
import resizeImage from "./resizeImage";

async function resizeAvatar(snapshot: FirebaseFirestore.DocumentSnapshot) {
    const data = snapshot.data();
    const filePath = data && data.avatarPath;
    if (!data || !filePath || data.avatarUrlSmall) {
        return; // No avatar or already resized
    }
    const url = await resizeImage(filePath, "200x200");
    await snapshot.ref.set({ avatarUrlSmall: url }, { merge: true });
}

exports.resizeAvatarOnCreate = functions.firestore
    .document("users/{userID}")
    .onCreate(async (snapshot: FirebaseFirestore.DocumentSnapshot) => {
        await resizeAvatar(snapshot);
    });

exports.resizeAvatarOnUpdate = functions.firestore
    .document("users/{userID}")
    .onUpdate(async (change: Change<FirebaseFirestore.DocumentSnapshot>) => {
        await resizeAvatar(change.after);
    });