Search code examples
javascriptmongodbexpressmultercloudinary

Upload audio and image from the same form using Multer Storage Cloudinary


I have to create a route that uploads 1 audio file and 1 image (resized) to Cloudinary using Multer Storage Cloudinary, and save the url and name in my mongo database. I get the error "Invalid image file" when I try to upload the audio file (even if I delete transformation and add "mp3" in allowedFormats.

Cloudinary code:

const cloudinary = require('cloudinary').v2;
const { CloudinaryStorage } = require('multer-storage-cloudinary');

cloudinary.config({
    cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
    api_key: process.env.CLOUDINARY_KEY,
    api_secret: process.env.CLOUDINARY_SECRET
});

const storage = new CloudinaryStorage({
    cloudinary,
    params: {
        folder: 'nono',
        // transformation: [
        //     {width: 500, height: 500, crop: "fill"}
        // // ],
        // allowedFormats: ['jpeg', 'png', 'jpg', 'mp3']
    }
});

module.exports = { cloudinary, storage }

Route code:

router.route("/")
    .get(catchAsync(sounds.index));
    .post(isLoggedIn, upload.fields([{ name: 'sound[audio]', maxCount: 1 }, { name: 'sound[image]', maxCount: 1 }]), joiValidationSounds, catchAsync(sounds.newSound));

module.exports.newSound = async (req, res) => {
    const sound = new Sound(req.body.sound);
    console.log(req.files)
    sound.image.url = req.files["sound[image]"][0].path;
    sound.image.filename = req.files["sound[image]"][0].filename;
    sound.audio.url = req.files["sound[audio]"][0].path;
    sound.audio.filename = req.files["sound[audio]"][0].filename;
    sound.author = req.user._id;
    await sound.save();
    req.flash("success", "Se ha añadido un nuevo sonido.");
    res.redirect(`/sounds/categories/:category/${sound._id}`);
}

Form code:

<form action="/sounds" method="POST" novalidate class="validated-form" enctype="multipart/form-data">
        <div class="row mb-3">
            <label for="audio" class="col-sm-2 col-form-label">Audio:</label>
            <div class="col-sm-10">
                <input type="file" name="sound[audio]" id="audio">
            </div>
        </div>
        
        <div class="row mb-3">
            <label for="image" class="col-sm-2 col-form-label">Imagen:</label>
            <div class="col-sm-10">
                <input type="file" name="sound[image]" id="image">
            </div>
        </div>

        <button type="submit" class="btn btn-success">Crear</button>
        <button type="reset" class="btn btn-success">Limpiar</button>
    </form>

I can upload 2 images instead without problems. I also checked that Cloudinary supports mp3 files.

EDIT: I managed to upload the audio with resource_type: 'video', allowedFormats: ['mp3'], and 2 different storages. But now I get the error "Unexpected field" when I try to upload both.

New code:

const storageAudio = new CloudinaryStorage({
    cloudinary: cloudinary,
    params: {
        folder: 'nono',
        format: 'mp3',
        resource_type: 'video',
        allowedFormats: ['mp3'],
    }
});

const storageImage = new CloudinaryStorage({
    cloudinary: cloudinary,
    params: {
        folder: 'nono',
        transformation: [
            {width: 500, height: 500, crop: "fill"}
        ],
        format: 'jpg',
        resource_type: 'image',
        allowedFormats: ['jpeg', 'png', 'jpg']
    }
});

module.exports = { cloudinary, storageImage, storageAudio };
const multer = require("multer");
const { storageImage, storageAudio  } = require("../cloudinary");
const uploadAudio = multer({ storage: storageAudio });
const uploadImage = multer({ storage: storageImage });

router.route("/")
    .get(catchAsync(sounds.index))
    .post(isLoggedIn, 
        // uploadAudio.fields([{ name: 'sound[audio]', maxCount: 1 }]), uploadImage.fields([{ name: 'sound[image]', maxCount: 1 }]),
        uploadAudio.single("sound[audio]"), 
        uploadImage.single("sound[image]"),
        // upload.fields([{ name: 'sound[audio]', maxCount: 1 }, { name: 'sound[image]', maxCount: 1 }]), 
    joiValidationSounds, catchAsync(sounds.newSound)
    );

Solution

  • Finally, I found a solution (with one storage and one form) to upload both image and audio:

    1)Set the params to resource_type: 'auto', and allowedFormats to all the formats you will upload.

    const storage = new CloudinaryStorage({
        cloudinary,
        params: {
            folder: 'nono',
            resource_type: 'auto',
            allowedFormats: ['jpeg', 'png', 'jpg', 'mp3'],
            transformation: [
                {width: 500, height: 500, crop: "fill"}
            ]
        }
    });
    

    2)Use upload.fields

    upload.fields([{ name: 'sound[audio]', maxCount: 1 }, { name: 'sound[image]', maxCount: 1 }]),