分割成功
Browse files- youtube_sub.js +87 -57
youtube_sub.js
CHANGED
|
@@ -369,7 +369,7 @@ GM_addStyle(`
|
|
| 369 |
console.warn('%c[Subtitle Manager]', 'color: #ff9800', ...args);
|
| 370 |
}
|
| 371 |
};
|
| 372 |
-
|
| 373 |
// 注入样式的函数
|
| 374 |
function injectStyles(styles) {
|
| 375 |
try {
|
|
@@ -1188,6 +1188,88 @@ GM_addStyle(`
|
|
| 1188 |
}
|
| 1189 |
}
|
| 1190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1191 |
async initializeSubtitles() {
|
| 1192 |
debug.log('Starting subtitle initialization');
|
| 1193 |
|
|
@@ -1229,61 +1311,9 @@ GM_addStyle(`
|
|
| 1229 |
time: sub.startTime
|
| 1230 |
}));
|
| 1231 |
|
| 1232 |
-
this.updateTabText('chinese', '翻译中...');
|
| 1233 |
-
|
| 1234 |
try {
|
| 1235 |
-
const
|
| 1236 |
-
|
| 1237 |
-
method: 'POST',
|
| 1238 |
-
url: 'https://sonygod-flash.hf.space/ask',
|
| 1239 |
-
headers: {
|
| 1240 |
-
'Content-Type': 'application/json',
|
| 1241 |
-
'Accept': 'application/json'
|
| 1242 |
-
},
|
| 1243 |
-
data: JSON.stringify({
|
| 1244 |
-
prompt: `请将以下英文字幕翻译成中文,
|
| 1245 |
-
,必须遵循以下规则:
|
| 1246 |
-
1. 保持JSON格式返回
|
| 1247 |
-
2. 严格按照英文字幕一一对应翻译,不能合并或拆分句子
|
| 1248 |
-
3. 必须保持原有的时间戳
|
| 1249 |
-
4. translations数组长度必须和输入字幕数量完全一致
|
| 1250 |
-
|
| 1251 |
-
${JSON.stringify(subtitleData, null, 2)}
|
| 1252 |
-
请返回格式:
|
| 1253 |
-
{
|
| 1254 |
-
"translations": [
|
| 1255 |
-
{
|
| 1256 |
-
"text": "中文翻译",
|
| 1257 |
-
"time": 原时间戳
|
| 1258 |
-
}
|
| 1259 |
-
]
|
| 1260 |
-
}
|
| 1261 |
-
注意:翻译数量${subtitleData.length}句,必须返回相同数量的翻译结果`,
|
| 1262 |
-
model: 'GEMINI'
|
| 1263 |
-
}),
|
| 1264 |
-
onload: response => {
|
| 1265 |
-
if (response.status === 200) {
|
| 1266 |
-
resolve(JSON.parse(response.responseText));
|
| 1267 |
-
} else {
|
| 1268 |
-
reject(new Error(`HTTP error: ${response.status}`));
|
| 1269 |
-
}
|
| 1270 |
-
},
|
| 1271 |
-
onerror: error => reject(error)
|
| 1272 |
-
});
|
| 1273 |
-
});
|
| 1274 |
-
|
| 1275 |
-
const content = response.data.response;
|
| 1276 |
-
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*(?:```|\.{3})/);
|
| 1277 |
-
|
| 1278 |
-
|
| 1279 |
-
if (!jsonMatch) {
|
| 1280 |
-
throw new Error('No JSON content found in response');
|
| 1281 |
-
}
|
| 1282 |
-
|
| 1283 |
-
const parsedResponse = JSON.parse(jsonMatch[1]);
|
| 1284 |
-
const translations = parsedResponse.translations || [];
|
| 1285 |
-
|
| 1286 |
-
// Create Chinese subtitles array matching English structure
|
| 1287 |
this.subtitles.chinese = this.subtitles.english.map((sub, index) => ({
|
| 1288 |
startTime: sub.startTime,
|
| 1289 |
endTime: sub.endTime,
|
|
@@ -1293,11 +1323,11 @@ GM_addStyle(`
|
|
| 1293 |
endTime: sub.endTime
|
| 1294 |
}]
|
| 1295 |
}));
|
| 1296 |
-
|
| 1297 |
this.updateTabText('chinese', '中文');
|
| 1298 |
} catch (error) {
|
| 1299 |
-
this.updateTabText('chinese', '翻译失败');
|
| 1300 |
debug.error('Translation failed:', error);
|
|
|
|
| 1301 |
}
|
| 1302 |
}
|
| 1303 |
|
|
|
|
| 369 |
console.warn('%c[Subtitle Manager]', 'color: #ff9800', ...args);
|
| 370 |
}
|
| 371 |
};
|
| 372 |
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
| 373 |
// 注入样式的函数
|
| 374 |
function injectStyles(styles) {
|
| 375 |
try {
|
|
|
|
| 1188 |
}
|
| 1189 |
}
|
| 1190 |
|
| 1191 |
+
async translateInChunks(subtitleData) {
|
| 1192 |
+
const CHUNK_SIZE = 100;
|
| 1193 |
+
const chunks = [];
|
| 1194 |
+
const translations = [];
|
| 1195 |
+
const DELAY_BETWEEN_CHUNKS = 1000; // 1 second delay
|
| 1196 |
+
|
| 1197 |
+
// Split into chunks
|
| 1198 |
+
for (let i = 0; i < subtitleData.length; i += CHUNK_SIZE) {
|
| 1199 |
+
chunks.push(subtitleData.slice(i, i + CHUNK_SIZE));
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
// Process each chunk
|
| 1203 |
+
for (let i = 0; i < chunks.length; i++) {
|
| 1204 |
+
this.updateTabText('chinese', `翻译中...(${i + 1}/${chunks.length})`);
|
| 1205 |
+
|
| 1206 |
+
if (i > 0) {
|
| 1207 |
+
await sleep(DELAY_BETWEEN_CHUNKS);
|
| 1208 |
+
}
|
| 1209 |
+
try {
|
| 1210 |
+
const response = await new Promise((resolve, reject) => {
|
| 1211 |
+
GM_xmlhttpRequest({
|
| 1212 |
+
method: 'POST',
|
| 1213 |
+
url: 'https://sonygod-flash.hf.space/ask',
|
| 1214 |
+
headers: {
|
| 1215 |
+
'Content-Type': 'application/json',
|
| 1216 |
+
'Accept': 'application/json'
|
| 1217 |
+
},
|
| 1218 |
+
data: JSON.stringify({
|
| 1219 |
+
prompt: `请将以下英文字幕翻译成中文(第${i + 1}组,共${chunks.length}组):
|
| 1220 |
+
1. 必须保持JSON格式返回
|
| 1221 |
+
2. 必须一一对应翻译,不能合并或拆分句子
|
| 1222 |
+
3. 必须保持原时间戳
|
| 1223 |
+
4. translations数组长度必须为${chunks[i].length}
|
| 1224 |
+
|
| 1225 |
+
${JSON.stringify(chunks[i], null, 2)}
|
| 1226 |
+
|
| 1227 |
+
返回格式:
|
| 1228 |
+
{
|
| 1229 |
+
"translations": [
|
| 1230 |
+
{
|
| 1231 |
+
"text": "中文翻译",
|
| 1232 |
+
"time": 原时间戳
|
| 1233 |
+
}
|
| 1234 |
+
]
|
| 1235 |
+
}`,
|
| 1236 |
+
model: 'GEMINI'
|
| 1237 |
+
}),
|
| 1238 |
+
onload: response => {
|
| 1239 |
+
if (response.status === 200) {
|
| 1240 |
+
resolve(JSON.parse(response.responseText));
|
| 1241 |
+
} else {
|
| 1242 |
+
reject(new Error(`HTTP error: ${response.status}`));
|
| 1243 |
+
}
|
| 1244 |
+
},
|
| 1245 |
+
onerror: error => reject(error)
|
| 1246 |
+
});
|
| 1247 |
+
});
|
| 1248 |
+
|
| 1249 |
+
const content = response.data.response;
|
| 1250 |
+
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*(?:```|\.{3})/);
|
| 1251 |
+
|
| 1252 |
+
if (!jsonMatch) {
|
| 1253 |
+
console.log('Response:', content);
|
| 1254 |
+
throw new Error(`Chunk ${i + 1}: No JSON content found`);
|
| 1255 |
+
}
|
| 1256 |
+
|
| 1257 |
+
const parsedResponse = JSON.parse(jsonMatch[1]);
|
| 1258 |
+
translations.push(...parsedResponse.translations);
|
| 1259 |
+
|
| 1260 |
+
} catch (error) {
|
| 1261 |
+
debug.error(`Chunk ${i + 1} translation failed:`, error);
|
| 1262 |
+
// Add empty translations for failed chunk
|
| 1263 |
+
translations.push(...chunks[i].map(sub => ({
|
| 1264 |
+
text: `[翻译失败] ${sub.text}`,
|
| 1265 |
+
time: sub.time
|
| 1266 |
+
})));
|
| 1267 |
+
}
|
| 1268 |
+
}
|
| 1269 |
+
|
| 1270 |
+
return translations;
|
| 1271 |
+
}
|
| 1272 |
+
|
| 1273 |
async initializeSubtitles() {
|
| 1274 |
debug.log('Starting subtitle initialization');
|
| 1275 |
|
|
|
|
| 1311 |
time: sub.startTime
|
| 1312 |
}));
|
| 1313 |
|
|
|
|
|
|
|
| 1314 |
try {
|
| 1315 |
+
const translations = await this.translateInChunks(subtitleData);
|
| 1316 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1317 |
this.subtitles.chinese = this.subtitles.english.map((sub, index) => ({
|
| 1318 |
startTime: sub.startTime,
|
| 1319 |
endTime: sub.endTime,
|
|
|
|
| 1323 |
endTime: sub.endTime
|
| 1324 |
}]
|
| 1325 |
}));
|
| 1326 |
+
|
| 1327 |
this.updateTabText('chinese', '中文');
|
| 1328 |
} catch (error) {
|
|
|
|
| 1329 |
debug.error('Translation failed:', error);
|
| 1330 |
+
this.updateTabText('chinese', '翻译失败');
|
| 1331 |
}
|
| 1332 |
}
|
| 1333 |
|