Search code examples
angularimagegoogle-cloud-functionsfirebase-storagesharp

Wrong MIME type after using sharp API with Firebase/Angular app


I am calling sharp(tmpFilePath).resize(150, 150).toFile(resizedFilePath) (using this sharp api: http://sharp.pixelplumbing.com/en/v0.15.1/api/) but the output file that's being produced is showing as type application/octet-stream rather than an image/jpeg, which is the original file type. I am using Angular and downloading/uploading to Firebase storage via cloud functions. When I download the source file and upload it right back to Firebase without calling the sharp API first, the newly uploaded file is an image/jpeg file as expected.

I was originally following this tutorial (https://angularfirebase.com/lessons/image-thumbnail-resizer-cloud-function/) but I actually couldn't access my storage bucket using his method, OR the one on Firebase docs: const gcs = require('@google-cloud/storage')(); but I was able to access it through the admin with const bucket = admin.storage().bucket(object.bucket);. It seems suspicious that I had to use this work-around, but again, my function works well if I leave out the sharp api call...so I just don't know that it's the root cause of my problem?

I submitted this issue on github (https://github.com/lovell/sharp/issues/1493) but the owner seems to think the issue is not related to sharp. Any idea what I've done wrong here? Or can anyone at least help me to narrow the problem down so I can try some better google searches?

Original file:
image

File returned by Sharp API:
image

My function from index.ts:

import * as functions from 'firebase-functions';

import { tmpdir } from 'os';
import { join, dirname } from 'path';

import * as sharp from 'sharp';
import * as fs from 'fs-extra';

const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

export const resizeImages = functions.storage.object().onFinalize(async object => { 
try {
    console.log(object);
    const bucket = admin.storage().bucket(object.bucket);
    const filePath = object.name;
    const fileName:string = filePath.split('/').pop();
    const bucketDir = dirname(filePath);
    console.log("filePath: " + filePath + "; fileName: " + fileName + "; bucketDir: " + bucketDir);

    if (bucketDir === 'profile-image' && object.contentType.includes('image')){
        console.log("A profile picture was added...")
        //The file is a profile picture...
        if (fileName.includes('unsized@')) {
            console.log("The picture that was added hasn't been sized yet...")
            //The file needs to be resized...
                const workingDir = join(tmpdir(), 'thumbs');
                const tmpFilePath = join(workingDir, fileName);

                // 1. Ensure thumbnail dir exists
                await fs.ensureDir(workingDir); 

                // 2. Download Source File
                await bucket.file(filePath).download({
                destination: tmpFilePath
                });
                console.log("Downloaded the source file");
                console.log(tmpFilePath);

                // 3. Resize the image  ********THIS PART IS NOT HAPPENING CORRECTLY
                console.log("About to start resizing...");
                const resizedfileName: string = fileName.split('@').pop();
                const resizedFilePath = join(workingDir, resizedfileName);
                await sharp(tmpFilePath).resize(150, 150).toFile(resizedFilePath);
                console.log("The image resizing is complete: "+ resizedFilePath);

                // 4. Upload the resized image
                const resizedImageUploadSnapshot = await bucket.upload(resizedFilePath, {destination: join('profile-image', resizedfileName)});
                console.log("Uploaded the resized image ");

                // 5. Cleanup remove the tmp/thumbs from the filesystem
                await fs.remove(workingDir);
                console.log("FUNCTION COMPLETED SUCCESSFULLY!")
                return true;
        } else {
            return false;
        }
    } else {     
        console.log('exiting function');
        return false;
    }

} catch (error) {
    console.log(error);
    return error;
}

My package.json file from the functions folder:

{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "lib/index.js",
  "dependencies": {
    "@google-cloud/storage": "^2.3.1",
    "firebase-admin": "~6.0.0",
    "firebase-functions": "^2.1.0",
    "firebase-tools": "^6.1.1",
    "fs-extra": "^7.0.1",
    "sharp": "^0.21.0",
    "stripe": "^6.15.1"
  },
  "devDependencies": {
    "tslint": "~5.8.0",
    "typescript": "^3.2.1"
  },
  "private": true
}

Solution

  • I solved this problem by adding a file extension to my paths, as follows:

    const tmpFilePath = join(workingDir, fileName + '.jpeg');  
    

    and

    const resizedFilePath = join(workingDir, resizedfileName + '.jpeg');