File size: 3,852 Bytes
ea81969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { SRT_TRANSLATOR_PROMPT } from '../../../prompts/srttranslator.js';
import { tryModels, getPrompt, cleanSRTOutput, DEFAULT_SAFETY_SETTINGS } from '@/backend/services/ai/utils';

/**

 * Counts the number of SRT blocks in a given text.

 */
function countSrtBlocks(text) {
    if (!text) return 0;
    // Improved regex to count standard SRT blocks accurately
    const matches = text.match(/^\d+\s*\r?\n\d{2}:\d{2}:\d{2},\d{3}/gm);
    return matches ? matches.length : 0;
}

export async function srtTranslate(srtContent, sourceLanguage, targetLanguage, apiKey, isOwnApi = false) {
    const models = ['gemini-3-flash-preview', 'gemini-flash-lite-latest'];
    const finalPrompt = SRT_TRANSLATOR_PROMPT(sourceLanguage, targetLanguage);
    
    // Normalize line endings and split into blocks
    const blocks = srtContent.replace(/\r\n/g, '\n').split(/\n\s*\n/).filter(b => b.trim().length > 0);
    
    const CHUNK_SIZE = 120; // Increased to 120 as requested for higher throughput
    const BATCH_SIZE = 2;   // Enabled Parallel processing (2 at a time)
    const COOLDOWN = 1000;  // Minimal cooldown between parallel batches
    
    console.log(`[AI-SRT-SAFE-MAX] Segment Blocks: ${blocks.length}. Chunks: ${Math.ceil(blocks.length/CHUNK_SIZE)}.`);

    const chunkTexts = [];
    const chunkBlockCounts = [];
    for (let i = 0; i < blocks.length; i += CHUNK_SIZE) {
        const slice = blocks.slice(i, i + CHUNK_SIZE);
        chunkTexts.push(slice.join('\n\n'));
        chunkBlockCounts.push(slice.length);
    }

    const results = [];
    for (let i = 0; i < chunkTexts.length; i += BATCH_SIZE) {
        const currentBatchIndices = Array.from({ length: Math.min(BATCH_SIZE, chunkTexts.length - i) }, (_, k) => i + k);
        
        const batchPromises = currentBatchIndices.map(idx => 
            tryModels(apiKey, models, async (ai, model) => {
                const inputText = chunkTexts[idx];
                const expectedCount = chunkBlockCounts[idx];
                
                let response = await ai.models.generateContent({
                    model: model,
                    contents: [{ parts: [{ text: `Translate these ${expectedCount} blocks now. Maintain all IDs:\n\n${inputText}` }] }],
                    config: { 
                        temperature: 0.1, 
                        systemInstruction: finalPrompt, 
                        safetySettings: DEFAULT_SAFETY_SETTINGS,
                        thinkingConfig: { thinkingBudget: 0 } 
                    }
                });

                let translatedText = cleanSRTOutput(response.text);
                let actualCount = countSrtBlocks(translatedText);

                // --- INTEGRITY AUTO-RECOVERY ---
                if (actualCount < expectedCount) {
                    console.warn(`[AI-SRT] Gap detected in chunk ${idx} (${actualCount}/${expectedCount}). Retrying with max precision...`);
                    response = await ai.models.generateContent({
                        model: model,
                        contents: [{ parts: [{ text: `CRITICAL: Do not skip lines. You MUST return exactly ${expectedCount} blocks. Translate ALL now:\n\n${inputText}` }] }],
                        config: { temperature: 0.0, systemInstruction: finalPrompt, safetySettings: DEFAULT_SAFETY_SETTINGS }
                    });
                    translatedText = cleanSRTOutput(response.text);
                }

                return translatedText;
            })
        );

        const batchResults = await Promise.all(batchPromises);
        results.push(...batchResults);

        if (i + BATCH_SIZE < chunkTexts.length) {
            await new Promise(r => setTimeout(r, COOLDOWN));
        }
    }

    return results.join('\n\n').trim();
}