|
|
#!/usr/bin/env node |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const axios = require('axios'); |
|
|
const FormData = require('form-data'); |
|
|
const fs = require('fs'); |
|
|
const path = require('path'); |
|
|
const WebSocket = require('ws'); |
|
|
|
|
|
|
|
|
const API_BASE_URL = process.env.BACKGROUNDFX_API_URL || 'https://api.backgroundfx.pro/v1'; |
|
|
const API_KEY = process.env.BACKGROUNDFX_API_KEY || 'your-api-key-here'; |
|
|
const WS_URL = process.env.BACKGROUNDFX_WS_URL || 'wss://ws.backgroundfx.pro'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BackgroundFXClient { |
|
|
constructor(apiKey, baseUrl = API_BASE_URL) { |
|
|
this.apiKey = apiKey; |
|
|
this.baseUrl = baseUrl.replace(/\/$/, ''); |
|
|
|
|
|
|
|
|
this.client = axios.create({ |
|
|
baseURL: this.baseUrl, |
|
|
headers: { |
|
|
'Authorization': `Bearer ${apiKey}`, |
|
|
'User-Agent': 'BackgroundFX-Node-Client/1.0' |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async removeBackground(imagePath, options = {}) { |
|
|
const { |
|
|
quality = 'high', |
|
|
model = 'auto', |
|
|
returnMask = false, |
|
|
edgeRefinement = 50 |
|
|
} = options; |
|
|
|
|
|
|
|
|
if (!fs.existsSync(imagePath)) { |
|
|
throw new Error(`File not found: ${imagePath}`); |
|
|
} |
|
|
|
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('file', fs.createReadStream(imagePath)); |
|
|
formData.append('quality', quality); |
|
|
formData.append('model', model); |
|
|
formData.append('return_mask', returnMask.toString()); |
|
|
formData.append('edge_refinement', edgeRefinement.toString()); |
|
|
|
|
|
try { |
|
|
console.log(`π Processing image: ${path.basename(imagePath)}`); |
|
|
|
|
|
const response = await this.client.post('/process/remove-background', formData, { |
|
|
headers: formData.getHeaders(), |
|
|
maxContentLength: Infinity, |
|
|
maxBodyLength: Infinity |
|
|
}); |
|
|
|
|
|
console.log('β
Background removed successfully!'); |
|
|
return response.data; |
|
|
} catch (error) { |
|
|
console.error('β Error processing image:', error.response?.data || error.message); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async processBatch(imagePaths, options = {}) { |
|
|
const formData = new FormData(); |
|
|
|
|
|
|
|
|
for (const imagePath of imagePaths) { |
|
|
if (!fs.existsSync(imagePath)) { |
|
|
console.warn(`β οΈ Skipping missing file: ${imagePath}`); |
|
|
continue; |
|
|
} |
|
|
formData.append('files', fs.createReadStream(imagePath)); |
|
|
} |
|
|
|
|
|
|
|
|
formData.append('options', JSON.stringify(options)); |
|
|
|
|
|
try { |
|
|
console.log(`π Processing batch of ${imagePaths.length} images...`); |
|
|
|
|
|
const response = await this.client.post('/process/batch', formData, { |
|
|
headers: formData.getHeaders() |
|
|
}); |
|
|
|
|
|
const jobId = response.data.id; |
|
|
console.log(`β
Batch job created: ${jobId}`); |
|
|
|
|
|
|
|
|
return await this.monitorJob(jobId); |
|
|
} catch (error) { |
|
|
console.error('β Batch processing failed:', error.message); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async monitorJob(jobId, pollInterval = 2000) { |
|
|
console.log(`π Monitoring job: ${jobId}`); |
|
|
|
|
|
while (true) { |
|
|
try { |
|
|
const response = await this.client.get(`/process/jobs/${jobId}`); |
|
|
const job = response.data; |
|
|
|
|
|
console.log(` Status: ${job.status} | Progress: ${job.progress}%`); |
|
|
|
|
|
if (job.status === 'completed') { |
|
|
console.log('β
Job completed!'); |
|
|
return job; |
|
|
} else if (job.status === 'failed') { |
|
|
throw new Error(`Job failed: ${job.error}`); |
|
|
} |
|
|
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, pollInterval)); |
|
|
} catch (error) { |
|
|
console.error('β Error monitoring job:', error.message); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async replaceBackground(imageId, background, blendMode = 'normal') { |
|
|
try { |
|
|
const response = await this.client.post('/process/replace-background', { |
|
|
image_id: imageId, |
|
|
background: background, |
|
|
blend_mode: blendMode |
|
|
}); |
|
|
|
|
|
console.log('β
Background replaced!'); |
|
|
return response.data; |
|
|
} catch (error) { |
|
|
console.error('β Error replacing background:', error.message); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async downloadResult(url, outputPath) { |
|
|
const writer = fs.createWriteStream(outputPath); |
|
|
|
|
|
const response = await axios({ |
|
|
url, |
|
|
method: 'GET', |
|
|
responseType: 'stream' |
|
|
}); |
|
|
|
|
|
response.data.pipe(writer); |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
writer.on('finish', () => { |
|
|
console.log(`πΎ Saved to: ${outputPath}`); |
|
|
resolve(outputPath); |
|
|
}); |
|
|
writer.on('error', reject); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
connectWebSocket(jobId) { |
|
|
return new Promise((resolve, reject) => { |
|
|
const ws = new WebSocket(`${WS_URL}?job_id=${jobId}`, { |
|
|
headers: { |
|
|
'Authorization': `Bearer ${this.apiKey}` |
|
|
} |
|
|
}); |
|
|
|
|
|
ws.on('open', () => { |
|
|
console.log('π WebSocket connected'); |
|
|
ws.send(JSON.stringify({ action: 'subscribe', job_id: jobId })); |
|
|
}); |
|
|
|
|
|
ws.on('message', (data) => { |
|
|
const message = JSON.parse(data); |
|
|
console.log('π¨ WebSocket message:', message); |
|
|
|
|
|
if (message.type === 'job:complete') { |
|
|
ws.close(); |
|
|
resolve(message.data); |
|
|
} else if (message.type === 'job:error') { |
|
|
ws.close(); |
|
|
reject(new Error(message.error)); |
|
|
} else if (message.type === 'job:progress') { |
|
|
console.log(` Progress: ${message.progress}%`); |
|
|
} |
|
|
}); |
|
|
|
|
|
ws.on('error', (error) => { |
|
|
console.error('β WebSocket error:', error); |
|
|
reject(error); |
|
|
}); |
|
|
|
|
|
ws.on('close', () => { |
|
|
console.log('π WebSocket disconnected'); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function exampleBasicUsage() { |
|
|
console.log('\n' + '='.repeat(60)); |
|
|
console.log('EXAMPLE 1: Basic Background Removal'); |
|
|
console.log('='.repeat(60)); |
|
|
|
|
|
const client = new BackgroundFXClient(API_KEY); |
|
|
|
|
|
try { |
|
|
|
|
|
const result = await client.removeBackground('sample_images/portrait.jpg', { |
|
|
quality: 'high' |
|
|
}); |
|
|
|
|
|
|
|
|
await client.downloadResult( |
|
|
result.image, |
|
|
'output/portrait_no_bg.png' |
|
|
); |
|
|
|
|
|
console.log('β¨ Basic processing complete!'); |
|
|
} catch (error) { |
|
|
console.error('Failed:', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function exampleBatchProcessing() { |
|
|
console.log('\n' + '='.repeat(60)); |
|
|
console.log('EXAMPLE 2: Batch Processing'); |
|
|
console.log('='.repeat(60)); |
|
|
|
|
|
const client = new BackgroundFXClient(API_KEY); |
|
|
|
|
|
const images = [ |
|
|
'sample_images/product1.jpg', |
|
|
'sample_images/product2.jpg', |
|
|
'sample_images/product3.jpg' |
|
|
]; |
|
|
|
|
|
try { |
|
|
const job = await client.processBatch(images, { |
|
|
quality: 'medium', |
|
|
model: 'rembg' |
|
|
}); |
|
|
|
|
|
|
|
|
for (const [index, result] of job.results.entries()) { |
|
|
await client.downloadResult( |
|
|
result.image, |
|
|
`output/batch/product${index + 1}_no_bg.png` |
|
|
); |
|
|
} |
|
|
|
|
|
console.log('β¨ Batch processing complete!'); |
|
|
} catch (error) { |
|
|
console.error('Failed:', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function exampleWebSocketMonitoring() { |
|
|
console.log('\n' + '='.repeat(60)); |
|
|
console.log('EXAMPLE 3: WebSocket Real-time Monitoring'); |
|
|
console.log('='.repeat(60)); |
|
|
|
|
|
const client = new BackgroundFXClient(API_KEY); |
|
|
|
|
|
try { |
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('files', fs.createReadStream('sample_images/large1.jpg')); |
|
|
formData.append('files', fs.createReadStream('sample_images/large2.jpg')); |
|
|
|
|
|
const response = await client.client.post('/process/batch', formData, { |
|
|
headers: formData.getHeaders() |
|
|
}); |
|
|
|
|
|
const jobId = response.data.id; |
|
|
console.log(`π Job ID: ${jobId}`); |
|
|
|
|
|
|
|
|
const result = await client.connectWebSocket(jobId); |
|
|
console.log('β¨ Processing complete via WebSocket!'); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed:', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function exampleBackgroundReplacement() { |
|
|
console.log('\n' + '='.repeat(60)); |
|
|
console.log('EXAMPLE 4: Background Replacement'); |
|
|
console.log('='.repeat(60)); |
|
|
|
|
|
const client = new BackgroundFXClient(API_KEY); |
|
|
|
|
|
try { |
|
|
|
|
|
const result = await client.removeBackground('sample_images/person.jpg'); |
|
|
const imageId = result.id; |
|
|
|
|
|
|
|
|
const backgrounds = [ |
|
|
{ type: 'color', value: '#3498db', name: 'blue' }, |
|
|
{ type: 'gradient', value: 'linear-gradient(45deg, #ff6b6b, #4ecdc4)', name: 'gradient' }, |
|
|
{ type: 'blur', value: 'blur:20', name: 'blurred' } |
|
|
]; |
|
|
|
|
|
for (const bg of backgrounds) { |
|
|
console.log(`π¨ Applying ${bg.name} background...`); |
|
|
const replaced = await client.replaceBackground(imageId, bg.value); |
|
|
await client.downloadResult( |
|
|
replaced.image, |
|
|
`output/person_${bg.name}_bg.png` |
|
|
); |
|
|
} |
|
|
|
|
|
console.log('β¨ Background replacement complete!'); |
|
|
} catch (error) { |
|
|
console.error('Failed:', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function exampleErrorHandling() { |
|
|
console.log('\n' + '='.repeat(60)); |
|
|
console.log('EXAMPLE 5: Error Handling'); |
|
|
console.log('='.repeat(60)); |
|
|
|
|
|
const client = new BackgroundFXClient(API_KEY); |
|
|
|
|
|
|
|
|
async function withRetry(fn, maxRetries = 3) { |
|
|
for (let i = 0; i < maxRetries; i++) { |
|
|
try { |
|
|
return await fn(); |
|
|
} catch (error) { |
|
|
console.log(`β οΈ Attempt ${i + 1} failed: ${error.message}`); |
|
|
if (i === maxRetries - 1) throw error; |
|
|
|
|
|
|
|
|
const delay = Math.pow(2, i) * 1000; |
|
|
console.log(`β³ Waiting ${delay}ms before retry...`); |
|
|
await new Promise(resolve => setTimeout(resolve, delay)); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
try { |
|
|
const result = await withRetry(() => |
|
|
client.removeBackground('sample_images/test.jpg') |
|
|
); |
|
|
console.log('β
Success after retries'); |
|
|
} catch (error) { |
|
|
console.error('β Failed after all retries:', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function exampleParallelProcessing() { |
|
|
console.log('\n' + '='.repeat(60)); |
|
|
console.log('EXAMPLE 6: Parallel Processing'); |
|
|
console.log('='.repeat(60)); |
|
|
|
|
|
const client = new BackgroundFXClient(API_KEY); |
|
|
|
|
|
const images = [ |
|
|
'sample_images/img1.jpg', |
|
|
'sample_images/img2.jpg', |
|
|
'sample_images/img3.jpg', |
|
|
'sample_images/img4.jpg' |
|
|
]; |
|
|
|
|
|
try { |
|
|
console.log(`π Processing ${images.length} images in parallel...`); |
|
|
const startTime = Date.now(); |
|
|
|
|
|
|
|
|
const promises = images.map(imagePath => |
|
|
client.removeBackground(imagePath, { quality: 'medium' }) |
|
|
.catch(err => ({ error: err.message, path: imagePath })) |
|
|
); |
|
|
|
|
|
const results = await Promise.all(promises); |
|
|
|
|
|
const elapsed = (Date.now() - startTime) / 1000; |
|
|
console.log(`β
Processed ${results.length} images in ${elapsed.toFixed(2)}s`); |
|
|
|
|
|
|
|
|
const successes = results.filter(r => !r.error).length; |
|
|
const failures = results.filter(r => r.error).length; |
|
|
|
|
|
console.log(` Successes: ${successes}`); |
|
|
console.log(` Failures: ${failures}`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed:', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function main() { |
|
|
console.log('\n' + '#'.repeat(60)); |
|
|
console.log('# BackgroundFX Pro - Node.js Examples'); |
|
|
console.log('#'.repeat(60)); |
|
|
|
|
|
|
|
|
if (API_KEY === 'your-api-key-here') { |
|
|
console.error('\nβ οΈ Please set your API key in BACKGROUNDFX_API_KEY environment variable'); |
|
|
process.exit(1); |
|
|
} |
|
|
|
|
|
|
|
|
const dirs = ['output', 'output/batch', 'sample_images']; |
|
|
dirs.forEach(dir => { |
|
|
if (!fs.existsSync(dir)) { |
|
|
fs.mkdirSync(dir, { recursive: true }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const examples = [ |
|
|
exampleBasicUsage, |
|
|
exampleBatchProcessing, |
|
|
exampleWebSocketMonitoring, |
|
|
exampleBackgroundReplacement, |
|
|
exampleErrorHandling, |
|
|
exampleParallelProcessing |
|
|
]; |
|
|
|
|
|
for (const example of examples) { |
|
|
try { |
|
|
await example(); |
|
|
} catch (error) { |
|
|
console.error(`\nβ Example failed: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
console.log('\n' + '#'.repeat(60)); |
|
|
console.log('# All examples complete!'); |
|
|
console.log('#'.repeat(60)); |
|
|
} |
|
|
|
|
|
|
|
|
if (require.main === module) { |
|
|
main().catch(console.error); |
|
|
} |
|
|
|
|
|
module.exports = { BackgroundFXClient }; |