musescore-downloader/src/cli.ts

158 lines
4.4 KiB
TypeScript
Raw Normal View History

2020-11-24 22:43:07 +00:00
/* eslint-disable @typescript-eslint/no-var-requires */
2020-11-24 22:32:44 +00:00
/* eslint-disable no-void */
import fs from 'fs'
2020-11-26 20:07:34 +00:00
import path from 'path'
2020-11-26 21:40:25 +00:00
import { fetchMscz, setMscz, MSCZ_URL_SYM } from './mscz'
2020-11-24 22:32:44 +00:00
import { loadMscore, INDV_DOWNLOADS, WebMscore } from './mscore'
2020-11-26 21:40:25 +00:00
import { ScoreInfo, ScoreInfoHtml, ScoreInfoObj } from './scoreinfo'
2020-11-24 22:35:44 +00:00
import { escapeFilename } from './utils'
2020-11-24 22:32:44 +00:00
import i18n from './i18n'
2020-11-24 22:43:07 +00:00
const inquirer: typeof import('inquirer') = require('inquirer')
const ora: typeof import('ora') = require('ora')
const chalk: typeof import('chalk') = require('chalk')
2020-11-24 22:32:44 +00:00
const SCORE_URL_PREFIX = 'https://musescore.com/'
2020-11-26 21:40:25 +00:00
const EXT = '.mscz'
2020-11-24 22:32:44 +00:00
2020-11-26 19:58:03 +00:00
interface Params {
fileInit: string;
2020-11-26 19:58:03 +00:00
confirmed: boolean;
part: number;
types: number[];
dest: string;
}
2020-11-24 22:32:44 +00:00
void (async () => {
2020-11-26 21:40:25 +00:00
let scoreinfo: ScoreInfo
// ask for the page url or path to local file
const { fileInit } = await inquirer.prompt<Params>({
type: 'input',
name: 'fileInit',
message: 'Score URL or path to local MSCZ file:',
suffix: `\n (starts with "${SCORE_URL_PREFIX}" or local filepath ends with "${EXT}")\n `,
validate (input: string) {
return input &&
(
input.startsWith(SCORE_URL_PREFIX) ||
(input.endsWith(EXT) && fs.statSync(input).isFile())
)
},
default: process.argv[2],
})
2020-11-26 21:40:25 +00:00
const isLocalFile = fileInit.endsWith(EXT)
if (!isLocalFile) {
2020-11-26 21:40:25 +00:00
// request scoreinfo
scoreinfo = await ScoreInfoHtml.request(fileInit)
2020-11-26 21:40:25 +00:00
// confirmation
const { confirmed } = await inquirer.prompt<Params>({
type: 'confirm',
name: 'confirmed',
message: 'Continue?',
prefix: `${chalk.yellow('!')} ` +
`ID: ${scoreinfo.id}\n ` +
`Title: ${scoreinfo.title}\n `,
default: true,
})
if (!confirmed) return
console.log() // print a blank line to the terminal
} else {
scoreinfo = new ScoreInfoObj(0, path.basename(fileInit, EXT))
}
2020-11-24 22:32:44 +00:00
const spinner = ora({
text: i18n('PROCESSING')(),
color: 'blue',
spinner: 'bounce',
indent: 0,
}).start()
let score: WebMscore
let metadata: import('webmscore/schemas').ScoreMetadata
try {
2020-11-26 21:40:25 +00:00
if (!isLocalFile) {
// fetch mscz file from the dataset, and cache it for side effect
await fetchMscz(scoreinfo)
} else {
// load local file
const data = await fs.promises.readFile(fileInit)
await setMscz(scoreinfo, data.buffer)
}
2020-11-24 22:32:44 +00:00
spinner.info('MSCZ file loaded')
2020-11-26 21:40:25 +00:00
if (!isLocalFile) {
spinner.info(`File URL: ${scoreinfo.store.get(MSCZ_URL_SYM) as string}`)
}
2020-11-24 22:32:44 +00:00
spinner.start()
// load score using webmscore
score = await loadMscore(scoreinfo)
metadata = await score.metadata()
spinner.info('Score loaded by webmscore')
} catch (err) {
spinner.fail(err.message)
return
}
spinner.succeed('OK\n')
// build part choices
const partChoices = metadata.excerpts.map(p => ({ name: p.title, value: p.id }))
// add the "full score" option as a "part"
partChoices.unshift({ value: -1, name: i18n('FULL_SCORE')() })
// build filetype choices
const typeChoices = INDV_DOWNLOADS.map((d, i) => ({ name: d.name, value: i }))
// part selection
2020-11-26 19:58:03 +00:00
const { part } = await inquirer.prompt<Params>({
2020-11-24 22:32:44 +00:00
type: 'list',
name: 'part',
message: 'Part Selection',
choices: partChoices,
})
const partName = partChoices[part + 1].name
await score.setExcerptId(part)
// filetype selection
2020-11-26 19:58:03 +00:00
const { types } = await inquirer.prompt<Params>({
2020-11-24 22:32:44 +00:00
type: 'checkbox',
name: 'types',
message: 'Filetype Selection',
choices: typeChoices,
validate (input: number[]) {
return input.length >= 1
},
2020-11-24 22:32:44 +00:00
})
const filetypes = types.map(i => INDV_DOWNLOADS[i])
// destination directory
2020-11-26 19:58:03 +00:00
const { dest } = await inquirer.prompt<Params>({
type: 'input',
name: 'dest',
message: 'Destination Directory:',
validate (input: string) {
return input && fs.statSync(input).isDirectory()
},
default: process.cwd(),
})
2020-11-24 22:32:44 +00:00
// export files
2020-11-26 21:40:25 +00:00
const fileName = scoreinfo.fileName || await score.titleFilenameSafe()
2020-11-24 22:32:44 +00:00
spinner.start()
await Promise.all(
filetypes.map(async (d) => {
const data = await d.action(score)
2020-11-26 20:07:34 +00:00
const n = `${fileName} - ${escapeFilename(partName)}.${d.fileExt}`
const f = path.join(dest, n)
2020-11-24 22:32:44 +00:00
await fs.promises.writeFile(f, data)
spinner.info(`Saved ${chalk.underline(f)}`)
spinner.start()
}),
)
spinner.succeed('OK')
})()