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
JavaScript
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