Last active
June 8, 2019 01:30
-
-
Save nicklasfrahm/c6000b1a471ddda02da69ecff1bd6b7f to your computer and use it in GitHub Desktop.
ID3 music tags
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { promisify } = require('util'); | |
const fs = require('fs'); | |
const path = require('path'); | |
const jimp = require('jimp'); | |
const mm = require('music-metadata'); | |
const Id3Writer = require('browser-id3-writer'); | |
const start = Date.now(); | |
const writeFile = promisify(fs.writeFile); | |
const readFile = promisify(fs.readFile); | |
const readdir = promisify(fs.readdir); | |
const getMp3Files = async dir => { | |
const allFiles = await readdir(dir); | |
return allFiles.filter(file => /\.mp3$/.test(file)); | |
}; | |
const resolveFeat = rawArtistsOrTitle => { | |
const trim = str => str.trim(); | |
const truthy = item => !!item; | |
const [artistsOrTitle, rawFeatArtists] = rawArtistsOrTitle | |
.split(/ fe?a?t\. /g) | |
.map(trim) | |
.filter(truthy); | |
const [artistOrTitle, ...otherArtists] = artistsOrTitle | |
? artistsOrTitle | |
.split('&') | |
.map(trim) | |
.filter(truthy) | |
: []; | |
const featArtists = rawFeatArtists | |
? rawFeatArtists | |
.split('&') | |
.map(trim) | |
.filter(truthy) | |
: []; | |
return [artistOrTitle, [...otherArtists, ...featArtists]]; | |
}; | |
const updateId3Tags = async (fileName, fileContent) => { | |
// Load data into parsers. | |
const writer = new Id3Writer(fileContent); | |
const metadata = await mm.parseBuffer(fileContent); | |
// Update thumbnail to have square shape. | |
const oldThumbnail = metadata.common.picture[0].data; | |
const img = await jimp.read(oldThumbnail); | |
const newThumbnail = await img.cover(512, 512).getBufferAsync(jimp.MIME_JPEG); | |
writer.setFrame('APIC', { | |
type: 3, | |
description: 'Album cover', | |
data: newThumbnail | |
}); | |
// Parse song title and | |
const artists = []; | |
const [rawArtists, rawTitleWithId] = fileName.split(' - ', 2); | |
const [mainArtist, featArtists] = resolveFeat(rawArtists); | |
artists.push(mainArtist, ...featArtists); | |
const [rawTitleWithRemix] = rawTitleWithId.split('-', 1); | |
const [rawTitle, rawRemix] = rawTitleWithRemix.split(' (', 2); | |
const [title, featArtistsFromTitle] = resolveFeat(rawTitle); | |
artists.push(...featArtistsFromTitle); | |
// Set song artists. | |
writer.setFrame('TPE1', artists); | |
// Set song title. | |
writer.setFrame('TIT2', rawRemix ? `${title} (${rawRemix.trim()}` : title); | |
// Set album artist. | |
writer.setFrame('TPE2', artists[0]); | |
// Set album title. | |
writer.setFrame('TALB', rawRemix ? `${title} (${rawRemix.trim()}` : title); | |
// Add tag to MP3 buffer. | |
writer.addTag(); | |
return Buffer.from(writer.arrayBuffer); | |
}; | |
(async () => { | |
let counter = 0; | |
const directory = path.resolve(process.argv[2] || '.'); | |
process.stdout.write(`Scanning directory: ${directory}\n`); | |
const mp3FileNames = await getMp3Files(directory); | |
const total = mp3FileNames.length; | |
const digits = total.toString().length; | |
const promises = mp3FileNames.map(async mp3FileName => { | |
// Normalize path. | |
const normalizedPath = path.resolve( | |
path.normalize(path.join(directory, mp3FileName)) | |
); | |
// Read MP3 file from disk. | |
const mp3File = await readFile(normalizedPath); | |
// Update ID3 tags. | |
const updatedMp3File = await updateId3Tags(mp3FileName, mp3File); | |
// Write file to disk. | |
await writeFile(normalizedPath, updatedMp3File); | |
// Update progress. | |
const current = (++counter).toString().padStart(digits, ' '); | |
process.stdout.write(`\rConverting ID3 tags: ${current}/${total}`); | |
}); | |
await Promise.all(promises); | |
if (total) { | |
process.stdout.write('\n'); | |
} | |
process.stdout.write(`Processed ${total} in ${Date.now() - start}ms.\n`); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment