matting-vip.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // VIP抠图处理模块(使用 BiRefNet)
  2. // 接收 base64 图片,返回抠图后的 base64 图片
  3. const { spawn } = require('child_process');
  4. const path = require('path');
  5. const fs = require('fs').promises;
  6. const fsSync = require('fs');
  7. const os = require('os');
  8. const http = require('http');
  9. class MattingVIP {
  10. /**
  11. * 处理 VIP 抠图请求
  12. * @param {http.IncomingMessage} req - HTTP 请求对象
  13. * @param {http.ServerResponse} res - HTTP 响应对象
  14. */
  15. static async handleRequest(req, res) {
  16. if (req.method !== 'POST') {
  17. res.writeHead(405, { 'Content-Type': 'application/json' });
  18. res.end(JSON.stringify({ error: 'Method not allowed' }));
  19. return;
  20. }
  21. let body = '';
  22. req.on('data', (chunk) => {
  23. body += chunk.toString();
  24. });
  25. req.on('end', async () => {
  26. try {
  27. const data = JSON.parse(body);
  28. const { imageBase64 } = data;
  29. if (!imageBase64) {
  30. res.writeHead(400, { 'Content-Type': 'application/json' });
  31. res.end(JSON.stringify({ error: 'Missing required field: imageBase64' }));
  32. return;
  33. }
  34. // 调用抠图处理
  35. const result = await this.processMatting(imageBase64);
  36. if (result.success) {
  37. res.writeHead(200, {
  38. 'Content-Type': 'application/json',
  39. 'Access-Control-Allow-Origin': '*'
  40. });
  41. res.end(JSON.stringify({
  42. success: true,
  43. imageData: result.imageData
  44. }));
  45. } else {
  46. res.writeHead(500, { 'Content-Type': 'application/json' });
  47. res.end(JSON.stringify({ error: result.error || 'VIP抠图失败' }));
  48. }
  49. } catch (error) {
  50. console.error('[MattingVIP] 处理请求失败:', error);
  51. res.writeHead(500, { 'Content-Type': 'application/json' });
  52. res.end(JSON.stringify({ error: '处理失败', details: error.message }));
  53. }
  54. });
  55. req.on('error', (error) => {
  56. console.error('[MattingVIP] 请求错误:', error);
  57. res.writeHead(500, { 'Content-Type': 'application/json' });
  58. res.end(JSON.stringify({ error: 'Request error', details: error.message }));
  59. });
  60. }
  61. /**
  62. * 对 base64 图片进行 VIP 抠图(使用 BiRefNet)
  63. * @param {string} imageBase64 - base64 图片数据
  64. * @returns {Promise<Object>} 处理结果
  65. */
  66. static async processMatting(imageBase64) {
  67. const tempDir = os.tmpdir();
  68. const timestamp = Date.now();
  69. const inputPath = path.join(tempDir, `matting_vip_input_${timestamp}.png`);
  70. const outputPath = path.join(tempDir, `matting_vip_output_${timestamp}.png`);
  71. try {
  72. // 清理 Base64 数据:移除可能存在的 data URL 前缀
  73. let cleanImageData = imageBase64;
  74. if (cleanImageData.includes(',')) {
  75. const base64Index = cleanImageData.indexOf(',');
  76. cleanImageData = cleanImageData.substring(base64Index + 1);
  77. }
  78. cleanImageData = cleanImageData.trim();
  79. // 将 base64 转换为图片文件
  80. const imageBuffer = Buffer.from(cleanImageData, 'base64');
  81. await fs.writeFile(inputPath, imageBuffer);
  82. console.log('[MattingVIP] 临时文件已创建:', inputPath);
  83. // 调用 Python 脚本进行抠图(使用 BiRefNet)
  84. const pythonScript = path.join(__dirname, 'python', 'birefnet-matting.py');
  85. const result = await this.runPythonScript(pythonScript, inputPath, outputPath);
  86. if (!result.success) {
  87. return { success: false, error: result.error };
  88. }
  89. // 读取输出图片并转换为 base64
  90. const outputBuffer = await fs.readFile(outputPath);
  91. const outputBase64 = outputBuffer.toString('base64');
  92. // 清理临时文件
  93. await this.cleanupFiles([inputPath, outputPath]);
  94. console.log('[MattingVIP] ✓ VIP抠图完成');
  95. return { success: true, imageData: outputBase64 };
  96. } catch (error) {
  97. console.error('[MattingVIP] VIP抠图失败:', error);
  98. // 清理临时文件
  99. await this.cleanupFiles([inputPath, outputPath]).catch(() => {});
  100. return { success: false, error: error.message };
  101. }
  102. }
  103. /**
  104. * 运行 Python 脚本
  105. * @param {string} scriptPath - Python 脚本路径
  106. * @param {string} inputPath - 输入文件路径
  107. * @param {string} outputPath - 输出文件路径
  108. * @returns {Promise<Object>} 执行结果
  109. */
  110. static async runPythonScript(scriptPath, inputPath, outputPath) {
  111. return new Promise((resolve) => {
  112. // 检查是否有虚拟环境
  113. const venvPython = path.join(__dirname, 'python', 'venv', 'Scripts', 'python.exe');
  114. let pythonCmd;
  115. // 优先使用虚拟环境中的 Python
  116. if (process.platform === 'win32' && fsSync.existsSync(venvPython)) {
  117. pythonCmd = venvPython;
  118. } else {
  119. pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
  120. }
  121. const python = spawn(pythonCmd, [scriptPath, inputPath, outputPath], {
  122. cwd: path.join(__dirname, 'python')
  123. });
  124. let stdout = '';
  125. let stderr = '';
  126. python.stdout.on('data', (data) => {
  127. stdout += data.toString();
  128. console.log('[MattingVIP] Python输出:', data.toString().trim());
  129. });
  130. python.stderr.on('data', (data) => {
  131. stderr += data.toString();
  132. console.error('[MattingVIP] Python错误:', data.toString().trim());
  133. });
  134. python.on('close', (code) => {
  135. if (code === 0) {
  136. resolve({ success: true });
  137. } else {
  138. console.error('[MattingVIP] Python 脚本执行失败:', stderr);
  139. resolve({ success: false, error: stderr || 'Python 脚本执行失败' });
  140. }
  141. });
  142. python.on('error', (error) => {
  143. console.error('[MattingVIP] 启动 Python 失败:', error);
  144. resolve({ success: false, error: error.message });
  145. });
  146. });
  147. }
  148. /**
  149. * 清理临时文件
  150. * @param {string[]} filePaths - 文件路径数组
  151. */
  152. static async cleanupFiles(filePaths) {
  153. for (const filePath of filePaths) {
  154. try {
  155. await fs.unlink(filePath);
  156. } catch (error) {
  157. // 忽略文件不存在的错误
  158. }
  159. }
  160. }
  161. }
  162. module.exports = MattingVIP;