Skip to content

Instantly share code, notes, and snippets.

@onegentig
Created April 25, 2025 23:40
Show Gist options
  • Save onegentig/d5e0509de484db8a16e42fddc35e8742 to your computer and use it in GitHub Desktop.
Save onegentig/d5e0509de484db8a16e42fddc35e8742 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S deno run -RWE
/**
* A little utility that renames music files to the format:
* XX. AUTHOR - TITLE.ext
* Made for myself, esp. for Bandcamp albums.
* @author onegen <https://github.com/onegentig>
* @date 2025-04-26
* @license MIT
*/
import { exists } from 'jsr:@std/fs/exists';
import { basename, extname } from 'jsr:@std/path';
import { parseFile } from 'npm:music-metadata';
/**
* Normalises the name of the file to the format:
* XX. AUTHOR - TITLE.ext
* (retains extension, number is padded to 2 digits).
* @param {string} file Original filename
* @returns {Promise<string>} Normalised filename
*/
async function normaliseAudioFileName(file: string): Promise<string> {
if (!(await exists(file))) {
console.error(`File ${file} does not exist.`);
return file;
}
const base = basename(file);
const meta = await parseFile(file);
if (!meta || !meta.common) {
console.error(`Could not parse metadata for ${file}`);
return file;
}
const num = meta.common?.track?.no
? meta.common.track.no.toString().padStart(2, '0')
: '00';
const artist = meta.common.artist || '[no artist]';
const title = meta.common.title || '[no title]';
const ext = extname(file);
const newFileBase = `${num}. ${artist} - ${title}${ext}`;
const newFile = file.replace(base, newFileBase);
Deno.rename(file, newFile);
console.info(`Renamed ${file} to ${newFile}`);
return newFile;
}
/**
* Main function
* @returns {Promise<boolean>} True on success, false on failure.
*/
async function main() {
if (Deno.args.length < 1) {
console.error(
'Either supply a directory of filename(s) as an argument.'
);
return false;
}
const files: Array<string> = [];
for (const arg of Deno.args) {
const stat = await Deno.stat(arg);
if (stat.isDirectory) {
for await (const entry of Deno.readDir(arg)) {
if (entry.isFile) {
files.push(`${arg}/${entry.name}`);
}
}
} else if (stat.isFile) {
files.push(arg);
}
}
if (files.length === 0) {
console.error('No files found.');
return false;
}
for (const file of files)
try {
await normaliseAudioFileName(file);
} catch (e) {
console.error(`Error processing ${file}: ${e}`);
}
console.info('All files processed! Have a nice day!');
return true;
}
if (import.meta.main) {
if (await main()) {
Deno.exit(0);
} else {
Deno.exit(1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment