-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
224 lines (186 loc) · 7.51 KB
/
Copy pathindex.js
File metadata and controls
224 lines (186 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/env node
const { Command } = require('commander');
const { rembg } = require('@remove-background-ai/rembg.js');
const dotenv = require('dotenv');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const ora = require('ora');
const { glob } = require('glob');
// Load environment variables
dotenv.config();
const program = new Command();
// CLI Configuration
program
.name('rembg-cli')
.version('1.0.0')
.description('Cross-platform CLI tool for batch background removal using rembg.js')
.option('-i, --input <path>', 'Input file or directory containing images')
.option('-o, --output <path>', 'Output directory for processed images', './output')
.option('-f, --format <format>', 'Output format (png, webp)', 'webp')
.option('-v, --verbose', 'Enable verbose logging')
.option('--api-key <key>', 'Override API key from environment')
.parse(process.argv);
const options = program.opts();
// Validate required options
if (!options.input) {
console.error(chalk.red('❌ Error: Input path is required. Use -i or --input to specify a file or directory.'));
console.log(chalk.yellow('Example: rembg-cli -i ./images -o ./output'));
process.exit(1);
}
// Get API key
const apiKey = options.apiKey || process.env.X_API_KEY;
if (!apiKey) {
console.error(chalk.red('❌ Error: X_API_KEY is not set.'));
console.log(chalk.yellow('Please set your API key in one of these ways:'));
console.log(chalk.cyan('1. Environment variable: export X_API_KEY=your_api_key'));
console.log(chalk.cyan('2. .env file: X_API_KEY=your_api_key'));
console.log(chalk.cyan('3. CLI option: --api-key your_api_key'));
process.exit(1);
}
// Supported image formats
const supportedFormats = ['.jpg', '.jpeg', '.png', '.webp'];
const supportedFormatsRegex = /\.(jpg|jpeg|png|webp)$/i;
/**
* Check if a file is a supported image format
*/
function isImageFile(filename) {
return supportedFormatsRegex.test(filename);
}
/**
* Process a single image
*/
async function processImage(inputFile, outputDir, apiKey, format = 'png') {
try {
const spinner = ora(`Processing ${path.basename(inputFile)}`).start();
const { outputImagePath, cleanup } = await rembg({
apiKey: apiKey,
inputImage: inputFile,
});
// Generate output filename with proper extension
const inputBasename = path.basename(inputFile, path.extname(inputFile));
const outputFileName = `${inputBasename}.${format}`;
const finalOutputPath = path.join(outputDir, outputFileName);
// Move the processed image to the final output location
fs.renameSync(outputImagePath, finalOutputPath);
spinner.succeed(chalk.green(`✅ Processed: ${path.basename(inputFile)} → ${outputFileName}`));
// Cleanup temporary files
cleanup();
return { success: true, input: inputFile, output: finalOutputPath };
} catch (error) {
if (options.verbose) {
console.error(chalk.red(`❌ Failed to process ${inputFile}:`), error.message);
} else {
console.error(chalk.red(`❌ Failed to process ${path.basename(inputFile)}: ${error.message}`));
}
return { success: false, input: inputFile, error: error.message };
}
}
/**
* Process all images in a directory
*/
async function processDirectory(inputDir, outputDir, apiKey, format) {
try {
// Find all image files recursively
const pattern = path.join(inputDir, '**', '*').replace(/\\/g, '/');
const allFiles = await glob(pattern, { nodir: true });
const imageFiles = allFiles.filter(file => isImageFile(file));
if (imageFiles.length === 0) {
console.log(chalk.yellow('⚠️ No supported image files found in the directory.'));
console.log(chalk.cyan('Supported formats: JPG, JPEG, PNG, WEBP'));
return { processed: 0, failed: 0, total: 0 };
}
console.log(chalk.blue(`📁 Found ${imageFiles.length} image(s) to process`));
console.log(chalk.gray(`📤 Output directory: ${path.resolve(outputDir)}`));
console.log(chalk.gray(`🎨 Output format: ${format.toUpperCase()}`));
console.log('');
let processed = 0;
let failed = 0;
const results = [];
// Process images sequentially to avoid API rate limits
for (const imageFile of imageFiles) {
const result = await processImage(imageFile, outputDir, apiKey, format);
results.push(result);
if (result.success) {
processed++;
} else {
failed++;
}
}
return { processed, failed, total: imageFiles.length, results };
} catch (error) {
console.error(chalk.red('❌ Error processing directory:'), error.message);
return { processed: 0, failed: 0, total: 0, error: error.message };
}
}
/**
* Main execution function
*/
async function main() {
const inputPath = path.resolve(options.input);
const outputPath = path.resolve(options.output);
// Validate input path
if (!fs.existsSync(inputPath)) {
console.error(chalk.red(`❌ Error: Input path does not exist: ${inputPath}`));
process.exit(1);
}
// Create output directory if it doesn't exist
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
if (options.verbose) {
console.log(chalk.gray(`📁 Created output directory: ${outputPath}`));
}
}
const inputStats = fs.statSync(inputPath);
let results;
console.log(chalk.blue.bold('🚀 Starting background removal process...'));
console.log('');
if (inputStats.isDirectory()) {
// Process directory
results = await processDirectory(inputPath, outputPath, apiKey, options.format);
} else if (inputStats.isFile() && isImageFile(inputPath)) {
// Process single file
console.log(chalk.blue(`📄 Processing single image: ${path.basename(inputPath)}`));
console.log(chalk.gray(`📤 Output directory: ${outputPath}`));
console.log(chalk.gray(`🎨 Output format: ${options.format.toUpperCase()}`));
console.log('');
const result = await processImage(inputPath, outputPath, apiKey, options.format);
results = {
processed: result.success ? 1 : 0,
failed: result.success ? 0 : 1,
total: 1,
results: [result]
};
} else {
console.error(chalk.red('❌ Error: Input must be a valid image file or directory containing images.'));
console.log(chalk.cyan('Supported formats: JPG, JPEG, PNG, WEBP'));
process.exit(1);
}
// Display final results
console.log('');
console.log(chalk.blue.bold('📊 Processing Complete!'));
console.log(chalk.green(`✅ Successfully processed: ${results.processed} image(s)`));
if (results.failed > 0) {
console.log(chalk.red(`❌ Failed to process: ${results.failed} image(s)`));
}
console.log(chalk.blue(`📈 Total images: ${results.total}`));
if (results.processed > 0) {
console.log(chalk.green(`🎉 All processed images saved to: ${path.resolve(outputPath)}`));
}
// Exit with appropriate code
process.exit(results.failed > 0 ? 1 : 0);
}
// Handle uncaught errors
process.on('uncaughtException', (error) => {
console.error(chalk.red('❌ Uncaught Exception:'), error.message);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error(chalk.red('❌ Unhandled Rejection at:'), promise, 'reason:', reason);
process.exit(1);
});
// Run the main function
main().catch((error) => {
console.error(chalk.red('❌ Fatal error:'), error.message);
process.exit(1);
});