#!/usr/bin/env node /* eslint-disable no-console */ const fs = require('fs-extra'); const tmp = require('tmp-promise'); const os = require('os'); const shitExec = require('child_process').exec; const Path = require('path'); async function exec(...args) { return new Promise((resolve, reject) => { shitExec(...args, (error, stdout) => { if (error) { reject(error); } else { resolve(stdout.trim()); } }); }); } const TMP_DIR = `/tmp/mudl_${Math.random().toString(36).substring(7)}`; async function thing() { if (!process.argv[2]) { console.log('need a video'); return; } const [pAlbum, pArtist, pTitle] = (process.argv.length >= 3 ? process.argv.slice(3).join(' ').trim().match(/^([^/]+)(?:\/([^/]+)(?:\/([^/]+))?)?$/) || [] : []).slice(1).map((item) => { return item ? item.trim() : item; }); const dir = await fs.mkdir(TMP_DIR); let files = []; let inputType = null; if (['/', '~', '.'].includes(process.argv[2][0])) { console.log('got a str8 filename'); inputType = 'file'; files = [{ image: null, file: process.argv[2], }]; } else { console.log('got a link'); inputType = 'url'; console.log(`Temporarily saving to ${dir.path}`); await exec(`youtube-dl --write-thumbnail -f bestaudio -x -o "${dir.path}/[%(album)s] -- [%(artist)s] -- [%(track_number)s] -- [%(track)s] -- [%(title)s].%(ext)s" ${process.argv[2]}`); console.log('Done saving'); const filesList = await fs.readdir(dir.path); files = filesList.map((rawFilePath) => { const filePath = Path.parse(rawFilePath); if (['.png', '.jpg', '.webp'].includes(filePath.ext)) return null; const rawImagePath = filesList.find((rawP) => { const p = Path.parse(rawP); return ['.png', '.jpg', '.webp'].includes(p.ext) && p.name === filePath.name; }); let album = pAlbum || null; let artist = pArtist || null; let title = pTitle || null; let trackNum = null; let rawTitle = null; const fullMatch = rawFilePath.match(/^\[(.*?)\] -- \[(.*?)\] -- \[(.*?)\] -- \[(.*?)\] -- \[(.*?)\]\..*?$/); if (fullMatch !== null) { const [yAlbum, yArtist, yTrackNum, yTitle, yRawTitle] = fullMatch.slice(1) .map(item => (item === 'NA' ? null : item)) .map(item => (item !== null ? item.trim() : null)) .map(item => (item !== null ? item.replace(/(^|\s+)(.)_($|\s+)/g, '$1$2$3') : null)); if (yAlbum && !album) album = yAlbum; if (yArtist && !artist) artist = yArtist; if (yTitle && !title) title = yTitle; if (yTrackNum && !trackNum) trackNum = yTrackNum; if (yRawTitle && !rawTitle) rawTitle = yRawTitle; } if (artist === null && title === null) { const rawTitleMatch = rawTitle.match(/^([^-]+)(?:\s*-\s*(.+))?$/); if (rawTitleMatch[2] !== undefined && rawTitleMatch[2] !== null) { [artist, title] = rawTitleMatch.slice(1).map(item => (item ? item.trim() : item)); } else { title = rawTitleMatch[1].trim(); } } if (album === null) album = 'Unknown'; if (artist === null) artist = 'Unknown'; if (title === null) title = 'Unknown'; return { file: Path.join(dir.path, rawFilePath), image: Path.join(dir.path, rawImagePath), album, artist, title, }; }).filter(p => p !== null); } for (const { album, artist, title, trackNum, file, image, } of files) { const path = `${os.homedir()}/Music/${album}/${artist} - ${title}.mp3`; await fs.ensureDir(`${os.homedir()}/Music/${album}`); if (!(await fs.exists(path))) { console.log(`Encoding to: ${path}`); if (image) { await exec(`ffmpeg -i "${file}" -i "${image}" -map 0:0 -map 1:0 -id3v2_version 4 -metadata:s:v title="Album cover" -metadata:s:v comment="Cover (front)" "${path}"`); } else { await exec(`ffmpeg -i "${file}" -id3v2_version 4 "${path}"`); } } console.log('Adding id3v2 tags'); const tagArgs = []; tagArgs.push(['--artist', artist || 'Unknown']); tagArgs.push(['--album', album || 'Unknown']); tagArgs.push(['--song', title || 'Unknown']); if (inputType === 'url') tagArgs.push(['--comment', process.argv[2]]); if (trackNum) tagArgs.push(['--track', trackNum]); const tagArgsString = tagArgs.map(([tag, val]) => `${tag} "${val}"`).join(' '); await exec(`id3v2 ${tagArgsString} "${path}"`); if (await fs.exists(Path.join(os.homedir(), '.tmsu'))) { console.log('adding tmsu tags'); const fsTagArgs = []; if (artist) fsTagArgs.push(['artist', artist]); if (album) fsTagArgs.push(['album', album]); if (fsTagArgs.length > 0) { const fsTagArgsString = fsTagArgs.map(([tag, val]) => `"${tag}=${val}"`).join(' '); await exec(`tmsu tag "${path}" ${fsTagArgsString}`); } } console.log(`Finished: ${album}/${artist} - ${title}`); } console.log('Finished everything, cleaning up...'); await dir.cleanup(); console.log('Done!'); } (async () => { try { await thing(); } catch (err) { console.error(err); await fs.rmdir(TMP_DIR, { recursive: true }); } })();