缓存完成
Browse files- youtube_sub.js +75 -11
youtube_sub.js
CHANGED
|
@@ -1188,21 +1188,43 @@ GM_addStyle(`
|
|
| 1188 |
}
|
| 1189 |
}
|
| 1190 |
|
| 1191 |
-
async
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|
|
@@ -1245,18 +1267,29 @@ GM_addStyle(`
|
|
| 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
|
|
@@ -1266,7 +1299,7 @@ GM_addStyle(`
|
|
| 1266 |
})));
|
| 1267 |
}
|
| 1268 |
}
|
| 1269 |
-
|
| 1270 |
return translations;
|
| 1271 |
}
|
| 1272 |
|
|
@@ -1305,6 +1338,37 @@ GM_addStyle(`
|
|
| 1305 |
|
| 1306 |
// If no Chinese subtitles, get translation
|
| 1307 |
if (this.subtitles.english.length > 0 && !this.subtitles.chinese.length) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1308 |
// Format English subtitles as JSON
|
| 1309 |
const subtitleData = this.subtitles.english.map(sub => ({
|
| 1310 |
text: sub.words.map(word => word.text).join(' '),
|
|
@@ -1313,7 +1377,7 @@ GM_addStyle(`
|
|
| 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,7 +1387,7 @@ GM_addStyle(`
|
|
| 1323 |
endTime: sub.endTime
|
| 1324 |
}]
|
| 1325 |
}));
|
| 1326 |
-
|
| 1327 |
this.updateTabText('chinese', '中文');
|
| 1328 |
} catch (error) {
|
| 1329 |
debug.error('Translation failed:', error);
|
|
|
|
| 1188 |
}
|
| 1189 |
}
|
| 1190 |
|
| 1191 |
+
async translateInChunks(subtitleData) {
|
| 1192 |
+
|
| 1193 |
+
// Get video URL as cache key
|
| 1194 |
+
const videoUrl = window.location.href;
|
| 1195 |
+
const cacheKey = `translations_${videoUrl}`;
|
| 1196 |
+
const CACHE_EXPIRY = 365 * 24 * 60 * 60 * 1000; // 7 days
|
| 1197 |
+
|
| 1198 |
+
|
| 1199 |
+
// Try to get from cache
|
| 1200 |
+
try {
|
| 1201 |
+
const cached = localStorage.getItem(cacheKey);
|
| 1202 |
+
if (cached) {
|
| 1203 |
+
const { translations, timestamp } = JSON.parse(cached);
|
| 1204 |
+
if (Date.now() - timestamp < CACHE_EXPIRY) {
|
| 1205 |
+
debug.log('Using cached translations');
|
| 1206 |
+
return translations;
|
| 1207 |
+
} else {
|
| 1208 |
+
localStorage.removeItem(cacheKey);
|
| 1209 |
+
}
|
| 1210 |
+
}
|
| 1211 |
+
} catch (e) {
|
| 1212 |
+
debug.error('Cache read error:', e);
|
| 1213 |
+
}
|
| 1214 |
const CHUNK_SIZE = 100;
|
| 1215 |
const chunks = [];
|
| 1216 |
const translations = [];
|
| 1217 |
const DELAY_BETWEEN_CHUNKS = 1000; // 1 second delay
|
| 1218 |
+
|
| 1219 |
// Split into chunks
|
| 1220 |
for (let i = 0; i < subtitleData.length; i += CHUNK_SIZE) {
|
| 1221 |
chunks.push(subtitleData.slice(i, i + CHUNK_SIZE));
|
| 1222 |
}
|
| 1223 |
+
|
| 1224 |
// Process each chunk
|
| 1225 |
for (let i = 0; i < chunks.length; i++) {
|
| 1226 |
this.updateTabText('chinese', `翻译中...(${i + 1}/${chunks.length})`);
|
| 1227 |
+
|
| 1228 |
if (i > 0) {
|
| 1229 |
await sleep(DELAY_BETWEEN_CHUNKS);
|
| 1230 |
}
|
|
|
|
| 1267 |
onerror: error => reject(error)
|
| 1268 |
});
|
| 1269 |
});
|
| 1270 |
+
|
| 1271 |
const content = response.data.response;
|
| 1272 |
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*(?:```|\.{3})/);
|
| 1273 |
+
|
| 1274 |
if (!jsonMatch) {
|
| 1275 |
console.log('Response:', content);
|
| 1276 |
throw new Error(`Chunk ${i + 1}: No JSON content found`);
|
| 1277 |
}
|
| 1278 |
+
|
| 1279 |
const parsedResponse = JSON.parse(jsonMatch[1]);
|
| 1280 |
translations.push(...parsedResponse.translations);
|
| 1281 |
+
|
| 1282 |
+
try {
|
| 1283 |
+
localStorage.setItem(cacheKey, JSON.stringify({
|
| 1284 |
+
translations,
|
| 1285 |
+
timestamp: Date.now()
|
| 1286 |
+
}));
|
| 1287 |
+
debug.log('Translations cached');
|
| 1288 |
+
} catch (e) {
|
| 1289 |
+
debug.error('Cache write error:', e);
|
| 1290 |
+
}
|
| 1291 |
+
|
| 1292 |
+
|
| 1293 |
} catch (error) {
|
| 1294 |
debug.error(`Chunk ${i + 1} translation failed:`, error);
|
| 1295 |
// Add empty translations for failed chunk
|
|
|
|
| 1299 |
})));
|
| 1300 |
}
|
| 1301 |
}
|
| 1302 |
+
|
| 1303 |
return translations;
|
| 1304 |
}
|
| 1305 |
|
|
|
|
| 1338 |
|
| 1339 |
// If no Chinese subtitles, get translation
|
| 1340 |
if (this.subtitles.english.length > 0 && !this.subtitles.chinese.length) {
|
| 1341 |
+
|
| 1342 |
+
const videoUrl = window.location.href;
|
| 1343 |
+
const cacheKey = `translations_${videoUrl}`;
|
| 1344 |
+
|
| 1345 |
+
// Try cache first
|
| 1346 |
+
try {
|
| 1347 |
+
const cached = localStorage.getItem(cacheKey);
|
| 1348 |
+
if (cached) {
|
| 1349 |
+
const { translations, timestamp } = JSON.parse(cached);
|
| 1350 |
+
const CACHE_EXPIRY = 365 * 24 * 60 * 60 * 1000; // 365 days
|
| 1351 |
+
|
| 1352 |
+
if (Date.now() - timestamp < CACHE_EXPIRY) {
|
| 1353 |
+
debug.log('Using cached translations');
|
| 1354 |
+
this.subtitles.chinese = this.subtitles.english.map((sub, index) => ({
|
| 1355 |
+
startTime: sub.startTime,
|
| 1356 |
+
endTime: sub.endTime,
|
| 1357 |
+
words: [{
|
| 1358 |
+
text: translations[index]?.text || '',
|
| 1359 |
+
startTime: sub.startTime,
|
| 1360 |
+
endTime: sub.endTime
|
| 1361 |
+
}]
|
| 1362 |
+
}));
|
| 1363 |
+
this.updateTabText('chinese', '中文');
|
| 1364 |
+
return;
|
| 1365 |
+
} else {
|
| 1366 |
+
localStorage.removeItem(cacheKey);
|
| 1367 |
+
}
|
| 1368 |
+
}
|
| 1369 |
+
} catch (e) {
|
| 1370 |
+
debug.error('Cache read error:', e);
|
| 1371 |
+
}
|
| 1372 |
// Format English subtitles as JSON
|
| 1373 |
const subtitleData = this.subtitles.english.map(sub => ({
|
| 1374 |
text: sub.words.map(word => word.text).join(' '),
|
|
|
|
| 1377 |
|
| 1378 |
try {
|
| 1379 |
const translations = await this.translateInChunks(subtitleData);
|
| 1380 |
+
|
| 1381 |
this.subtitles.chinese = this.subtitles.english.map((sub, index) => ({
|
| 1382 |
startTime: sub.startTime,
|
| 1383 |
endTime: sub.endTime,
|
|
|
|
| 1387 |
endTime: sub.endTime
|
| 1388 |
}]
|
| 1389 |
}));
|
| 1390 |
+
|
| 1391 |
this.updateTabText('chinese', '中文');
|
| 1392 |
} catch (error) {
|
| 1393 |
debug.error('Translation failed:', error);
|