Files
img2mp3/bin/cli.js
2025-09-06 19:11:40 +02:00

426 líneas
12 KiB
JavaScript
Archivo Ejecutable

#!/usr/bin/env node
const { Command } = require('commander');
const chalk = require('chalk');
const ora = require('ora');
const inquirer = require('inquirer');
const fs = require('fs');
const path = require('path');
const { IMG2MP3 } = require('../src/index');
const program = new Command();
const img2mp3 = new IMG2MP3();
// Package info
const packageJson = require('../package.json');
program
.name('img2mp3')
.description('Encode MP3 files into images and decode them back, with built-in player')
.version(packageJson.version);
/**
* Encode command
*/
program
.command('encode')
.description('Encode MP3 file into an image, creating a .i2m file')
.argument('<image>', 'Source image file')
.argument('<mp3>', 'MP3 file to embed')
.argument('[output]', 'Output .i2m file (optional)')
.option('-v, --verbose', 'Show detailed output')
.action(async (image, mp3, output, options) => {
try {
// Validate input files
if (!fs.existsSync(image)) {
console.error(chalk.red(`Error: Image file '${image}' not found`));
process.exit(1);
}
if (!fs.existsSync(mp3)) {
console.error(chalk.red(`Error: MP3 file '${mp3}' not found`));
process.exit(1);
}
// Generate output filename if not provided
if (!output) {
const baseName = path.basename(mp3, path.extname(mp3));
output = `${baseName}.i2m`;
}
// Ensure .i2m extension
if (!output.endsWith('.i2m')) {
output += '.i2m';
}
const spinner = ora('Encoding MP3 into image...').start();
const result = await img2mp3.encode(image, mp3, output);
spinner.succeed(chalk.green('Encoding completed successfully!'));
console.log(chalk.cyan('\nResults:'));
console.log(`📁 Output file: ${chalk.yellow(output)}`);
console.log(`📊 Original image: ${chalk.blue(formatBytes(result.originalImageSize))}`);
console.log(`🎵 MP3 file: ${chalk.blue(formatBytes(result.mp3Size))}`);
console.log(`💾 Final .i2m file: ${chalk.blue(formatBytes(result.outputSize))}`);
console.log(`📐 Container image size: ${chalk.blue(result.imageSize)}`);
if (options.verbose) {
console.log(chalk.gray('\nDetailed info:'));
console.log(chalk.gray(JSON.stringify(result, null, 2)));
}
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
});
/**
* Decode command
*/
program
.command('decode')
.description('Decode .i2m file back to image and MP3')
.argument('<i2m>', '.i2m file to decode')
.option('-i, --image <path>', 'Output path for extracted image')
.option('-m, --mp3 <path>', 'Output path for extracted MP3')
.option('-v, --verbose', 'Show detailed output')
.action(async (i2mFile, options) => {
try {
if (!fs.existsSync(i2mFile)) {
console.error(chalk.red(`Error: .i2m file '${i2mFile}' not found`));
process.exit(1);
}
// Generate output filenames if not provided
const baseName = path.basename(i2mFile, '.i2m');
const imagePath = options.image || `${baseName}_extracted_image.png`;
const mp3Path = options.mp3 || `${baseName}_extracted.mp3`;
const spinner = ora('Decoding .i2m file...').start();
const result = await img2mp3.decode(i2mFile, imagePath, mp3Path);
spinner.succeed(chalk.green('Decoding completed successfully!'));
console.log(chalk.cyan('\nExtracted files:'));
console.log(`🖼️ Image: ${chalk.yellow(imagePath)} (${chalk.blue(formatBytes(result.extractedImageSize))})`);
console.log(`🎵 MP3: ${chalk.yellow(mp3Path)} (${chalk.blue(formatBytes(result.extractedMp3Size))})`);
if (options.verbose) {
console.log(chalk.gray('\nDetailed info:'));
console.log(chalk.gray(JSON.stringify(result, null, 2)));
}
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
});
/**
* Play command
*/
program
.command('play')
.description('Play audio from .i2m file')
.argument('<i2m>', '.i2m file to play')
.option('-v, --verbose', 'Show detailed output')
.action(async (i2mFile, options) => {
try {
if (!fs.existsSync(i2mFile)) {
console.error(chalk.red(`Error: .i2m file '${i2mFile}' not found`));
process.exit(1);
}
console.log(chalk.cyan(`🎵 Playing: ${path.basename(i2mFile)}`));
if (options.verbose) {
const info = await img2mp3.getInfo(i2mFile);
if (info.isValid) {
console.log(chalk.gray(`Audio size: ${formatBytes(info.mp3Size)}`));
console.log(chalk.gray(`Container size: ${formatBytes(info.containerSize)}`));
}
}
await img2mp3.play(i2mFile);
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
});
/**
* Info command
*/
program
.command('info')
.description('Show information about .i2m file')
.argument('<i2m>', '.i2m file to analyze')
.action(async (i2mFile) => {
try {
if (!fs.existsSync(i2mFile)) {
console.error(chalk.red(`Error: .i2m file '${i2mFile}' not found`));
process.exit(1);
}
const spinner = ora('Analyzing .i2m file...').start();
const info = await img2mp3.getInfo(i2mFile);
spinner.stop();
if (!info.isValid) {
console.error(chalk.red(`Error: ${info.error}`));
process.exit(1);
}
console.log(chalk.cyan(`\n📊 Information for: ${path.basename(i2mFile)}`));
console.log(chalk.green('✅ Valid .i2m file'));
console.log(`🎵 Embedded audio: ${chalk.yellow(formatBytes(info.mp3Size))}`);
console.log(`🖼️ Original image: ${chalk.yellow(formatBytes(info.originalImageSize))}`);
console.log(`📦 Container file: ${chalk.yellow(formatBytes(info.containerSize))}`);
console.log(`📐 Container dimensions: ${chalk.blue(info.dimensions)}`);
console.log(`🏷️ Format: ${chalk.blue(info.format)}`);
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
});
/**
* Interactive mode
*/
program
.command('interactive')
.alias('i')
.description('Interactive mode for easy usage')
.action(async () => {
console.log(chalk.cyan('🎵 Welcome to IMG2MP3 Interactive Mode!\n'));
try {
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
{ name: '📥 Encode MP3 into image', value: 'encode' },
{ name: '📤 Decode .i2m file', value: 'decode' },
{ name: '▶️ Play .i2m file', value: 'play' },
{ name: '📊 Show .i2m file info', value: 'info' },
{ name: '❌ Exit', value: 'exit' }
]
}
]);
if (action === 'exit') {
console.log(chalk.yellow('👋 Goodbye!'));
return;
}
switch (action) {
case 'encode':
await interactiveEncode();
break;
case 'decode':
await interactiveDecode();
break;
case 'play':
await interactivePlay();
break;
case 'info':
await interactiveInfo();
break;
}
} catch (error) {
if (error.isTtyError) {
console.error(chalk.red('Interactive mode not supported in this environment'));
} else {
console.error(chalk.red(`Error: ${error.message}`));
}
}
});
/**
* Interactive encode function
*/
async function interactiveEncode() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'image',
message: 'Path to source image:',
validate: (input) => {
if (!input.trim()) return 'Please enter a path';
if (!fs.existsSync(input)) return 'File not found';
return true;
}
},
{
type: 'input',
name: 'mp3',
message: 'Path to MP3 file:',
validate: (input) => {
if (!input.trim()) return 'Please enter a path';
if (!fs.existsSync(input)) return 'File not found';
return true;
}
},
{
type: 'input',
name: 'output',
message: 'Output .i2m file (optional):',
default: (answers) => {
const baseName = path.basename(answers.mp3, path.extname(answers.mp3));
return `${baseName}.i2m`;
}
}
]);
const spinner = ora('Encoding...').start();
try {
const result = await img2mp3.encode(answers.image, answers.mp3, answers.output);
spinner.succeed(chalk.green('Encoding completed!'));
console.log(chalk.cyan('\n📁 Created:'), chalk.yellow(answers.output));
console.log(chalk.cyan('💾 Size:'), chalk.blue(formatBytes(result.outputSize)));
} catch (error) {
spinner.fail(chalk.red(`Encoding failed: ${error.message}`));
}
}
/**
* Interactive decode function
*/
async function interactiveDecode() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'i2m',
message: 'Path to .i2m file:',
validate: (input) => {
if (!input.trim()) return 'Please enter a path';
if (!fs.existsSync(input)) return 'File not found';
return true;
}
}
]);
const baseName = path.basename(answers.i2m, '.i2m');
const spinner = ora('Decoding...').start();
try {
const result = await img2mp3.decode(
answers.i2m,
`${baseName}_image.png`,
`${baseName}.mp3`
);
spinner.succeed(chalk.green('Decoding completed!'));
console.log(chalk.cyan('\n📁 Extracted files:'));
console.log(`🖼️ ${baseName}_image.png`);
console.log(`🎵 ${baseName}.mp3`);
} catch (error) {
spinner.fail(chalk.red(`Decoding failed: ${error.message}`));
}
}
/**
* Interactive play function
*/
async function interactivePlay() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'i2m',
message: 'Path to .i2m file to play:',
validate: (input) => {
if (!input.trim()) return 'Please enter a path';
if (!fs.existsSync(input)) return 'File not found';
return true;
}
}
]);
try {
console.log(chalk.cyan(`\n🎵 Playing: ${path.basename(answers.i2m)}`));
await img2mp3.play(answers.i2m);
} catch (error) {
console.error(chalk.red(`Playback failed: ${error.message}`));
}
}
/**
* Interactive info function
*/
async function interactiveInfo() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'i2m',
message: 'Path to .i2m file to analyze:',
validate: (input) => {
if (!input.trim()) return 'Please enter a path';
if (!fs.existsSync(input)) return 'File not found';
return true;
}
}
]);
const spinner = ora('Analyzing...').start();
try {
const info = await img2mp3.getInfo(answers.i2m);
spinner.stop();
if (!info.isValid) {
console.error(chalk.red(`Error: ${info.error}`));
return;
}
console.log(chalk.cyan(`\n📊 File: ${path.basename(answers.i2m)}`));
console.log(chalk.green('✅ Valid .i2m file'));
console.log(`🎵 Audio: ${formatBytes(info.mp3Size)}`);
console.log(`🖼️ Image: ${formatBytes(info.originalImageSize)}`);
console.log(`📦 Total: ${formatBytes(info.containerSize)}`);
} catch (error) {
spinner.fail(chalk.red(`Analysis failed: ${error.message}`));
}
}
/**
* Format bytes to human readable format
*/
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Handle cleanup on exit
process.on('SIGINT', () => {
console.log(chalk.yellow('\n\n👋 Cleaning up and exiting...'));
img2mp3.cleanup();
process.exit(0);
});
process.on('SIGTERM', () => {
img2mp3.cleanup();
process.exit(0);
});
// Parse arguments
program.parse();