commit 4ac138b4ab3d54155c5f7ea8d0307bab5ec3f280 Author: installgen2 Date: Sat Jun 22 05:47:25 2019 -0700 a diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c5cd293 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.{bat,cmd}] +end_of_line = crlf diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..a770ead --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends" : "@edenjs/eden" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c54ea1d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Automatically normalize line endings for all text-based files +# http://git-scm.com/docs/gitattributes#_end_of_line_conversion +* text=auto + +# For the following file types, normalize line endings to LF on +# checkin and prevent conversion to CRLF when they are checked out +# (this is required in order to prevent newline related issues like, +# for example, after the build script is run) +.* text eol=lf +*.css text eol=lf +*.html text eol=lf +*.jade text eol=lf +*.js text eol=lf +*.json text eol=lf +*.less text eol=lf +*.scss text eol=lf +*.md text eol=lf +*.sh text eol=lf +*.txt text eol=lf +*.xml text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25d22d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Debug log from npm +npm-debug.log + +# Error log from yarn +yarn-error.log + +# yarn lock - do work pls +yarn.lock + +# nodejs modules +node_modules/ + +# edenjs stuff +data/ +config.js +edenappconfig.js + +# IDE +.idea +.remote-sync.json + +# Log and Sentry +*.log +*.sentry + +# Redis +*.rdb + +# sqlite files +*.db +*.sdb +*.sqlite +*.db3 +*.s3db +*.sqlite3 +*.sl3 +*.db2 +*.s2db +*.sqlite2 +*.sl2 + +# Other junk +.*.swp +._* +.DS_Store +.hg +.npmrc +.lock-wscript +.svn +.wafpickle-* +config.gypi +CVS diff --git a/index.js b/index.js new file mode 100644 index 0000000..59b562d --- /dev/null +++ b/index.js @@ -0,0 +1,161 @@ +#!/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()); + } + }); + }); +} + +(async () => { + 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 tmp.dir({ prefix : 'mudl_', unsafeCleanup : true }); + + 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'].includes(filePath.ext)) return null; + + const rawImagePath = filesList.find((rawP) => { + const p = Path.parse(rawP); + return ['.png', '.jpg'].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!'); +})(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9e95e40 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "mudl", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "bluebird": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmp-promise": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.0.5.tgz", + "integrity": "sha512-hOabTz9Tp49wCozFwuJe5ISrOqkECm6kzw66XTP23DuzNU7QS/KiZq5LC9Y7QSy8f1rPSLy4bKaViP0OwGI1cA==", + "requires": { + "bluebird": "^3.5.0", + "tmp": "0.0.33" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6acbeeb --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "mudl", + "description": "nothing yet", + "version": "1.0.1", + "dependencies": { + "fs-extra": "^7.0.1", + "tmp-promise": "^1.0.5" + }, + "bin": { + "mudl" : "index.js" + }, + "engines": { + "node": ">= 8.0.0" + }, + "devDependencies": { + "eslint": "^5.16.0", + "@edenjs/eslint-config-eden": "^2.0.14", + "eslint-config-airbnb": "^17.1.0", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-jsx-a11y": "^6.2.1", + "eslint-plugin-react": "^7.12.4" + } +}