UNPKG

retroload

Version:

Command line utility for converting tape archive files of historical computers into sound for loading them on real devices

91 lines 5.36 kB
#!/usr/bin/env node var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { Command } from 'commander'; import { Logger, decodeWav, formatPosition, getAllDecoders, version as libVersion } from 'retroload-lib'; import { readFile, writeFile } from './Utils.js'; import { version as cliVersion } from './version.js'; main() .catch((err) => { Logger.error(err); }); // eslint-disable-next-line @typescript-eslint/require-await function main() { return __awaiter(this, void 0, void 0, function* () { // TODO / Nice to haves: // - CSW as additional input format // - kcsss (and many others) as additional output format // - option to fix block number on read errors // - additional statistics: total block count, invalid files/blocks // - visualize WAVE samples of section around error as ASCII graph when --on-error was set to 'stop' const program = (new Command()) .name('retroload-decode') .description('Decode WAVE files of historical computers.') .argument('<infile>', 'Path to WAVE file to decode') .allowExcessArguments(false) .option('-o <outpath>', 'Prefix (filename or complete path) for files to write', './') .option('-l, --loglevel <loglevel>', 'Verbosity of log output', '1') .requiredOption('--format <format>', `Output format (one of: ${getDecoderList()})`) .option('--on-error <errorhandling>', 'Error handling strategy (one of: ignore, skipfile, stop)', 'ignore') .option('--channel <channel>', 'Use specified channel to get samples from, in case the input file has multiple channels. Numbering starts at 0.') .option('--skip <samples>', 'Start processing of input data after skipping a specific number of samples', '0') .option('--no-proposed-name', 'Just use numeric file names instead of file names from tape/archive.') .option('--extension <extension>', 'Use specified file extension instead of the one proposed by the decoder.') .version(`retroload: ${cliVersion}\nretroload-lib: ${libVersion}`) .showHelpAfterError(); program.parse(); const options = program.opts(); const infile = program.args[0]; Logger.setVerbosity(parseInt(typeof options['loglevel'] === 'string' ? options['loglevel'] : '1', 10)); const outputFormat = (typeof options['format'] === 'string' ? options['format'] : undefined); if (outputFormat === undefined) { // should actually be catched by commander because of requiredOption Logger.error('error: missing required argument \'format\''); process.exit(1); } const outPathPrefix = typeof options['o'] === 'string' ? options['o'] : './'; const decoderSettings = getDecoderSettings(options); const data = readFile(infile); Logger.debug(`Output format: ${outputFormat}`); Logger.debug(`Settings: ${JSON.stringify(decoderSettings)}`); Logger.debug(`Processing ${infile}...`); let i = 0; for (const file of decodeWav(data, outputFormat, decoderSettings)) { const extension = typeof options['extension'] === 'string' ? options['extension'] : file.proposedExtension; const fallbackName = `${i}.${extension}`; const proposedName = file.proposedName === undefined ? fallbackName : `${file.proposedName}.${extension}`; const fileName = options['proposedName'] ? proposedName : fallbackName; Logger.info(`Writing file: ${fileName} (${file.data.length()} bytes, position in input: ${formatPosition(file.begin)} - ${formatPosition(file.end)})`); Logger.debug(file.data.asHexDump()); writeFile(`${outPathPrefix}${fileName}`, file.data.asUint8Array()); i++; } Logger.info(`Dumped ${i} file(s).`); }); } function getDecoderList() { return getAllDecoders().map((c) => c.format).join(', '); } function getDecoderSettings(options) { if (typeof options['onError'] !== 'string') { Logger.error('error: invalid value for argument \'on-error\''); process.exit(1); } if (options['onError'] !== 'stop' && options['onError'] !== 'ignore' && options['onError'] !== 'skipfile') { Logger.error('error: invalid value for argument \'on-error\''); process.exit(1); } return { onError: options['onError'], skip: parseInt(typeof options['skip'] === 'string' ? options['skip'] : '0', 10), channel: typeof options['channel'] === 'string' ? parseInt(options['channel'], 10) : undefined, }; } //# sourceMappingURL=retroload-decode.js.map