testingfaces commited on
Commit
c566489
Β·
verified Β·
1 Parent(s): 038dcef

Update translator.py

Browse files
Files changed (1) hide show
  1. translator.py +47 -30
translator.py CHANGED
@@ -3,9 +3,15 @@ Department 3 β€” Translator
3
  UPGRADED v2: IndicTrans2 as primary for Telugu/Hindi/Tamil/Kannada
4
 
5
  MERGED β€” best of both versions:
6
- ⭐ KEPT: IndicTransToolkit, non-gated models, chunk boundaries
7
- βœ… FIXED bugs with bos_token_ids
8
- ⭐ NEW: Added Project Glossary to protect the 'ClearWave AI' name from literal translation
 
 
 
 
 
 
9
  """
10
 
11
  import re
@@ -15,6 +21,7 @@ import threading
15
 
16
  logger = logging.getLogger(__name__)
17
 
 
18
  INDICTRANS2_EN_INDIC = "ai4bharat/indictrans2-en-indic-1B"
19
  INDICTRANS2_INDIC_EN = "ai4bharat/indictrans2-indic-en-1B"
20
 
@@ -57,13 +64,15 @@ CHUNK_WORDS_INDIC = 50
57
  NLLB_MODEL_ID = "facebook/nllb-200-distilled-1.3B"
58
  MAX_TOKENS = 512
59
 
 
60
  class Translator:
61
  def __init__(self):
 
62
  self._it2_tok_ei = None
63
  self._it2_mod_ei = None
64
  self._it2_tok_ie = None
65
  self._it2_mod_ie = None
66
- self._it2_ip = None
67
  self._it2_loaded = False
68
 
69
  self._helsinki_models = {}
@@ -94,20 +103,6 @@ class Translator:
94
  except Exception as e2:
95
  print(f"[Translator] Helsinki pre-load failed: {e2}")
96
 
97
- # ── NEW: Protect project name from literal translation ──
98
- def _apply_glossary(self, text: str) -> str:
99
- """Forces correct branding regardless of language."""
100
- protected_terms = {
101
- "క్లియర్ డేడ్": "ClearWave AI",
102
- "క్లియర్డేడ్": "ClearWave AI",
103
- "clearwave ai": "ClearWave AI",
104
- "clearwave": "ClearWave AI",
105
- "ΰ°•ΰ±ΰ°²ΰ°Ώΰ°―ΰ°°ΰ±β€Œΰ°΅ΰ±‡ΰ°΅ΰ±": "ClearWave AI"
106
- }
107
- for wrong, right in protected_terms.items():
108
- text = re.sub(wrong, right, text, flags=re.IGNORECASE)
109
- return text
110
-
111
  # ══════════════════════════════════════════════════════════════════
112
  # PUBLIC β€” TRANSLATE
113
  # ══════════════════════════════════════════════════════════════════
@@ -122,45 +117,46 @@ class Translator:
122
  print(f"[Translator] {len(chunks)} chunks ({max_words}w), "
123
  f"{len(text)} chars, {src_lang}β†’{tgt_lang}")
124
 
 
125
  INDIC_TO_EN = {"te", "kn", "ml", "bn", "gu", "mr", "pa", "ur"}
126
  if src_lang in INDIC_TO_EN and tgt_lang == "en":
127
  try:
128
- res, method = self._google_chunks(chunks, src_lang, tgt_lang)
129
- if "[Translation failed" not in res:
130
- return self._apply_glossary(res), method
131
  except Exception as e:
132
  logger.warning(f"Google {src_lang}β†’en failed ({e}), trying IndicTrans2")
133
 
 
134
  is_indic = (
135
  (src_lang == "en" and tgt_lang in INDICTRANS2_SUPPORTED) or
136
  (src_lang in INDICTRANS2_SUPPORTED and tgt_lang == "en")
137
  )
138
  if is_indic:
139
  try:
140
- res, method = self._indictrans2_chunks(chunks, src_lang, tgt_lang)
141
- return self._apply_glossary(res), method
142
  except Exception as e:
143
  logger.warning(f"IndicTrans2 failed ({e}), trying Helsinki")
144
 
 
145
  if (src_lang, tgt_lang) in HELSINKI_MODELS:
146
  try:
147
- res, method = self._helsinki_chunks(chunks, src_lang, tgt_lang)
148
- return self._apply_glossary(res), method
149
  except Exception as e:
150
  logger.warning(f"Helsinki failed ({e}), trying NLLB")
151
 
 
152
  try:
153
  if not self._nllb_loaded:
154
  self._init_nllb()
155
  self._nllb_loaded = True
156
  if self._pipeline is not None or self._model is not None:
157
- res, method = self._nllb_chunks(chunks, src_lang, tgt_lang)
158
- return self._apply_glossary(res), method
159
  except Exception as e:
160
  logger.warning(f"NLLB failed ({e}), using Google")
161
 
162
- res, method = self._google_chunks(chunks, src_lang, tgt_lang)
163
- return self._apply_glossary(res), method
164
 
165
  # ══════════════════════════════════════════════════════════════════
166
  # PUBLIC β€” SUMMARIZE
@@ -197,6 +193,9 @@ class Translator:
197
  if self._it2_loaded:
198
  return
199
  from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
 
 
 
200
  try:
201
  from IndicTransToolkit.processor import IndicProcessor
202
  except ImportError:
@@ -231,11 +230,17 @@ class Translator:
231
  print("[Translator] βœ… IndicTrans2 fully loaded!")
232
 
233
  def _indictrans2_chunks(self, chunks, src_lang, tgt_lang):
 
 
 
 
 
234
  t0 = time.time()
235
  if not self._it2_loaded:
236
  self._load_indictrans2()
237
 
238
  import torch
 
239
  src_code = INDICTRANS2_CODES.get(src_lang, "eng_Latn")
240
  tgt_code = INDICTRANS2_CODES.get(tgt_lang, "tel_Telu")
241
 
@@ -244,6 +249,7 @@ class Translator:
244
  else:
245
  tokenizer, model = self._it2_tok_ie, self._it2_mod_ie
246
 
 
247
  try:
248
  forced_bos_token_id = tokenizer.convert_tokens_to_ids(tgt_code)
249
  except Exception:
@@ -255,6 +261,7 @@ class Translator:
255
  if not chunk.strip():
256
  continue
257
  try:
 
258
  batch = self._it2_ip.preprocess_batch(
259
  [chunk], src_lang=src_code, tgt_lang=tgt_code)
260
  inputs = tokenizer(
@@ -270,6 +277,7 @@ class Translator:
270
  num_return_sequences=1,
271
  max_length=MAX_TOKENS,
272
  )
 
273
  if forced_bos_token_id is not None:
274
  gen_kwargs["forced_bos_token_id"] = forced_bos_token_id
275
 
@@ -280,6 +288,7 @@ class Translator:
280
  ids, skip_special_tokens=True,
281
  clean_up_tokenization_spaces=True,
282
  )
 
283
  postprocessed = self._it2_ip.postprocess_batch(decoded, lang=tgt_code)
284
  results.append(postprocessed[0])
285
 
@@ -304,6 +313,7 @@ class Translator:
304
  continue
305
  try:
306
  out = pipe(chunk, max_length=MAX_TOKENS)
 
307
  text = out[0].get("translation_text") or out[0].get("generated_text", chunk)
308
  results.append(text)
309
  except Exception as e:
@@ -317,6 +327,7 @@ class Translator:
317
  if model_id not in self._helsinki_models:
318
  from transformers import pipeline as hf_pipeline
319
  print(f"[Translator] Loading Helsinki {model_id}...")
 
320
  try:
321
  pipe = hf_pipeline("translation", model=model_id,
322
  device_map="auto", max_length=MAX_TOKENS)
@@ -328,9 +339,14 @@ class Translator:
328
  return self._helsinki_models[model_id]
329
 
330
  # ══════════════════════════════════════════════════════════════════
331
- # CHUNKING
332
  # ══════════════════════════════════════════════════════════════════
333
  def _chunk(self, text, max_words):
 
 
 
 
 
334
  sentences = re.split(r'(?<=[.!?ΰ₯€])\s+', text.strip())
335
  chunks, cur, count = [], [], 0
336
 
@@ -338,6 +354,7 @@ class Translator:
338
  words_in_sent = s.split()
339
  w = len(words_in_sent)
340
 
 
341
  if w > max_words:
342
  if cur:
343
  chunks.append(" ".join(cur))
 
3
  UPGRADED v2: IndicTrans2 as primary for Telugu/Hindi/Tamil/Kannada
4
 
5
  MERGED β€” best of both versions:
6
+ ⭐ KEPT from friend's version:
7
+ - IndicTransToolkit.IndicProcessor for correct pre/post-processing
8
+ - Non-gated 1B models (ai4bharat/indictrans2-en-indic-1B) β€” no HF token needed
9
+ - Helsinki fallback handles both "translation_text" and "generated_text" keys
10
+ - Indic→English uses Google first (opus-mt-mul-en transliterates, not translates)
11
+ - L8: Wrong <2te> prefix already removed (friend fixed this one correctly)
12
+ βœ… FIXED bugs that friend's version missed:
13
+ - L9: forced_bos_token_id now passed to model.generate() β€” target lang enforced
14
+ - L10: _chunk word-boundary fallback for unpunctuated transcripts
15
  """
16
 
17
  import re
 
21
 
22
  logger = logging.getLogger(__name__)
23
 
24
+ # ⭐ KEPT: non-gated 1B models
25
  INDICTRANS2_EN_INDIC = "ai4bharat/indictrans2-en-indic-1B"
26
  INDICTRANS2_INDIC_EN = "ai4bharat/indictrans2-indic-en-1B"
27
 
 
64
  NLLB_MODEL_ID = "facebook/nllb-200-distilled-1.3B"
65
  MAX_TOKENS = 512
66
 
67
+
68
  class Translator:
69
  def __init__(self):
70
+ # ⭐ KEPT: separate tokenizer/model handles + IndicProcessor
71
  self._it2_tok_ei = None
72
  self._it2_mod_ei = None
73
  self._it2_tok_ie = None
74
  self._it2_mod_ie = None
75
+ self._it2_ip = None # IndicProcessor instance
76
  self._it2_loaded = False
77
 
78
  self._helsinki_models = {}
 
103
  except Exception as e2:
104
  print(f"[Translator] Helsinki pre-load failed: {e2}")
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  # ══════════════════════════════════════════════════════════════════
107
  # PUBLIC β€” TRANSLATE
108
  # ══════════════════════════════════════════════════════════════════
 
117
  print(f"[Translator] {len(chunks)} chunks ({max_words}w), "
118
  f"{len(text)} chars, {src_lang}β†’{tgt_lang}")
119
 
120
+ # ⭐ KEPT: Indicβ†’English: use Google first (better than transliterating opus-mt-mul-en)
121
  INDIC_TO_EN = {"te", "kn", "ml", "bn", "gu", "mr", "pa", "ur"}
122
  if src_lang in INDIC_TO_EN and tgt_lang == "en":
123
  try:
124
+ result = self._google_chunks(chunks, src_lang, tgt_lang)
125
+ if "[Translation failed" not in result[0]:
126
+ return result
127
  except Exception as e:
128
  logger.warning(f"Google {src_lang}β†’en failed ({e}), trying IndicTrans2")
129
 
130
+ # Priority 1: IndicTrans2
131
  is_indic = (
132
  (src_lang == "en" and tgt_lang in INDICTRANS2_SUPPORTED) or
133
  (src_lang in INDICTRANS2_SUPPORTED and tgt_lang == "en")
134
  )
135
  if is_indic:
136
  try:
137
+ return self._indictrans2_chunks(chunks, src_lang, tgt_lang)
 
138
  except Exception as e:
139
  logger.warning(f"IndicTrans2 failed ({e}), trying Helsinki")
140
 
141
+ # Priority 2: Helsinki-NLP
142
  if (src_lang, tgt_lang) in HELSINKI_MODELS:
143
  try:
144
+ return self._helsinki_chunks(chunks, src_lang, tgt_lang)
 
145
  except Exception as e:
146
  logger.warning(f"Helsinki failed ({e}), trying NLLB")
147
 
148
+ # Priority 3: NLLB
149
  try:
150
  if not self._nllb_loaded:
151
  self._init_nllb()
152
  self._nllb_loaded = True
153
  if self._pipeline is not None or self._model is not None:
154
+ return self._nllb_chunks(chunks, src_lang, tgt_lang)
 
155
  except Exception as e:
156
  logger.warning(f"NLLB failed ({e}), using Google")
157
 
158
+ # Priority 4: Google
159
+ return self._google_chunks(chunks, src_lang, tgt_lang)
160
 
161
  # ══════════════════════════════════════════════════════════════════
162
  # PUBLIC β€” SUMMARIZE
 
193
  if self._it2_loaded:
194
  return
195
  from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
196
+ # IndicTransToolkit is NOT on PyPI β€” must be installed manually.
197
+ # If missing, _load_indictrans2 raises RuntimeError and the caller
198
+ # falls back to Helsinki / NLLB / Google automatically.
199
  try:
200
  from IndicTransToolkit.processor import IndicProcessor
201
  except ImportError:
 
230
  print("[Translator] βœ… IndicTrans2 fully loaded!")
231
 
232
  def _indictrans2_chunks(self, chunks, src_lang, tgt_lang):
233
+ """
234
+ ⭐ KEPT: IndicProcessor.preprocess_batch / postprocess_batch
235
+ FIX L9: forced_bos_token_id now resolved and passed to model.generate()
236
+ so the decoder is forced to output the correct target language.
237
+ """
238
  t0 = time.time()
239
  if not self._it2_loaded:
240
  self._load_indictrans2()
241
 
242
  import torch
243
+
244
  src_code = INDICTRANS2_CODES.get(src_lang, "eng_Latn")
245
  tgt_code = INDICTRANS2_CODES.get(tgt_lang, "tel_Telu")
246
 
 
249
  else:
250
  tokenizer, model = self._it2_tok_ie, self._it2_mod_ie
251
 
252
+ # FIX L9: resolve forced_bos_token_id for target language
253
  try:
254
  forced_bos_token_id = tokenizer.convert_tokens_to_ids(tgt_code)
255
  except Exception:
 
261
  if not chunk.strip():
262
  continue
263
  try:
264
+ # ⭐ KEPT: IndicProcessor pre-processing
265
  batch = self._it2_ip.preprocess_batch(
266
  [chunk], src_lang=src_code, tgt_lang=tgt_code)
267
  inputs = tokenizer(
 
277
  num_return_sequences=1,
278
  max_length=MAX_TOKENS,
279
  )
280
+ # FIX L9: enforce target language in decoder
281
  if forced_bos_token_id is not None:
282
  gen_kwargs["forced_bos_token_id"] = forced_bos_token_id
283
 
 
288
  ids, skip_special_tokens=True,
289
  clean_up_tokenization_spaces=True,
290
  )
291
+ # ⭐ KEPT: IndicProcessor post-processing
292
  postprocessed = self._it2_ip.postprocess_batch(decoded, lang=tgt_code)
293
  results.append(postprocessed[0])
294
 
 
313
  continue
314
  try:
315
  out = pipe(chunk, max_length=MAX_TOKENS)
316
+ # ⭐ KEPT: handles both key names across transformers versions
317
  text = out[0].get("translation_text") or out[0].get("generated_text", chunk)
318
  results.append(text)
319
  except Exception as e:
 
327
  if model_id not in self._helsinki_models:
328
  from transformers import pipeline as hf_pipeline
329
  print(f"[Translator] Loading Helsinki {model_id}...")
330
+ # ⭐ KEPT: fallback to text2text-generation for newer transformers
331
  try:
332
  pipe = hf_pipeline("translation", model=model_id,
333
  device_map="auto", max_length=MAX_TOKENS)
 
339
  return self._helsinki_models[model_id]
340
 
341
  # ══════════════════════════════════════════════════════════════════
342
+ # CHUNKING β€” FIXED (L10)
343
  # ══════════════════════════════════════════════════════════════════
344
  def _chunk(self, text, max_words):
345
+ """
346
+ FIX L10: Added word-boundary fallback.
347
+ Transcripts often have no sentence-ending punctuation β€” without this,
348
+ the entire text becomes one oversized chunk causing OOM / truncation.
349
+ """
350
  sentences = re.split(r'(?<=[.!?ΰ₯€])\s+', text.strip())
351
  chunks, cur, count = [], [], 0
352
 
 
354
  words_in_sent = s.split()
355
  w = len(words_in_sent)
356
 
357
+ # FIX L10: sentence longer than max_words β†’ split on word boundaries
358
  if w > max_words:
359
  if cur:
360
  chunks.append(" ".join(cur))