Spaces:
Runtime error
Runtime error
File size: 6,455 Bytes
645e6ce 1b1f7c9 645e6ce 1b1f7c9 22fefdc 645e6ce cd2f3db 1b1f7c9 645e6ce cd2f3db d715e46 cd2f3db 9f4a0b0 d715e46 1b1f7c9 d715e46 9f4a0b0 cd2f3db ceb1c28 cd2f3db 1b1f7c9 4d6d7ae 645e6ce 1b1f7c9 cd2f3db 9f4a0b0 1b1f7c9 9f4a0b0 cd2f3db ceb1c28 cd2f3db 1b1f7c9 4d6d7ae 1b1f7c9 cd2f3db 1b1f7c9 cd2f3db 1b1f7c9 d715e46 cd2f3db 4d6d7ae cd2f3db 1b1f7c9 645e6ce 1b1f7c9 22fefdc 3186dbd 8f710ba 3186dbd 22fefdc 1b1f7c9 cd2f3db d715e46 cd2f3db ceb1c28 9f4a0b0 1b1f7c9 d715e46 1b1f7c9 a3e9728 ceb1c28 1b1f7c9 cd2f3db 1b1f7c9 cd2f3db 1b1f7c9 cd2f3db 1b1f7c9 645e6ce 4d6d7ae 1b1f7c9 cd2f3db 3186dbd 645e6ce 1b1f7c9 22fefdc 1b1f7c9 cd2f3db 1b1f7c9 645e6ce 084bd67 1b1f7c9 cd2f3db | 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 | 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}`);
});
|