Different image dimensions | Cloud storage platform by Firebase

I'm in the process of developing functionality to generate thumbnails based on an uploaded file. However, I'm uncertain if my current approach is the most efficient way to accomplish this task. The section of code

if (fileName.startsWith(THUMB_PREFIX)) {
seems to be causing issues as it continuously produces thumbnails.

Is there a simpler method to create multiple thumbnails using this particular example? You can refer to the following link for more information: https://github.com/firebase/functions-samples/blob/master/generate-thumbnail/functions/index.js

exports.onFileChange = functions.storage.object()
    .onFinalize((object) => {
        const sizes = [200, 50];
        const timestamp = + new Date();

        sizes.forEach((size, index) => {

            // File and directory paths.
            const filePath = object.name;
            const contentType = object.contentType; // This is the image MIME type
            const fileDir = path.dirname(filePath);
            let fileName = path.basename(filePath);
            let newFileName = path.basename(filePath)
            let currentThumbURL = '';
            const filename = fileName.substr(0, fileName.indexOf('.'));
            let fileExtension = fileName.split('.').pop();

            fileName = filename + timestamp + '.' + fileExtension;

            const thumbFilePath = path.normalize(path.join(fileDir, `${THUMB_PREFIX}-${size}-${fileName}`));
            const tempLocalFile = path.join(os.tmpdir(), filePath);
            const tempLocalDir = path.dirname(tempLocalFile);
            const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);

            var folder = fileDir.substr(0, fileDir.indexOf('/'));

            if (folder !== 'profile') return null;

            if (!contentType.startsWith('image/')) {
                console.log('This is not a profile image.');
                return null;
            }

            // Exit if the image is already a thumbnail.
            if (fileName.startsWith(THUMB_PREFIX)) {
                console.log('Already a Thumbnail.');
                return null;
            }

            // Cloud Storage files
            const bucket = gcs.bucket(object.bucket);
            const file = bucket.file(filePath);
            const thumbFile = bucket.file(thumbFilePath);
            const metadata = {
                contentType: contentType,
            };

            // Create the temp directory where the storage file will be downloaded.
            return mkdirp(tempLocalDir).then(() => {
                // Download file from bucket.
                return file.download({ destination: tempLocalFile });
            }).then(() => {
                console.log('The file has been downloaded to', tempLocalFile);
                // Generate a thumbnail using ImageMagick.
                return spawn('convert', [tempLocalFile, '-thumbnail', `${size}x${size}>`, tempLocalThumbFile], { capture: ['stdout', 'stderr'] });
            }).then(() => {
                console.log('Thumbnail created at', tempLocalThumbFile);
                // Uploading the Thumbnail.
                return bucket.upload(tempLocalThumbFile, { destination: thumbFilePath, metadata: metadata });
            }).then(() => {
                console.log('Thumbnail uploaded to Storage at', thumbFilePath);
                // Once the image has been uploaded delete the local files to free up disk space.
                console.log('Delet tempLocalFile', tempLocalFile)
                console.log('Delete tepLocalThumbFile', tempLocalThumbFile)
                fs.unlinkSync(tempLocalFile);
                fs.unlinkSync(tempLocalThumbFile);
                // Get the Signed URLs for the thumbnail and original image.
                const config = {
                    action: 'read',
                    expires: '03-01-2500',
                };
                return Promise.all([
                    thumbFile.getSignedUrl(config),
                    file.getSignedUrl(config),
                ]);
            }).then((results) => {

                const thumbResult = results[0];
                const originalResult = results[1];
                const thumbFileUrl = thumbResult[0];
                const fileUrl = originalResult[0];

                // Add the URLs to the Database
                ...


            }).then(() => {

                console.log('Thumbnail URLs saved to database. Delete original uploaded image.')

            }

            ).catch(error => console.log(error));
        });
    });

Answer №1

Based on the comments provided earlier, it appears that the root cause of your issue lies in the fact that you are not verifying the correct filename using

fileName.startsWith(THUMB_PREFIX)
, given that your file names adhere to the pattern of userkey + -size- + timestamp.

However, there seems to be a flaw in your code as well: you are employing

sizes.forEach((size, index) => {})
which leads to triggering the Cloud Function code twice.

The initial Cloud Function code (as per Firebase official examples) concatenates multiple asynchronous tasks to generate the thumbnail. This code returns a single promise at the completion of the chain.

If your intention is to execute this code twice within the Cloud Function, you need to revise the promise chain so that only one promise is returned.


You can potentially implement something like the following. This is just an initial proposal outlining how the async operations could be duplicated for parallel processing. It's important to note that there is scope for optimizing the code to minimize redundancy and bear in mind that this version has not been tested at all!

return mkdirp(tempLocalDir)
  .then(() => {
    // Download files from bucket.
    const promises = [];
    promises.push(file_1.download({ destination: tempLocalFile_1 }));
    promises.push(file_2.download({ destination: tempLocalFile_2 }));
    return Promise.all(promises);
  })
  .then(() => {
    const promises = [];
    promises.push(
      spawn(
        'convert',
        [
          tempLocalFile,
          '-thumbnail',
          `${THUMB_MAX_WIDTH_1}x${THUMB_MAX_HEIGHT_1}>`,
          tempLocalThumbFile_1
        ],
        { capture: ['stdout', 'stderr'] }
      )
    );

    promises.push(
      spawn(
        'convert',
        [
          tempLocalFile,
          '-thumbnail',
          `${THUMB_MAX_WIDTH_2}x${THUMB_MAX_HEIGHT_2}>`,
          tempLocalThumbFile_2
        ],
        { capture: ['stdout', 'stderr'] }
      )
    );

    return Promise.all(promises);
  })
  .then(() => {
    //console.log('Thumbnail created at', tempLocalThumbFile);
    // Uploading the Thumbnail.

    const promises = [];
    promises.push(
      bucket.upload(tempLocalThumbFile_1, {
        destination: thumbFilePath_1,
        metadata: metadata
      })
    );
    promises.push(
      bucket.upload(tempLocalThumbFile_1, {
        destination: thumbFilePath_1,
        metadata: metadata
      })
    );
    return Promise.all(promises);
  })
  .then(() => {
    console.log('Thumbnail uploaded to Storage at', thumbFilePath);
    // After uploading the image, delete the local files to free up disk space.
    fs.unlinkSync(tempLocalFile_1);
    fs.unlinkSync(tempLocalThumbFile_1);
    fs.unlinkSync(tempLocalFile_2);
    fs.unlinkSync(tempLocalThumbFile_2);
    // Obtain Signed URLs for the thumbnail and original image.
    const config = {
      action: 'read',
      expires: '03-01-2500'
    };
    return Promise.all([
      thumbFile_1.getSignedUrl(config),
      file_1.getSignedUrl(config),
      thumbFile_2.getSignedUrl(config),
      file_2.getSignedUrl(config)
    ]);
  })
  .then(results => {....}}

Furthermore, I recommend watching the three videos on "JavaScript Promises" from the Firebase video series available at: https://firebase.google.com/docs/functions/video-series/

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Configuring Google Maps API (including charts) for maximum height of 100%

I am having trouble getting my map to display at 100% height using the Google Maps API. I've encountered similar issues in the past with the Google Charts API as well. From what I've gathered, it seems like setting the height of the html and bod ...

Executing Multiple Ajax Requests Concurrently with Single Callback in jQuery

I am experimenting with ways to improve page loading speed by breaking up all the database calls into separate scripts and executing them simultaneously using ajax. After doing some research, I discovered that jQuery's $.when().then() function could h ...

Pop-up windows, the modern day digital version of fortune cookies

Expressing my requirement might be a bit challenging, but I will do my best. My goal is to create a web application using ASP.Net with C#. This project calls for functionality similar to the Windows popup: When a user clicks on the favorite button in IE- ...

What is the best way to stylize a date using Bootstrap-datepicker?

While this topic is well-known, I have a slightly more complex question regarding it. I have gone through the documentation here. My goal is to display the date in French format (dd/mm/yyyy) and save the value in US format (yyyy-mm-dd). Additionally, I nee ...

Converting an Array of Arrays into a List using Mapping

I am dealing with an Array of responses, organized by the corresponding question, shown below: https://i.sstatic.net/s2R62.png sortedAnswers= [[Answer1, Answer2, Answer3, Answer4],[AnswerA, AnswerB, AnswerC, AnswerD]...] My goal is to display a list ...

Looking for a three.js resource to enhance my project

Looking for guidance on how to import a .obj 3D model into three.js. Any information or tutorials available? ...

The click event does not seem to be functioning properly within a contenteditable div

Incorporating a delete button within the div would allow users to delete it upon clicking. However, there are currently two issues: (1) The alignment of the delete button with the text in the div is not accurate. (2) The click event for the button is not ...

Utilizing arrow keys as a means of setting focus (HTML & JavaScript)

Is it possible to program the left arrow key to function like the tab button (moving focus to the next focusable item) and the right arrow key to function as a shift+tab (moving focus to the previous focusable item)? I've made some progress with the ...

Starting a tab just once

I have successfully created 4 static HTML pages that include: Home About Services Contact Each page contains a link that leads to another static page named myVideo.html, which plays a video about me in a new tab. The link is available on every page so ...

What is the most effective method for accurately tallying the number of matches in a Datalist using the Input value as a reference

I have set up a datalist element with an associated input element for autocompletion in modern browsers: HTML code <input type="search" id="search" list="autocomplete" /> <datalist id="autocomplete"> <option value="c++" /> < ...

Autocomplete component fails to trigger onChange event upon losing focus in Mui framework

When using a Mui Autocomplete with the properties of multiple and freeSolo, a situation arises where pressing Return triggers an onChange event. However, when tabbing out of the Autocomplete widget, the typed text remains without updating the state of the ...

Tips on how to retrieve a variable from an array in Vue JS

New to Javascript and Vue, I am exploring examples to grasp the fundamental concepts. <template> /*<p v-bind:class="['bold', 'italic', isValid ? 'valid' : 'invalid']">*/ <p v-bind:class= ...

Troubleshooting logic errors and repetitive functions in AngularJS

My code using AngularJS is experiencing a logic error. I have created a function that iterates through a JSON array and retrieves the weather conditions as strings, such as 'clear', 'cloudy', etc. The function then compares these string ...

Enhance the current bootstrap sidebar by making it collapsible

My project is built in bootstrap and features a top navigation bar along with a sidebar. I would like to make the sidebar collapsible, similar to this example: The only challenge is that the sidebar in my project is positioned on the left side of the pag ...

Attempting to activate cookies, however receiving a message indicating that cookies are not enabled

When trying to log in to a page using request in my node.js server, I set 'jar' to true like this: var request = require('request'); request = request.defaults({jar: true}); After that, I make a post request with the login details: r ...

What is the best method for managing a JSON javascript post when encountering a 500 error?

When it comes to writing a JSON block, there are various methods one can use. Personally, I prefer the following approach: $.post('/controller', { variable : variable }, function(data){ if(data.status == '304') { // No chan ...

Commence Animation upon Scrolling to the Top

I'm currently working on a project where I want a javascript animation to trigger as soon as the user scrolls into view of the specific section that contains all the elements related to the animation. To achieve this, I've implemented the followi ...

Picture is currently not displaying within a NextJS element

A question arises in my Firebase web app regarding the NextJS component below: import Link from "next/link"; import Image from 'next/image' import "./displayShop.css"; import telImg from '../images/Telephone.png'; ...

Loading web pages using ajax and handling relative paths

My method involves using ajax to load HTML files when links on my page are clicked. The process is simplified as follows: links.click(function () { var href = $(this).attr("href"); history.pushState(null, null, href); $.get($(this).attr("href" ...

Angular is throwing an error because it cannot find the definition for

Embarking on a tutorial journey to dive into Angular setup. Facing a persistent error in my code... Error: [$injector:undef] http://errors.angularjs.org/1.6.0/$injector/undef?p0=Bear Listing the order of files within the <head> tag in the html ...