137 lines
4.4 KiB
JavaScript
137 lines
4.4 KiB
JavaScript
const axios = require('axios');
|
|
const FormData = require('form-data');
|
|
const Config = require('../models/Config');
|
|
const { decrypt } = require('../utils/crypto');
|
|
|
|
exports.analyzeImage = async (req, res) => {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).json({ error: 'No image provided' });
|
|
}
|
|
|
|
// Fetch API Key from DB
|
|
const apiKeyConfig = await Config.findOne({ keyName: 'PERFECT_CORP_API_KEY' });
|
|
if (!apiKeyConfig) {
|
|
return res.status(500).json({ error: 'API Key configuration missing' });
|
|
}
|
|
const decryptedApiKey = decrypt(apiKeyConfig.encryptedValue, apiKeyConfig.iv);
|
|
|
|
const baseUrl = process.env.PERFECT_CORP_API_URL || 'https://yce-api-01.makeupar.com/s2s/v2.0';
|
|
|
|
// Step 1: Initialize Upload
|
|
console.log('Step 1: Initializing Upload...');
|
|
const initPayload = {
|
|
files: [
|
|
{
|
|
content_type: req.file.mimetype || 'image/jpeg',
|
|
file_name: req.file.originalname || 'image.jpg',
|
|
file_size: req.file.size
|
|
}
|
|
]
|
|
};
|
|
|
|
const initResponse = await axios.post(`${baseUrl}/file`, initPayload, {
|
|
headers: {
|
|
'Authorization': `Bearer ${decryptedApiKey}`,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const fileData = initResponse.data?.data?.files?.[0];
|
|
if (!fileData || !fileData.requests || !fileData.requests[0]) {
|
|
throw new Error('Failed to get upload URL from Perfect Corp API');
|
|
}
|
|
|
|
const fileId = fileData.file_id;
|
|
const uploadRequest = fileData.requests[0];
|
|
|
|
// Step 2: S3 Upload
|
|
console.log(`Step 2: Uploading image to S3 (fileId: ${fileId})...`);
|
|
await axios.put(uploadRequest.url, req.file.buffer, {
|
|
headers: uploadRequest.headers
|
|
});
|
|
|
|
// Step 3: Create Task
|
|
console.log('Step 3: Creating AI Task...');
|
|
const taskPayload = {
|
|
src_file_id: fileId,
|
|
dst_actions: [
|
|
"wrinkle", "pore", "texture", "acne", "moisture", "eye_bag",
|
|
"firmness", "age_spot", "radiance", "redness", "oiliness"
|
|
],
|
|
miniserver_args: {
|
|
enable_mask_overlay: false // Generate multiple output masks
|
|
},
|
|
format: "json"
|
|
};
|
|
|
|
const taskResponse = await axios.post(`${baseUrl}/task/skin-analysis`, taskPayload, {
|
|
headers: {
|
|
'Authorization': `Bearer ${decryptedApiKey}`,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const taskId = taskResponse.data?.data?.task_id;
|
|
if (!taskId) {
|
|
throw new Error('Failed to create AI task');
|
|
}
|
|
|
|
// Step 4: Poll Task Status
|
|
console.log(`Step 4: Polling task status for taskId: ${taskId}...`);
|
|
let taskFinished = false;
|
|
let finalResult = null;
|
|
let attempts = 0;
|
|
const maxAttempts = 30; // 60 seconds max
|
|
|
|
while (!taskFinished && attempts < maxAttempts) {
|
|
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
|
|
attempts++;
|
|
|
|
const pollResponse = await axios.get(`${baseUrl}/task/skin-analysis/${taskId}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${decryptedApiKey}`
|
|
}
|
|
});
|
|
|
|
const status = pollResponse.data?.data?.task_status;
|
|
if (status === 'success') {
|
|
taskFinished = true;
|
|
finalResult = pollResponse.data;
|
|
} else if (status === 'error' || status === 'failed') {
|
|
throw new Error(`AI Task failed with status: ${status}`);
|
|
} else {
|
|
console.log(`Polling... (status: ${status}, attempt: ${attempts})`);
|
|
}
|
|
}
|
|
|
|
if (!taskFinished) {
|
|
throw new Error('AI Task polling timed out');
|
|
}
|
|
|
|
console.log('Task finished successfully!');
|
|
|
|
// Format output to mirror the score_info.json structure
|
|
const outputArray = finalResult?.data?.results?.output || [];
|
|
const formattedScores = {};
|
|
|
|
for (const item of outputArray) {
|
|
if (item.type) {
|
|
formattedScores[item.type] = {
|
|
value: item.raw_score || item.ui_score || 0,
|
|
ui_score: item.ui_score,
|
|
raw_score: item.raw_score,
|
|
output_mask_name: (item.mask_urls && item.mask_urls.length > 0) ? item.mask_urls[0] : null
|
|
};
|
|
}
|
|
}
|
|
|
|
// Send formatted scores back to frontend
|
|
res.json({ result: { scores: formattedScores, raw_response: finalResult } });
|
|
|
|
} catch (error) {
|
|
console.error('Error analyzing image:', error.response ? JSON.stringify(error.response.data) : error.message);
|
|
res.status(500).json({ error: 'Failed to analyze image' });
|
|
}
|
|
};
|