mmdhx commited on
Commit
ab8d53d
·
verified ·
1 Parent(s): 15e4dab

Update egg-api.js

Browse files
Files changed (1) hide show
  1. egg-api.js +168 -154
egg-api.js CHANGED
@@ -1,175 +1,189 @@
1
  const express = require('express');
 
 
2
  const cv = require('opencv4nodejs');
3
  const Jimp = require('jimp');
4
- const ffmpeg = require('fluent-ffmpeg');
5
  const axios = require('axios');
6
- const fs = require('fs');
7
- const os = require('os');
8
- const path = require('path');
9
- const app = express();
10
- const PORT = 7860;
11
-
12
- app.use(express.json());
13
 
14
- // GIF转换为视频
15
- async function gifToVideo(inputGifPath, outputVideoPath) {
16
- console.log('开始将GIF转换为视频...');
17
- return new Promise((resolve, reject) => {
18
- ffmpeg(inputGifPath)
19
- .inputFormat('gif')
20
- .output(outputVideoPath)
21
- .on('start', (commandLine) => {
22
- console.log('FFmpeg 命令:', commandLine);
23
- })
24
- .on('end', () => {
25
- console.log('GIF成功转换为视频:', outputVideoPath);
26
- resolve();
27
- })
28
- .on('error', (err) => {
29
- console.error('GIF转换为视频时出错:', err);
30
- reject(err);
31
- })
32
- .run();
33
- });
34
- }
35
-
36
- // 添加将输出视频转换为 GIF 的函数
37
- async function videoToGif(inputVideoPath, outputGifPath) {
38
- console.log('开始将视频转换为GIF...');
39
- return new Promise((resolve, reject) => {
40
- ffmpeg(inputVideoPath)
41
- .output(outputGifPath)
42
- .on('start', (commandLine) => {
43
- console.log('FFmpeg 命令:', commandLine);
44
- })
45
- .on('end', () => {
46
- console.log('视频成功转换为GIF:', outputGifPath);
47
- resolve();
48
- })
49
- .on('error', (err) => {
50
- console.error('视频转换为GIF时出错:', err);
51
- reject(err);
52
- })
53
- .run();
54
- });
55
- }
56
 
57
  // 从文件夹加载所有分类器
58
- async function loadClassifiersFromFolder(folderPath) {
59
- console.log(`开始从文件夹 ${folderPath} 加载分类器...`);
60
- const classifiers = [];
61
-
62
- const files = fs.readdirSync(folderPath);
63
- for (const file of files) {
64
- const filePath = path.join(folderPath, file);
65
- const classifier = new cv.CascadeClassifier(filePath);
66
- classifiers.push(classifier);
67
- console.log(`加载分类器: ${file}`);
68
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- console.log('分类器加载完成');
71
- return classifiers;
72
- }
73
 
74
- // 处理 GIF 的 Express 接口
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  app.get('/processGif', async (req, res) => {
76
- try {
77
- const { gifUrl } = req.query;
78
- const tempDir = os.tmpdir();
79
- const tempGifPath = path.join(tempDir, 'temp.gif');
80
- const tempVideoPath = path.join(tempDir, 'temp_video.mp4');
81
- const outputGifPath = path.join(tempDir, 'output.gif');
82
- const replacementImagePath = path.join(__dirname, 'replacement_face.png');
83
- const classifiersFolder = path.join(__dirname, 'classifiers');
84
-
85
- console.log('接收到GIF处理请求:', gifUrl);
86
-
87
- // 下载 GIF 到本地
88
- console.log('开始下载GIF...');
89
- const response = await axios.get(gifUrl, { responseType: 'stream' });
90
- const writer = fs.createWriteStream(tempGifPath);
91
- response.data.pipe(writer);
92
-
93
- // 等待文件写入完成
94
- await new Promise((resolve, reject) => {
95
- writer.on('finish', resolve);
96
- writer.on('error', reject);
97
- });
98
 
99
- console.log('GIF下载完成:', tempGifPath);
100
-
101
- // 从文件夹动态加载分类器
102
- const classifiers = await loadClassifiersFromFolder(classifiersFolder);
103
-
104
- // 将GIF转换为视频
105
- await gifToVideo(tempGifPath, tempVideoPath);
106
-
107
- // 读取视频文件
108
- console.log('开始读取视频文件...');
109
- const videoCapture = new cv.VideoCapture(tempVideoPath);
110
- const frameWidth = videoCapture.get(cv.CAP_PROP_FRAME_WIDTH);
111
- const frameHeight = videoCapture.get(cv.CAP_PROP_FRAME_HEIGHT);
112
- const originalFps = videoCapture.get(cv.CAP_PROP_FPS);
113
- const videoWriter = new cv.VideoWriter(path.join(tempDir, 'out.mp4'), cv.VideoWriter.fourcc('avc1'), originalFps, new cv.Size(frameWidth, frameHeight));
114
- console.log('视频文件读取完成');
115
-
116
-
117
- // 处理每一帧
118
- console.log('开始处理每一帧...');
119
- while (true) {
120
- const frame = videoCapture.read();
121
- if (frame.empty) break;
122
-
123
- const grayFrame = frame.bgrToGray();
124
- const faces = [];
125
- classifiers.forEach(classifier => {
126
- const allfaces = classifier.detectMultiScale(grayFrame).objects;
127
- faces.push(...allfaces);
128
- });
129
-
130
- const replacementFace = await Jimp.read(replacementImagePath);
131
-
132
- if (Array.isArray(faces) && faces.length > 0) {
133
- console.log(`检测到 ${faces.length} 张脸`);
134
- for (const rect of faces) {
135
- const resizedSubstituteImage = await replacementFace.clone().resize(rect.width, rect.height);
136
- const substituteRegion = frame.getRegion(rect);
137
- for (let y = 0; y < rect.height; y++) {
138
- for (let x = 0; x < rect.width; x++) {
139
- const { r, g, b, a } = Jimp.intToRGBA(resizedSubstituteImage.getPixelColor(x, y));
140
- if (a > 0) {
141
- const color = new cv.Vec3(b, g, r);
142
- substituteRegion.set(y, x, color);
143
- }
144
- }
145
- }
146
- const buffer = await resizedSubstituteImage.getBufferAsync(Jimp.MIME_PNG);
147
- const substituteMat = cv.imdecode(Buffer.from(buffer), cv.IMREAD_UNCHANGED);
148
- substituteMat.copyTo(frame.getRegion(rect));
149
- }
150
- } else {
151
- console.error('未检测到人脸或faces数组为空');
152
  }
 
 
 
153
 
154
- videoWriter.write(frame);
155
- }
156
 
157
- videoCapture.release();
158
- videoWriter.release();
159
- cv.destroyAllWindows();
160
- console.log('帧处理完成');
161
 
162
- await videoToGif(path.join(tempDir, 'out.mp4'), outputGifPath);
 
 
 
 
 
163
 
164
- res.set('Content-Type', 'image/gif');
165
- res.sendFile(outputGifPath);
166
- } catch (error) {
167
- console.error('处理GIF时出错:', error);
168
- res.status(500).send('内部服务器错误');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  }
 
170
  });
171
 
 
 
 
 
 
172
 
173
- app.listen(PORT, () => {
174
- console.log(`服务器运行在端口 ${PORT}`);
175
  });
 
1
  const express = require('express');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
  const cv = require('opencv4nodejs');
5
  const Jimp = require('jimp');
 
6
  const axios = require('axios');
7
+ const sharp = require('sharp');
8
+ const serveStatic = require('serve-static');
9
+ const ffmpeg = require('fluent-ffmpeg');
 
 
 
 
10
 
11
+ const app = express();
12
+ const port = 7860;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  // 从文件夹加载所有分类器
15
+ const loadClassifiersFromFolder = async (folderPath) => {
16
+ const classifiers = [];
17
+ const files = fs.readdirSync(folderPath);
18
+ for (const file of files) {
19
+ const filePath = path.join(folderPath, file);
20
+ const classifier = new cv.CascadeClassifier(filePath);
21
+ classifiers.push(classifier);
22
+ }
23
+ return classifiers;
24
+ };
25
+
26
+ // 下载并转换图像到内存中
27
+ const downloadAndConvertImage = async (url) => {
28
+ try {
29
+ const response = await axios.get(url, { responseType: 'arraybuffer' });
30
+ return await sharp(response.data).toFormat('jpeg').toBuffer();
31
+ } catch (error) {
32
+ throw new Error('Error downloading or converting image: ' + error.message);
33
+ }
34
+ };
35
+
36
+ // 图像处理函数
37
+ const processImage = async (imageBuffer, classifiersFolder, replacementImageBuffer) => {
38
+ const classifiers = await loadClassifiersFromFolder(classifiersFolder);
39
+ const img = cv.imdecode(imageBuffer);
40
+ const grayImg = img.bgrToGray();
41
+ const allFaces = classifiers.flatMap(classifier => classifier.detectMultiScale(grayImg).objects);
42
+
43
+ if (allFaces.length === 0) throw new Error('No faces detected in the image.');
44
+
45
+ const replacementFace = await Jimp.read(replacementImageBuffer);
46
+ allFaces.forEach((faceRect) => {
47
+ const resizedReplacementFace = replacementFace.resize(faceRect.width, faceRect.height);
48
+ resizedReplacementFace.scan(0, 0, resizedReplacementFace.bitmap.width, resizedReplacementFace.bitmap.height, (x, y, idx) => {
49
+ if (resizedReplacementFace.bitmap.data[idx + 3] > 0) {
50
+ img.set(faceRect.y + y, faceRect.x + x, new cv.Vec3(
51
+ resizedReplacementFace.bitmap.data[idx + 2],
52
+ resizedReplacementFace.bitmap.data[idx + 1],
53
+ resizedReplacementFace.bitmap.data[idx]
54
+ ));
55
+ }
56
+ });
57
+ });
58
 
59
+ return cv.imencode('.jpg', img);
60
+ };
 
61
 
62
+ // GIF转换为视频
63
+ const gifToVideo = async (inputGifPath, outputVideoPath) => {
64
+ return new Promise((resolve, reject) => {
65
+ ffmpeg(inputGifPath)
66
+ .inputFormat('gif')
67
+ .output(outputVideoPath)
68
+ .on('end', resolve)
69
+ .on('error', reject)
70
+ .run();
71
+ });
72
+ };
73
+
74
+ // 视频转换为GIF
75
+ const videoToGif = async (inputVideoPath, outputGifPath) => {
76
+ return new Promise((resolve, reject) => {
77
+ ffmpeg(inputVideoPath)
78
+ .output(outputGifPath)
79
+ .on('end', resolve)
80
+ .on('error', reject)
81
+ .run();
82
+ });
83
+ };
84
+
85
+ // 处理 GIF 的接口
86
  app.get('/processGif', async (req, res) => {
87
+ const { gifUrl } = req.query;
88
+
89
+ if (!gifUrl) {
90
+ return res.status(400).send('GIF URL is required');
91
+ }
92
+
93
+ const tempDir = path.join(__dirname, 'temp');
94
+ const tempGifPath = path.join(tempDir, 'temp.gif');
95
+ const tempVideoPath = path.join(tempDir, 'temp_video.mp4');
96
+ const outputGifPath = path.join(tempDir, 'output.gif');
97
+ const classifiersFolder = path.join(__dirname, 'classifiers');
98
+ const replacementImagePath = path.join(__dirname, 'replacement_face.png');
99
+
100
+ try {
101
+ // 下载 GIF 到本地
102
+ const response = await axios.get(gifUrl, { responseType: 'stream' });
103
+ const writer = fs.createWriteStream(tempGifPath);
104
+ response.data.pipe(writer);
105
+ await new Promise((resolve, reject) => {
106
+ writer.on('finish', resolve);
107
+ writer.on('error', reject);
108
+ });
109
 
110
+ // 从文件夹动态加载分类器
111
+ const classifiers = await loadClassifiersFromFolder(classifiersFolder);
112
+ await gifToVideo(tempGifPath, tempVideoPath);
113
+
114
+ const videoCapture = new cv.VideoCapture(tempVideoPath);
115
+ const frameWidth = videoCapture.get(cv.CAP_PROP_FRAME_WIDTH);
116
+ const frameHeight = videoCapture.get(cv.CAP_PROP_FRAME_HEIGHT);
117
+ const originalFps = videoCapture.get(cv.CAP_PROP_FPS);
118
+ const videoWriter = new cv.VideoWriter(path.join(tempDir, 'out.mp4'), cv.VideoWriter.fourcc('avc1'), originalFps, new cv.Size(frameWidth, frameHeight));
119
+
120
+ while (true) {
121
+ const frame = videoCapture.read();
122
+ if (frame.empty) break;
123
+
124
+ const grayFrame = frame.bgrToGray();
125
+ const faces = classifiers.flatMap(classifier => classifier.detectMultiScale(grayFrame).objects);
126
+
127
+ const replacementFace = await Jimp.read(replacementImagePath);
128
+ if (faces.length > 0) {
129
+ faces.forEach(rect => {
130
+ const resizedSubstituteImage = replacementFace.resize(rect.width, rect.height);
131
+ resizedSubstituteImage.scan(0, 0, resizedSubstituteImage.bitmap.width, resizedSubstituteImage.bitmap.height, (x, y, idx) => {
132
+ if (resizedSubstituteImage.bitmap.data[idx + 3] > 0) {
133
+ frame.set(rect.y + y, rect.x + x, new cv.Vec3(
134
+ resizedSubstituteImage.bitmap.data[idx + 2],
135
+ resizedSubstituteImage.bitmap.data[idx + 1],
136
+ resizedSubstituteImage.bitmap.data[idx]
137
+ ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  }
139
+ });
140
+ });
141
+ }
142
 
143
+ videoWriter.write(frame);
144
+ }
145
 
146
+ videoCapture.release();
147
+ videoWriter.release();
148
+ await videoToGif(path.join(tempDir, 'out.mp4'), outputGifPath);
 
149
 
150
+ res.set('Content-Type', 'image/gif').sendFile(outputGifPath);
151
+ } catch (error) {
152
+ console.error('Error processing GIF:', error);
153
+ res.status(500).send('Error processing GIF');
154
+ }
155
+ });
156
 
157
+ // 图像处理接口
158
+ app.get('/process-image', async (req, res) => {
159
+ const { imageUrl, replacementImageUrl } = req.query;
160
+
161
+ if (!imageUrl || !replacementImageUrl) {
162
+ return res.status(400).send('Both image URL and replacement image URL are required');
163
+ }
164
+
165
+ const classifiersFolder = './classifiers';
166
+ try {
167
+ const imageBuffer = await downloadAndConvertImage(imageUrl);
168
+ const replacementImageBuffer = await downloadAndConvertImage(replacementImageUrl);
169
+ const outputBuffer = await processImage(imageBuffer, classifiersFolder, replacementImageBuffer);
170
+ res.set('Content-Type', 'image/jpeg').send(outputBuffer);
171
+ } catch (error) {
172
+ if (error.message === 'No faces detected in the image.') {
173
+ res.status(404).send('No faces detected in the image.');
174
+ } else {
175
+ console.error('Error processing image:', error);
176
+ res.status(500).send('Error processing image');
177
  }
178
+ }
179
  });
180
 
181
+ // 静态文件服务
182
+ app.use(serveStatic(__dirname));
183
+ app.get('/', (req, res) => {
184
+ res.sendFile(path.join(__dirname, 'index.html'));
185
+ });
186
 
187
+ app.listen(port, () => {
188
+ console.log(`Server is running on http://localhost:${port}`);
189
  });