426 líneas
12 KiB
JavaScript
Archivo Ejecutable
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();
|