const express = require('express'); const cv = require('opencv4nodejs'); const Jimp = require('jimp'); const ffmpeg = require('fluent-ffmpeg'); const axios = require('axios'); const fs = require('fs'); const os = require('os'); const path = require('path'); const app = express(); const PORT = 7860; app.use(express.json()); // GIF转换为视频 async function gifToVideo(inputGifPath, outputVideoPath) { console.log('开始将GIF转换为视频...'); return new Promise((resolve, reject) => { ffmpeg(inputGifPath) .inputFormat('gif') .output(outputVideoPath) .on('start', (commandLine) => { console.log('FFmpeg 命令:', commandLine); }) .on('end', () => { console.log('GIF成功转换为视频:', outputVideoPath); resolve(); }) .on('error', (err) => { console.error('GIF转换为视频时出错:', err); reject(err); }) .run(); }); } // 添加将输出视频转换为 GIF 的函数 async function videoToGif(inputVideoPath, outputGifPath) { console.log('开始将视频转换为GIF...'); return new Promise((resolve, reject) => { ffmpeg(inputVideoPath) .output(outputGifPath) .on('start', (commandLine) => { console.log('FFmpeg 命令:', commandLine); }) .on('end', () => { console.log('视频成功转换为GIF:', outputGifPath); resolve(); }) .on('error', (err) => { console.error('视频转换为GIF时出错:', err); reject(err); }) .run(); }); } // 从文件夹加载所有分类器 async function loadClassifiersFromFolder(folderPath) { console.log(`开始从文件夹 ${folderPath} 加载分类器...`); const classifiers = []; const files = fs.readdirSync(folderPath); for (const file of files) { const filePath = path.join(folderPath, file); const classifier = new cv.CascadeClassifier(filePath); classifiers.push(classifier); console.log(`加载分类器: ${file}`); } console.log('分类器加载完成'); return classifiers; } // 处理 GIF 的 Express 接口 app.get('/processGif', async (req, res) => { try { const { gifUrl } = req.query; const tempDir = os.tmpdir(); const tempGifPath = path.join(tempDir, 'temp.gif'); const tempVideoPath = path.join(tempDir, 'temp_video.mp4'); const outputGifPath = path.join(tempDir, 'output.gif'); const replacementImagePath = path.join(__dirname, 'replacement_face.png'); const classifiersFolder = path.join(__dirname, 'classifiers'); console.log('接收到GIF处理请求:', gifUrl); // 下载 GIF 到本地 console.log('开始下载GIF...'); const response = await axios.get(gifUrl, { responseType: 'stream' }); const writer = fs.createWriteStream(tempGifPath); response.data.pipe(writer); // 等待文件写入完成 await new Promise((resolve, reject) => { writer.on('finish', resolve); writer.on('error', reject); }); console.log('GIF下载完成:', tempGifPath); // 从文件夹动态加载分类器 const classifiers = await loadClassifiersFromFolder(classifiersFolder); // 将GIF转换为视频 await gifToVideo(tempGifPath, tempVideoPath); // 读取视频文件 console.log('开始读取视频文件...'); const videoCapture = new cv.VideoCapture(tempVideoPath); const frameWidth = videoCapture.get(cv.CAP_PROP_FRAME_WIDTH); const frameHeight = videoCapture.get(cv.CAP_PROP_FRAME_HEIGHT); const originalFps = videoCapture.get(cv.CAP_PROP_FPS); const videoWriter = new cv.VideoWriter(path.join(tempDir, 'out.mp4'), cv.VideoWriter.fourcc('avc1'), originalFps, new cv.Size(frameWidth, frameHeight)); console.log('视频文件读取完成'); // 处理每一帧 console.log('开始处理每一帧...'); while (true) { const frame = videoCapture.read(); if (frame.empty) break; const grayFrame = frame.bgrToGray(); const faces = []; classifiers.forEach(classifier => { const allfaces = classifier.detectMultiScale(grayFrame).objects; faces.push(...allfaces); }); const replacementFace = await Jimp.read(replacementImagePath); if (Array.isArray(faces) && faces.length > 0) { console.log(`检测到 ${faces.length} 张脸`); for (const rect of faces) { const resizedSubstituteImage = await replacementFace.clone().resize(rect.width, rect.height); const substituteRegion = frame.getRegion(rect); for (let y = 0; y < rect.height; y++) { for (let x = 0; x < rect.width; x++) { const { r, g, b, a } = Jimp.intToRGBA(resizedSubstituteImage.getPixelColor(x, y)); if (a > 0) { const color = new cv.Vec3(b, g, r); substituteRegion.set(y, x, color); } } } const buffer = await resizedSubstituteImage.getBufferAsync(Jimp.MIME_PNG); const substituteMat = cv.imdecode(Buffer.from(buffer), cv.IMREAD_UNCHANGED); substituteMat.copyTo(frame.getRegion(rect)); } } else { console.error('未检测到人脸或faces数组为空'); } videoWriter.write(frame); } videoCapture.release(); videoWriter.release(); cv.destroyAllWindows(); console.log('帧处理完成'); await videoToGif(path.join(tempDir, 'out.mp4'), outputGifPath); res.set('Content-Type', 'image/gif'); res.sendFile(outputGifPath); } catch (error) { console.error('处理GIF时出错:', error); res.status(500).send('内部服务器错误'); } }); app.listen(PORT, () => { console.log(`服务器运行在端口 ${PORT}`); });