|
@@ -4,7 +4,10 @@ import { getLipSyncServer } from '../utils/lipSyncConfig';
|
4
|
4
|
|
5
|
5
|
// 从配置中获取服务器地址
|
6
|
6
|
const getServerUrl = () => {
|
7
|
|
- return getLipSyncServer();
|
|
7
|
+ const serverConfig = getLipSyncServer();
|
|
8
|
+ const serverUrl = serverConfig.apiUrl || serverConfig;
|
|
9
|
+ // 移除末尾的斜杠,避免拼接时出现双斜杠
|
|
10
|
+ return serverUrl.replace(/\/$/, '');
|
8
|
11
|
};
|
9
|
12
|
|
10
|
13
|
/**
|
|
@@ -39,74 +42,152 @@ const testServer = async (serverUrl) => {
|
39
|
42
|
};
|
40
|
43
|
|
41
|
44
|
/**
|
42
|
|
- * 提交对口型合成任务
|
43
|
|
- * @param {string} videoUrl - 视频URL
|
44
|
|
- * @param {string} audioUrl - 音频URL
|
45
|
|
- * @returns {Promise<string>} - 返回任务ID
|
|
45
|
+ * 提交对口型任务
|
|
46
|
+ * @param {string} videoUrl 视频URL
|
|
47
|
+ * @param {string} audioUrl 音频URL
|
|
48
|
+ * @returns {Promise<{id: string}>} 任务信息
|
46
|
49
|
*/
|
47
|
50
|
const submitLipSyncTask = async (videoUrl, audioUrl) => {
|
48
|
51
|
try {
|
49
|
|
- if (!videoUrl || !audioUrl) {
|
50
|
|
- throw new Error('视频URL和音频URL不能为空');
|
51
|
|
- }
|
|
52
|
+ console.log('开始提交对口型任务...');
|
|
53
|
+ console.log('视频URL:', videoUrl);
|
|
54
|
+ console.log('音频URL:', audioUrl);
|
52
|
55
|
|
|
56
|
+ // 获取服务器地址
|
53
|
57
|
const serverUrl = getServerUrl();
|
54
|
|
- const response = await axios.post(`${serverUrl}/direct-lip-sync/`, {
|
|
58
|
+ if (!serverUrl) {
|
|
59
|
+ throw new Error('未配置对口型服务器地址');
|
|
60
|
+ }
|
|
61
|
+
|
|
62
|
+ // 构建请求数据
|
|
63
|
+ const requestData = {
|
55
|
64
|
video_url: videoUrl,
|
56
|
65
|
audio_url: audioUrl
|
57
|
|
- }, {
|
|
66
|
+ };
|
|
67
|
+
|
|
68
|
+ const apiUrl = `${serverUrl}/direct-lip-sync/`;
|
|
69
|
+ console.log('发送对口型任务请求:', {
|
|
70
|
+ url: apiUrl,
|
|
71
|
+ data: requestData
|
|
72
|
+ });
|
|
73
|
+
|
|
74
|
+ // 发送POST请求获取任务ID
|
|
75
|
+ const response = await axios.post(apiUrl, requestData, {
|
58
|
76
|
headers: {
|
59
|
77
|
'accept': 'application/json, text/plain, */*',
|
|
78
|
+ 'accept-language': 'zh-CN,zh;q=0.9',
|
60
|
79
|
'content-type': 'application/json',
|
61
|
80
|
'origin': 'https://heygem.fyshark.com',
|
62
|
|
- 'referer': 'https://heygem.fyshark.com/'
|
|
81
|
+ 'priority': 'u=1, i',
|
|
82
|
+ 'referer': 'https://heygem.fyshark.com/',
|
|
83
|
+ 'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126"',
|
|
84
|
+ 'sec-ch-ua-mobile': '?0',
|
|
85
|
+ 'sec-ch-ua-platform': '"macOS"',
|
|
86
|
+ 'sec-fetch-dest': 'empty',
|
|
87
|
+ 'sec-fetch-mode': 'cors',
|
|
88
|
+ 'sec-fetch-site': 'cross-site',
|
|
89
|
+ 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) jianyin_agent/2.4.3 Chrome/126.0.6478.234 Electron/31.7.6 Safari/537.36'
|
63
|
90
|
}
|
64
|
91
|
});
|
65
|
92
|
|
66
|
|
- if (response.data && response.data.length > 0 && response.data[0].task_id) {
|
67
|
|
- console.log('对口型任务提交成功:', response.data[0]);
|
68
|
|
- return response.data[0].task_id;
|
69
|
|
- } else {
|
|
93
|
+ console.log('对口型任务提交响应:', response.data);
|
|
94
|
+
|
|
95
|
+ // 检查响应数据
|
|
96
|
+ if (!response.data || !response.data.id) {
|
70
|
97
|
console.error('对口型任务提交失败:', response.data);
|
71
|
98
|
throw new Error('提交任务失败,未返回任务ID');
|
72
|
99
|
}
|
|
100
|
+
|
|
101
|
+ return {
|
|
102
|
+ id: response.data.id
|
|
103
|
+ };
|
73
|
104
|
} catch (error) {
|
74
|
105
|
console.error('提交对口型任务错误:', error);
|
75
|
|
- toast.error(`提交对口型任务失败: ${error.message}`);
|
|
106
|
+
|
|
107
|
+ // 处理服务器忙碌的情况
|
|
108
|
+ if (error.response && error.response.status === 500 && error.response.data && error.response.data.detail === '503: 服务器忙碌中,请稍后再试') {
|
|
109
|
+ toast.error('服务器正在处理其他任务,请等待当前任务完成后再试');
|
|
110
|
+ throw new Error('服务器正在处理其他任务,请等待当前任务完成后再试');
|
|
111
|
+ }
|
|
112
|
+
|
76
|
113
|
throw error;
|
77
|
114
|
}
|
78
|
115
|
};
|
79
|
116
|
|
80
|
117
|
/**
|
81
|
118
|
* 获取对口型任务信息
|
82
|
|
- * @param {string} taskId - 任务ID
|
83
|
|
- * @returns {Promise<Object>} - 返回任务信息
|
|
119
|
+ * @param {string} taskId 任务ID
|
|
120
|
+ * @returns {Promise<{status: string, progress: number, result_url?: string}>} 任务信息
|
84
|
121
|
*/
|
85
|
122
|
const getLipSyncTaskInfo = async (taskId) => {
|
86
|
123
|
try {
|
87
|
|
- if (!taskId) {
|
88
|
|
- throw new Error('任务ID不能为空');
|
89
|
|
- }
|
|
124
|
+ console.log('获取对口型任务信息:', taskId);
|
90
|
125
|
|
|
126
|
+ // 获取服务器地址
|
91
|
127
|
const serverUrl = getServerUrl();
|
92
|
|
- const response = await axios.get(`${serverUrl}/direct-lip-sync/?task_id=${taskId}`, {
|
|
128
|
+ if (!serverUrl) {
|
|
129
|
+ throw new Error('未配置对口型服务器地址');
|
|
130
|
+ }
|
|
131
|
+
|
|
132
|
+ const apiUrl = `${serverUrl}/direct-lip-sync/${taskId}`;
|
|
133
|
+ console.log('获取对口型任务状态:', apiUrl);
|
|
134
|
+
|
|
135
|
+ // 发送GET请求获取任务状态
|
|
136
|
+ const response = await axios.get(apiUrl, {
|
93
|
137
|
headers: {
|
94
|
138
|
'accept': 'application/json, text/plain, */*',
|
|
139
|
+ 'content-type': 'application/json',
|
95
|
140
|
'origin': 'https://heygem.fyshark.com',
|
96
|
|
- 'referer': 'https://heygem.fyshark.com/'
|
|
141
|
+ 'referer': 'https://heygem.fyshark.com/',
|
|
142
|
+ 'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"',
|
|
143
|
+ 'sec-ch-ua-mobile': '?0',
|
|
144
|
+ 'sec-ch-ua-platform': '"macOS"',
|
|
145
|
+ 'sec-fetch-dest': 'empty',
|
|
146
|
+ 'sec-fetch-mode': 'cors',
|
|
147
|
+ 'sec-fetch-site': 'cross-site',
|
|
148
|
+ 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
|
97
|
149
|
}
|
98
|
150
|
});
|
99
|
151
|
|
100
|
|
- if (response.data && response.data.length > 0) {
|
101
|
|
- console.log('对口型任务信息:', response.data[0]);
|
102
|
|
- return response.data[0];
|
103
|
|
- } else {
|
104
|
|
- console.error('获取任务信息失败:', response.data);
|
105
|
|
- throw new Error('获取任务信息失败,未返回有效数据');
|
|
152
|
+ console.log('对口型任务状态响应:', response.data);
|
|
153
|
+
|
|
154
|
+ // 检查响应数据
|
|
155
|
+ if (!response.data) {
|
|
156
|
+ throw new Error('获取任务状态失败,响应数据为空');
|
|
157
|
+ }
|
|
158
|
+
|
|
159
|
+ // 检查任务ID是否匹配
|
|
160
|
+ if (response.data.id !== parseInt(taskId)) {
|
|
161
|
+ throw new Error('未找到指定任务ID的信息');
|
106
|
162
|
}
|
|
163
|
+
|
|
164
|
+ // 转换状态码为状态文本
|
|
165
|
+ let statusText = 'processing';
|
|
166
|
+ if (response.data.status === 1) {
|
|
167
|
+ statusText = 'processing';
|
|
168
|
+ } else if (response.data.status === 2) {
|
|
169
|
+ statusText = 'completed';
|
|
170
|
+ } else if (response.data.status === 3) {
|
|
171
|
+ statusText = 'failed';
|
|
172
|
+ }
|
|
173
|
+
|
|
174
|
+ // 如果有结果URL,拼接完整的下载地址
|
|
175
|
+ let resultUrl = response.data.result_url;
|
|
176
|
+ if (resultUrl) {
|
|
177
|
+ // 确保路径以/开头
|
|
178
|
+ const formattedPath = resultUrl.startsWith('/') ? resultUrl : `/${resultUrl}`;
|
|
179
|
+ // 修改下载链接的端口为8383
|
|
180
|
+ const downloadUrl = serverUrl.replace(/-80\./, '-8383.');
|
|
181
|
+ resultUrl = `${downloadUrl}/download${formattedPath}`;
|
|
182
|
+ }
|
|
183
|
+
|
|
184
|
+ return {
|
|
185
|
+ status: statusText,
|
|
186
|
+ progress: response.data.progress || 0,
|
|
187
|
+ result_url: resultUrl
|
|
188
|
+ };
|
107
|
189
|
} catch (error) {
|
108
|
|
- console.error('获取对口型任务信息错误:', error);
|
109
|
|
- toast.error(`获取任务状态失败: ${error.message}`);
|
|
190
|
+ console.error('获取对口型任务信息失败:', error);
|
110
|
191
|
throw error;
|
111
|
192
|
}
|
112
|
193
|
};
|
|
@@ -127,59 +208,69 @@ const getLipSyncResultUrl = (resultPath) => {
|
127
|
208
|
};
|
128
|
209
|
|
129
|
210
|
/**
|
130
|
|
- * 提交对口型任务并定期检查进度直到完成
|
131
|
|
- * @param {string} videoUrl - 视频URL
|
132
|
|
- * @param {string} audioUrl - 音频URL
|
133
|
|
- * @param {function} onProgressUpdate - 进度更新回调函数 (progress, status) => void
|
134
|
|
- * @param {function} onComplete - 完成回调函数 (resultUrl) => void
|
135
|
|
- * @param {function} onError - 错误回调函数 (error) => void
|
136
|
|
- * @returns {Promise<void>}
|
|
211
|
+ * 处理对口型任务
|
|
212
|
+ * @param {string} videoUrl 视频URL
|
|
213
|
+ * @param {string} audioUrl 音频URL
|
|
214
|
+ * @param {Function} onProgress 进度回调函数
|
|
215
|
+ * @param {Function} onComplete 完成回调函数
|
|
216
|
+ * @param {Function} onError 错误回调函数
|
137
|
217
|
*/
|
138
|
|
-const processLipSyncTask = async (videoUrl, audioUrl, onProgressUpdate, onComplete, onError) => {
|
|
218
|
+const processLipSyncTask = async (videoUrl, audioUrl, onProgress, onComplete, onError) => {
|
139
|
219
|
try {
|
140
|
|
- // 提交任务
|
141
|
|
- const taskId = await submitLipSyncTask(videoUrl, audioUrl);
|
142
|
|
-
|
143
|
|
- // 定义检查间隔(秒)
|
144
|
|
- const checkInterval = 5;
|
145
|
|
- let completed = false;
|
146
|
|
-
|
147
|
|
- // 轮询检查任务状态
|
148
|
|
- const checkStatus = async () => {
|
|
220
|
+ console.log('开始处理对口型任务...');
|
|
221
|
+ console.log('视频URL:', videoUrl);
|
|
222
|
+ console.log('音频URL:', audioUrl);
|
|
223
|
+
|
|
224
|
+ // 1. 提交任务
|
|
225
|
+ const taskInfo = await submitLipSyncTask(videoUrl, audioUrl);
|
|
226
|
+ console.log('对口型任务提交成功:', taskInfo);
|
|
227
|
+
|
|
228
|
+ if (!taskInfo || !taskInfo.id) {
|
|
229
|
+ throw new Error('提交任务失败,未返回任务ID');
|
|
230
|
+ }
|
|
231
|
+
|
|
232
|
+ // 2. 轮询任务状态
|
|
233
|
+ const pollInterval = 5000; // 5秒
|
|
234
|
+ const maxAttempts = 120; // 最多等待10分钟
|
|
235
|
+ let attempts = 0;
|
|
236
|
+
|
|
237
|
+ const pollTaskStatus = async () => {
|
149
|
238
|
try {
|
150
|
|
- if (completed) return;
|
151
|
|
-
|
152
|
|
- const taskInfo = await getLipSyncTaskInfo(taskId);
|
153
|
|
-
|
|
239
|
+ const statusInfo = await getLipSyncTaskInfo(taskInfo.id);
|
|
240
|
+ console.log('对口型任务状态:', statusInfo);
|
|
241
|
+
|
154
|
242
|
// 更新进度
|
155
|
|
- if (onProgressUpdate) {
|
156
|
|
- onProgressUpdate(taskInfo.progress, taskInfo.status);
|
|
243
|
+ if (onProgress) {
|
|
244
|
+ onProgress(statusInfo.progress, statusInfo.status);
|
157
|
245
|
}
|
158
|
|
-
|
|
246
|
+
|
159
|
247
|
// 检查任务状态
|
160
|
|
- if (taskInfo.status === 2) { // 已完成
|
161
|
|
- completed = true;
|
162
|
|
- const resultUrl = getLipSyncResultUrl(taskInfo.result_url);
|
|
248
|
+ if (statusInfo.status === 'completed') {
|
163
|
249
|
if (onComplete) {
|
164
|
|
- onComplete(resultUrl, taskInfo);
|
|
250
|
+ onComplete(statusInfo.result_url, taskInfo);
|
165
|
251
|
}
|
166
|
|
- } else if (taskInfo.status === 1) { // 进行中
|
167
|
|
- // 继续轮询
|
168
|
|
- setTimeout(checkStatus, checkInterval * 1000);
|
169
|
|
- } else { // 错误状态
|
170
|
|
- completed = true;
|
171
|
|
- throw new Error(`任务状态异常: ${taskInfo.status}`);
|
|
252
|
+ return true;
|
|
253
|
+ } else if (statusInfo.status === 'failed') {
|
|
254
|
+ throw new Error('对口型任务处理失败');
|
172
|
255
|
}
|
173
|
|
- } catch (error) {
|
174
|
|
- if (onError) {
|
175
|
|
- onError(error);
|
|
256
|
+
|
|
257
|
+ // 继续轮询
|
|
258
|
+ attempts++;
|
|
259
|
+ if (attempts >= maxAttempts) {
|
|
260
|
+ throw new Error('对口型任务超时');
|
176
|
261
|
}
|
|
262
|
+
|
|
263
|
+ // 等待一段时间后继续轮询
|
|
264
|
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
265
|
+ return await pollTaskStatus();
|
|
266
|
+ } catch (error) {
|
|
267
|
+ console.error('轮询对口型任务状态失败:', error);
|
|
268
|
+ throw error;
|
177
|
269
|
}
|
178
|
270
|
};
|
179
|
|
-
|
|
271
|
+
|
180
|
272
|
// 开始轮询
|
181
|
|
- checkStatus();
|
182
|
|
-
|
|
273
|
+ await pollTaskStatus();
|
183
|
274
|
} catch (error) {
|
184
|
275
|
console.error('处理对口型任务出错:', error);
|
185
|
276
|
if (onError) {
|