Welcome folks today in this blog post we will be using the fluent-ffmpeg
library to change the pitch and speed
of mp3 audio file in browser using node.js
and express in javascript. All the full source code of the application is shown below.
Get Started
In order to get started you need to make a new node.js
project using the below command
npm init -y
npm i express
npm i fluent-ffmpeg
npm i multer
Now you will see the below directory
structure at the end of this app
As you can see you need to make the uploads
folder inside the public folder which will be the static
directory for this project. And inside the uploads directory we will store all the audio
files which will be uploaded by the user.
Dependencies
We will be using the following dependencies in our project:
express
– a Node.js web framework
multer
– middleware for handling file uploads
fluent-ffmpeg
– a Node.js wrapper for FFmpeg
Backend Code
Now guys we need to make the index.js
file inside the root directory and copy paste the following code
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
const express = require("express"); const multer = require("multer"); const ffmpeg = require("fluent-ffmpeg"); const path = require("path"); const app = express(); const port = 3000; // Set up file upload middleware const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "public/uploads/"); }, filename: function (req, file, cb) { cb(null, Date.now() + path.extname(file.originalname)); }, }); const upload = multer({ storage: storage }); // Set up static files middleware app.use(express.static("public")); // Handle file upload app.post("/upload", upload.single("file"), (req, res) => { const filePath = req.file.path; const fileName = req.file.originalname; // Set up FFmpeg command const command = ffmpeg(filePath); // Handle pitch change if (req.body.pitch) { const pitch = parseFloat(req.body.pitch); const speedAdjustment = 1 / Math.pow(2, pitch / 12); command.audioFilters([ { filter: "asetrate", options: `48000*${Math.pow(2, pitch / 12)}`, }, { filter: 'atempo', options: speedAdjustment.toFixed(2), } ]); } // Handle speed change if (req.body.speed) { const speed = parseFloat(req.body.speed); command.audioFilters(`atempo=${speed}`); } // Generate output file name const outputFile = uuidv4() + path.extname(fileName); // Save modified file command .on("error", (err) => { console.log("An error occurred: " + err.message); res.status(500).send("An error occurred"); }) .on("end", () => { res.download(path.join(__dirname, "public/uploads", outputFile)); }) .save(path.join(__dirname, "public/uploads", outputFile)); }); // Start server app.listen(port, () => { console.log(`App listening at http://localhost:${port}`); }); |
Here’s what this code does:
- We set up the file upload middleware using Multer.
- We set up the static files middleware using Express.
- We define a route for handling file uploads. When a file is uploaded, we use FFmpeg to modify the pitch and speed of the audio file, if the user has specified these parameters. We then save the modified file to the
uploads
directory and send it to the user for download.
- We start the server listening on port 3000.
Frontend Code
Now guys inside the public
folder we need to create the index.html
file and copy paste the following code
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Audio Pitch and Speed Changer</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 0; } h1 { margin: 0; padding: 1rem; text-align: center; background-color: #4CAF50; color: white; } form { margin: 1rem; padding: 1rem; background-color: #f2f2f2; border: 1px solid #ccc; border-radius: 5px; text-align: center; } .form-group { margin: 1rem; display: flex; flex-direction: column; justify-content: center; align-items: center; } label { margin-bottom: 0.5rem; font-weight: bold; } input[type="range"] { width: 90%; margin-bottom: 0.5rem; } button[type="submit"] { padding: 0.5rem; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; } #result { margin: 1rem; padding: 1rem; background-color: #f2f2f2; border: 1px solid #ccc; border-radius: 5px; text-align: center; } #result audio { width: 100%; } #download-button { padding: 0.5rem; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; } </style> </head> <body> <h1>Audio Pitch and Speed Changer</h1> <form id="upload-form" enctype="multipart/form-data"> <input type="file" name="file" required> <div class="form-group"> <label for="pitch-slider">Pitch:</label> <input type="range" name="pitch" id="pitch-slider" min="-2" max="10" step="0.1" value="1"> <span id="pitch-value">1</span> </div> <div class="form-group"> <label for="speed-slider">Speed:</label> <input type="range" name="speed" id="speed-slider" min="0.5" max="10" step="0.1" value="1"> <span id="speed-value">1</span> </div> <button type="submit">Upload and Modify</button> </form> <div id="result" style="display: none;"> <h2>Modified Audio:</h2> <audio controls></audio> <a id="download-button" href="#" download="modified-audio.mp3">Download</a> </div> <script src="js/main.js"></script> </body> </html> |
- We set the font family to Arial and set the margin and padding of the body to 0.
- We style the
h1
element to have a green background, white text, and no margin or padding.
- We style the
form
element to have a light gray background, a border, rounded corners, and some margin and padding.
- We style the
.form-group
class to display its child elements as a column with center alignment, and add some margin.
- We style the
label
element to have some margin and bold text.
- We style the
input[type="range"]
elements to have a width of 90% and some margin.
- We style the submit button to have a green background, white text, no border, rounded corners, and a pointer cursor.
- We style the
#result
element to have a light gray background, a border, rounded corners, and some margin and padding.
- We style the
audio
element inside#result
to have a width of 100%.
- We style the download button to have a green background, white text, no border, rounded corners, and a pointer cursor.
Now we need to add the client
side ajax
code which will actually make the fetch
request to the backend server to actually change the pitch and the speed of the uploaded
mp3 audio file as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
const form = document.getElementById("upload-form"); const pitchSlider = document.getElementById("pitch-slider"); const pitchValue = document.getElementById("pitch-value"); const speedSlider = document.getElementById("speed-slider"); const speedValue = document.getElementById("speed-value"); const result = document.getElementById("result"); const audio = result.querySelector("audio"); const downloadButton = result.querySelector("#download-button"); form.addEventListener("submit", (e) => { e.preventDefault(); const formData = new FormData(form); fetch("/upload", { method: "POST", body: formData, }) .then((res) => { if (!res.ok) { throw new Error("Network response was not ok"); } return res.blob(); }) .then((blob) => { result.style.display = "block"; audio.src = URL.createObjectURL(blob); downloadButton.href = audio.src; downloadButton.download = "modified-audio.mp3"; }) .catch((err) => { console.error("An error occurred: " + err.message); alert("An error occurred. Please try again later."); }); }); pitchSlider.addEventListener("input", (e) => { pitchValue.textContent = e.target.value; }); speedSlider.addEventListener("input", (e) => { speedValue.textContent = e.target.value; }); // Event listener for the download button downloadButton.addEventListener("click", () => { const audioSrc = audioPlayer.src; const fileName = downloadButton.getAttribute("download"); download(audioSrc, fileName); }); // Function to download the modified audio function download(src, fileName) { const link = document.createElement("a"); link.href = src; link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); } |
- We select the form, sliders, value elements, result element, audio element, and download button element from the DOM.
- We add an event listener to the form that prevents the default form submission behavior, creates a
FormData
object from the form data, and sends a POST request to the server usingfetch()
. When the response comes back, we display the result element, set thesrc
of the audio element to a URL created from the response blob, and set thehref
anddownload
attributes of the download button to the same URL and a file name respectively.