Extracting MP3 Metadata and Saving Thumbnails Using Node.js

Mohammed shamseer pv
4 min readSep 24, 2024

--

In this tutorial, I’ll show you how to extract metadata from MP3 files, save album art thumbnails, and store all the metadata in a JSON file using Node.js. We’ll use the following tools:

  • Axios for handling HTTP requests.
  • Node-ID3 for reading MP3 metadata.
  • uuid for generating unique IDs.
  • fs for file system operations in Node.js.

Step 1: Setting Up the Project

Begin by creating a new Node.js project.

mkdir mp3-metadata-extractor
cd mp3-metadata-extractor
npm init -y

Next, install the necessary dependencies:

const axios = require('axios');
const ID3Reader = require('node-id3');
const fs = require('fs');
const path = require('path');
const { v4: uuidv4 } = require('uuid');

Step 3: Base URL and Thumbnail Folder

We’ll use a base URL to access MP3 files and create a folder to store album art thumbnails.

// Base URL for MP3 files
const baseURL = 'https://devshamseer.github.io/all-audio-songs/allsongs/sonng';

// Total number of songs
const totalSongs = 154;

// Folder for thumbnails
const thumbnailsFolderPath = path.join(__dirname, 'songs-thumbnail');
if (!fs.existsSync(thumbnailsFolderPath)) {
fs.mkdirSync(thumbnailsFolderPath);
}

Step 4: Metadata Extraction Function

This function streams the MP3 file, extracts metadata, and saves the album art.

// Function to get MP3 metadata and thumbnail from a network URL
async function getMP3MetadataAndThumbnail(mp3Url) {
try {
const response = await axios({ url: mp3Url, method: 'GET', responseType: 'arraybuffer' });
const tags = ID3Reader.read(response.data);

// Clean metadata fields
const title = cleanMetadataField(tags.title);
const artist = cleanMetadataField(tags.artist);
const album = cleanMetadataField(tags.album);

// Save album art if available
let thumbnailUrl = defaultThumbnailUrl;
if (tags.image) {
const imagePath = path.join(thumbnailsFolderPath, `${album}_${Date.now()}.jpg`);
fs.writeFileSync(imagePath, Buffer.from(tags.image.imageBuffer));
thumbnailUrl = 'https://yourdomain.com/thumbnails/' + path.basename(imagePath);
}

// Store metadata
allMetadata.push({ id: uuidv4(), title, artist, album, url: mp3Url, thumbnail: thumbnailUrl });
} catch (error) {
console.error('Error fetching metadata for', mp3Url, error.message);
}
}

Step 5: Processing Multiple MP3 Files

We’ll process all MP3 URLs and store the metadata in a JSON file.

// Function to process all MP3 files
async function processAllMP3s() {
const mp3Urls = [];

for (let i = 1; i <= totalSongs; i++) {
mp3Urls.push(`${baseURL}${i}.mp3`);
}

try {
await Promise.all(mp3Urls.map(url => getMP3MetadataAndThumbnail(url)));
} catch (error) {
console.error('Error processing MP3s', error);
}

// Write metadata to JSON
const metadataFilePath = path.join(__dirname, 'songs-thumbnail', 'all_metadata.json');
fs.writeFileSync(metadataFilePath, JSON.stringify(allMetadata, null, 2));
}

Step 6: Running the Script

To run the script, use:

node index.js

Full code

const axios = require('axios');
const ID3Reader = require('node-id3');
const fs = require('fs');
const path = require('path');
const { v4: uuidv4 } = require('uuid');

// Base URL for MP3 files
const baseURL = 'https://devshamseer.github.io/all-audio-songs/allsongs/sonng';

// Total number of songs
const totalSongs = 154; // Update this to match the total number of songs

// Folder for thumbnails
const thumbnailsFolderPath = path.join(__dirname, 'songs-thumbnail');
if (!fs.existsSync(thumbnailsFolderPath)) {
fs.mkdirSync(thumbnailsFolderPath);
}

// Array to store metadata
const allMetadata = [];

// Default thumbnail URL
const defaultThumbnailUrl = 'https://devshamseer.github.io/all-audio-songs/applogo/applogo1.png';

// Function to format album name
function formatAlbumName(albumName) {
return albumName.replace(/\s+/g, '_').toLowerCase();
}

// Function to clean metadata fields
function cleanMetadataField(field) {
return field ? field.replace(/from_.*?$/, '').trim() : 'Unknown';
}

// Function to get MP3 metadata and thumbnail from a network URL
async function getMP3MetadataAndThumbnail(mp3Url) {
try {
// Stream the MP3 file from the URL using Axios
const response = await axios({
url: mp3Url,
method: 'GET',
responseType: 'arraybuffer'
});

// Extract metadata from the buffer
const tags = ID3Reader.read(response.data);

// Clean metadata fields
const title = cleanMetadataField(tags.title);
const artist = cleanMetadataField(tags.artist);
const album = cleanMetadataField(tags.album);
const genre = cleanMetadataField(tags.genre);
const year = cleanMetadataField(tags.recordingTime);

// Log metadata to console
console.log('Processing URL:', mp3Url);
console.log('Song Title:', title);

// Extract and save album art
let thumbnailUrl = defaultThumbnailUrl;
if (tags.image) {
const albumFormatted = formatAlbumName(album || 'unknown_album');
const imagePath = path.join(thumbnailsFolderPath, `${albumFormatted}_${Date.now()}.${tags.image.mime.split('/')[1]}`);
fs.writeFileSync(imagePath, Buffer.from(tags.image.imageBuffer));
console.log('Album art saved to', imagePath);

thumbnailUrl = 'https://devshamseer.github.io/all-audio-songs/songs-thumbnail/' + path.basename(imagePath);
} else {
console.log('No album art found for', mp3Url);
}

// Collect metadata
allMetadata.push({
id: uuidv4(),
title: title,
artist: artist,
album: album,
genre: genre,
year: year,
url: mp3Url ,
thumbnail: thumbnailUrl
});

} catch (error) {
if (error.response && error.response.status === 404) {
console.log('Skipping URL due to 404:', mp3Url);
} else {
console.error('Error fetching metadata for', mp3Url, ':', error.message);
}
}
}

// Process all MP3 URLs concurrently
async function processAllMP3s() {
const mp3Urls = [];

// Create an array of MP3 URLs
for (let i = 1; i <= totalSongs; i++) {
mp3Urls.push(`${baseURL}${i}.mp3`);
}

// Use Promise.all to process all MP3 URLs in parallel
try {
await Promise.all(mp3Urls.map(mp3Url => getMP3MetadataAndThumbnail(mp3Url)));
} catch (error) {
console.error('Error processing all MP3s:', error.message);
}

// Write all metadata to a single JSON file
const metadataFilePath = path.join(__dirname, 'songs-thumbnail', 'all_metadata.json');
fs.writeFileSync(metadataFilePath, JSON.stringify(allMetadata, null, 2));

// Print count of saved songs
console.log('All metadata saved to', metadataFilePath);
console.log('Total saved songs count:', allMetadata.length);
}

// Start processing
processAllMP3s();

Once complete, the metadata (including album art URLs) will be saved in a JSON file.

--

--

Mohammed shamseer pv
Mohammed shamseer pv

Written by Mohammed shamseer pv

skilled in Flutter, Node.js, Python, and Arduino, passionate about AI and creating innovative solutions. Active in tech community projects.

No responses yet