[SOLVED 2/28/24 10:10 PM EDT] Get Duration of Audio File

Thank you, I was finally able to get it to work properly after many many different attempts. If anyone ever wants to return an audio files duration and then delete the file after using this method(im sure there are better methods out there) then here is how I did it:

  1. Download Node.js
  2. Create a Node.js project in the terminal and create a package.json file by running the following command:
npm init
  1. create a .js file inside a subdirectory of your node js project(ex: myproj/api/duration.js)
  2. Paste the following code into the file:
const axios = require('axios');
const fs = require('fs').promises;
const path = require('path');
const os = require('os');
const mm = require('music-metadata');
const cloudinary = require('cloudinary').v2;

export default async function handler(req, res) {
  try {
    // Log the entire request object
    console.log('Received request:', req);

    // Handle CORS
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST');

    if (req.method === 'OPTIONS') {
      // Pre-flight request, respond successfully
      console.log('Pre-flight request received');
      res.status(200).end();
      return;
    }

    // Parse the request body as JSON
    let requestBody;
    try {
      requestBody = JSON.parse(req.body);
    } catch (error) {
      console.error('Error parsing request body as JSON:', error);
      res.status(400).json({ error: 'Error parsing request body as JSON' });
      return;
    }

    // Log the entire request body
    console.log('Request body:', requestBody);

    // Check if the request has a body
    if (!requestBody || typeof requestBody !== 'object' || !requestBody.fileLink) {
      console.error('Invalid request. No fileLink found in the request body.');
      res.status(400).json({ error: 'Invalid request. No fileLink found in the request body.' });
      return;
    }

    // Get the file link from the request body
    const { fileLink } = requestBody;

    console.log('File link:', fileLink);

    // Extract public ID from fileLink using a regular expression
    const publicIdMatch = fileLink.match(/\/([^\/]+)\.[a-zA-Z0-9]+$/);
    const publicId = publicIdMatch ? publicIdMatch[1] : null;

    if (!publicId) {
      console.error('Unable to extract public ID from fileLink.');
      res.status(400).json({ error: 'Unable to extract public ID from fileLink.' });
      return;
    }

    console.log('Public ID:', publicId);
  
    const fileTypeMatch = fileLink.match(/\.(mp3|mp4|mov|avi|wmv|flv)$/i);
    const resourceType = fileTypeMatch ? 'video' : 'image'; // Assume 'image' if not audio/video

    console.log('Resource Type:', resourceType);

    // Download the file using axios
    const response = await axios.get(fileLink, { responseType: 'arraybuffer' });
    const fileBuffer = Buffer.from(response.data);

    // Write the buffer to a temporary file
    const tempFilePath = path.join(os.tmpdir(), 'temp_audio_file.mp3');
    await fs.writeFile(tempFilePath, fileBuffer);

    // Get Duration using music-metadata on the temporary file
    const metadata = await mm.parseFile(tempFilePath, { duration: true });
    const durationInSeconds = metadata.format.duration;

    // Delete the temporary file after processing
    await fs.unlink(tempFilePath);

    // Delete the file from Cloudinary
    const options = {
      resource_type: resourceType, // Change to 'audio' if using audio files
      invalidate: true,
      api_key: 'your_cloudinary_api_key',  // Directly pass the API key here
      cloud_name: 'your_cloudinary_cloud_name'  // Replace with your actual Cloudinary cloud name
    };

    // Generate Cloudinary signature
    const timestamp = Math.floor(Date.now() / 1000);
    const signature = cloudinary.utils.api_sign_request(
      { public_id: publicId, timestamp: timestamp, invalidate: true },
      'your_cloudinary_api_secret'
    );

    // Make the Cloudinary request
    const cloudinaryResponse = await cloudinary.uploader.destroy(publicId, { ...options, public_id: publicId, timestamp, signature });
    console.log('Cloudinary response:', cloudinaryResponse);
    console.log('API Key:', options.api_key);

    res.status(200).json({ duration: durationInSeconds });
  } catch (error) {
    console.error('Server error:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
}
  1. Create a Vercel account, then install vercel in the Node.js terminal
    npm install -g vercel
  1. run vercel by doing the following command in the terminal
vercel
  1. You will need to modify your package.json file in your main directory to the following:
{
  "name": "your Vercel project name",
  "version": "1.0.0",
  "description": "",
  "main": "duration.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.24.0",
    "cloudinary": "^2.0.1",
    "music-metadata": "^5.4.3"
  }
}

  1. after you setup the vercel project, you want to type
vercel --prod

when this is done you want to login to vercel on your web browser and click on your project, you should see a domain that is similar to: projectname.vercel.app

  1. Now you want to setup the blocks in Thunkable, create a new webapi component and set the url to the “domain” plus the duration.js file directory. Ex: https://yourproject.vercel.app/api/duration.js. Then you want to setup the post as follows:

After all that(assuming I didn’t miss any steps, I did do alot of trial and error while figuring this out so I made this tutorial off of memory, I will edit it accordingly as I see fit.) it should return something like this: “{“duration”:duration in seconds}”

Again this is my first ever step by step tutorial for thunkable so please let me know if I missed any steps or if anyone is having any issues, I spent over 10 hours trying to figure this out.

1 Like