sreepathi-ravikumar commited on
Commit
916cab7
·
verified ·
1 Parent(s): 13cdf1c

Update video2.py

Browse files
Files changed (1) hide show
  1. video2.py +226 -146
video2.py CHANGED
@@ -46,20 +46,18 @@ import html
46
  import unicodedata
47
  import tempfile
48
  import os
49
- import asyncio
50
- from concurrent.futures import ThreadPoolExecutor
51
  from functools import lru_cache
52
- from gtts import gTTS # ADD: Import gTTS for replacement
53
  from pydub import AudioSegment
54
  from pydub.effects import normalize
55
  from mutagen.mp3 import MP3
 
56
 
57
- # Global constants (unchanged)
58
- AUDIO_DIR = os.path.join("/app/data", "sound") # Ensure this matches your BASE_DIR
59
- os.makedirs(AUDIO_DIR, exist_ok=True)
60
- VOICE_EN = "en" # CHANGE: For gTTS, use lang codes instead of full voice names
61
 
62
- # Pre-compiled regex patterns (unchanged)
 
63
  URL_PATTERN = re.compile(r'https?://[^\s<>"\']+|www\.[^\s<>"\']+')
64
  TAG_PATTERN = re.compile(r'<[^>]*>|[<>]')
65
  BRACKET_PATTERN = re.compile(r'[\{\}\[\]]')
@@ -68,63 +66,91 @@ WHITESPACE_PATTERN = re.compile(r'\s+')
68
  SENTENCE_PATTERN = re.compile(r'(?<=[.!?])\s+')
69
  SUB_PATTERN = re.compile(r'(?<=[,;:])\s+')
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  @lru_cache(maxsize=1024)
72
  def clean_text_for_tts(text):
73
  """Cleans text before TTS with optimized regex and caching."""
74
- if not text or text.isspace():
75
- return "Default text for empty input" # Fallback for empty input
76
  text = str(text).strip()
77
  text = html.unescape(text)
78
 
79
- # Use pre-compiled patterns (much faster)
80
  text = URL_PATTERN.sub('', text)
81
  text = TAG_PATTERN.sub('', text)
82
  text = BRACKET_PATTERN.sub('', text)
83
  text = SPECIAL_CHAR_PATTERN.sub('', text)
84
  text = text.replace('\\n', ' ').replace('\\t', ' ').replace('\\r', ' ')
85
 
86
- # Batch remove keywords (faster than multiple re.sub calls)
87
  for keyword in ['voice', 'speak', 'prosody', 'ssml', 'xmlns']:
88
  text = text.replace(keyword, '').replace(keyword.upper(), '')
89
 
90
  text = unicodedata.normalize('NFKD', text)
91
  text = WHITESPACE_PATTERN.sub(' ', text)
92
- text = text.strip()
93
- if not text:
94
- return "Default text for empty input" # Ensure non-empty output
95
- return text
96
 
97
- def generate_safe_audio(text, lang): # CHANGE: Remove async/semaphore; gTTS is sync
98
- """Generate clean audio with gTTS (synchronous)."""
99
- cleaned_text = clean_text_for_tts(text)
100
- print(f"Generating audio for text: {cleaned_text[:50]}... with lang: {lang}") # Debug log
101
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
102
- fname = temp_file.name
103
- temp_file.close()
104
-
105
- try:
106
- # Use gTTS with specified lang (e.g., 'en' for English, 'ta' for Tamil)
107
- tts = gTTS(text=cleaned_text, lang=lang, slow=False) # slow=False for natural speed
108
- tts.save(fname)
109
- if os.path.exists(fname) and os.path.getsize(fname) > 0:
110
- print(f"Audio generated: {fname}") # Debug log
111
- return fname
112
- else:
113
- print(f"Audio file {fname} is empty or missing") # Debug log
114
- os.unlink(fname)
115
- return None
116
- except Exception as e:
117
- print(f"Error generating audio for '{cleaned_text[:20]}...': {e}") # Debug log
118
- if os.path.exists(fname):
119
- os.unlink(fname)
120
- return None
121
 
122
  @lru_cache(maxsize=256)
123
- def smart_text_chunking(text, max_chars=80):
124
- """Cached text chunking for speed."""
125
  text = clean_text_for_tts(text)
126
  if not text:
127
- return ("Default text for chunking",) # Non-empty fallback
 
128
  sentences = SENTENCE_PATTERN.split(text)
129
  chunks = []
130
 
@@ -158,72 +184,107 @@ def smart_text_chunking(text, max_chars=80):
158
  if current_chunk:
159
  chunks.append(current_chunk.strip())
160
 
161
- return tuple(chunks) or ("Default text for chunking",) # Non-empty fallback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
- def process_audio_segment_fast(audio_file):
164
- """Fast audio processing in separate thread."""
165
  try:
166
  segment = AudioSegment.from_file(audio_file)
167
  segment = normalize(segment)
168
 
169
- # Only strip silence for longer segments
170
  if len(segment) > 200:
171
  try:
172
  segment = segment.strip_silence(silence_len=50, silence_thresh=-40)
173
  except:
174
- pass # Skip if fails
175
 
176
- print(f"Processed audio segment: {audio_file}") # Debug log
177
  return segment
178
  except Exception as e:
179
- print(f"Warning: Error processing audio segment {audio_file}: {e}")
180
  return None
181
  finally:
182
- # Cleanup temp file immediately
183
  try:
184
  if os.path.exists(audio_file):
185
  os.unlink(audio_file)
186
  except:
187
  pass
188
 
189
- def bilingual_tts_optimized(text, output_file="audio0.mp3", LANG_TA=None, max_concurrent=5):
190
- """Ultra-optimized bilingual TTS with gTTS and parallel processing via threads."""
191
- print(f"Starting gTTS bilingual TTS for output: {output_file}") # Debug log
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  try:
194
- chunks = smart_text_chunking(text)
 
195
  if not chunks:
196
  print("Error: No valid text chunks after cleaning")
197
  return None
198
 
199
- print(f"Processing {len(chunks)} text chunks with max {max_concurrent} concurrent requests...")
 
 
 
 
 
 
 
 
 
200
 
201
- is_bilingual_tamil = LANG_TA is not None and LANG_TA == 'ta'
 
 
202
 
203
- # Prepare all audio files using ThreadPoolExecutor (since gTTS is sync)
204
- audio_files = []
205
- with ThreadPoolExecutor(max_workers=max_concurrent) as executor:
206
- futures = []
207
- for chunk in chunks:
208
- is_tamil = any('\u0B80' <= char <= '\u0BFF' for char in chunk)
209
- lang = LANG_TA if (is_bilingual_tamil and is_tamil) else (LANG_TA or VOICE_EN)
210
- futures.append(executor.submit(generate_safe_audio, chunk, lang))
211
-
212
- # Collect results
213
- for future in futures:
214
- result = future.result()
215
- if result:
216
- audio_files.append(result)
217
 
218
- if not audio_files:
219
  print("Error: No audio was successfully generated")
220
  return None
221
 
222
- print(f"Successfully generated {len(audio_files)} audio segments")
223
 
224
- # Process audio segments in parallel using another ThreadPoolExecutor
225
- with ThreadPoolExecutor(max_workers=min(len(audio_files), 4)) as executor:
226
- audio_segments = list(executor.map(process_audio_segment_fast, audio_files))
227
 
228
  # Filter out None segments
229
  audio_segments = [seg for seg in audio_segments if seg is not None]
@@ -232,106 +293,125 @@ def bilingual_tts_optimized(text, output_file="audio0.mp3", LANG_TA=None, max_co
232
  print("Error: No audio segments were successfully processed")
233
  return None
234
 
235
- # Merge audio segments (fast concatenation)
236
  print("Merging audio segments...")
237
  merged_audio = audio_segments[0]
238
- pause = AudioSegment.silent(duration=200)
239
 
240
  for segment in audio_segments[1:]:
241
  merged_audio += pause + segment
242
 
243
- # Apply final processing (compression and normalization) for quality
244
  print("Applying final audio processing...")
 
 
 
 
 
245
  merged_audio = merged_audio.compress_dynamic_range(
246
- threshold=-20.0,
247
- ratio=4.0,
248
- attack=5.0,
249
  release=50.0
250
  )
 
 
251
  merged_audio = normalize(merged_audio)
252
 
253
- # Export with high quality (192k bitrate for better quality matching edge_tts)
254
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
255
- merged_audio.export(output_file, format="mp3", bitrate="192k")
256
- if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
257
- print(f"✅ Audio successfully generated: {output_file}")
258
- return output_file
259
- else:
260
- print(f"Error: Audio file {output_file} is empty or not created")
261
- return None
 
262
 
263
  except Exception as main_error:
264
  print(f"Main error in bilingual TTS: {main_error}")
 
 
265
  return None
266
 
267
- async def generate_tts_optimized(id, lines, lang):
268
- """Optimized TTS generation function (now sync-wrapped for async compatibility)."""
269
- # CHANGE: Map to gTTS lang codes (no neural voices; use standard lang)
270
- lang_map = {
271
- "English": "en",
272
- "Tamil": "ta",
273
- "Hindi": "hi",
274
- "Malayalam": "ml",
275
- "Kannada": "kn",
276
- "Telugu": "te",
277
- "Bengali": "bn",
278
- "Marathi": "mr",
279
- "Gujarati": "gu",
280
- "Punjabi": "pa",
281
- "Urdu": "ur",
282
- "French": "fr",
283
- "German": "de",
284
- "Spanish": "es",
285
- "Italian": "it",
286
- "Russian": "ru",
287
- "Japanese": "ja",
288
- "Korean": "ko",
289
- "Chinese": "zh",
290
- "Arabic": "ar",
291
- "Portuguese": "pt",
292
- "Dutch": "nl",
293
- "Greek": "el",
294
- "Hebrew": "he",
295
- "Turkish": "tr",
296
- "Polish": "pl",
297
- "Thai": "th",
298
- "Vietnamese": "vi",
299
- "Swedish": "sv",
300
- "Finnish": "fi",
301
- "Czech": "cs",
302
- "Hungarian": "hu"
303
- }
304
 
305
  audio_name = f"audio{id}.mp3"
306
  audio_path = os.path.join(AUDIO_DIR, audio_name)
307
 
308
- print(f"Generating audio for id {id}, lang: {lang}") # Debug log
309
  if "&&&" in lang:
310
- listf = lang.split("&&&")
311
- text = listf[0].strip()
312
- lang_name = listf[1].strip()
313
- lang_to_use = lang_map.get(lang_name, VOICE_EN)
314
  else:
315
- text = lines[id]
316
- lang_to_use = lang_map.get(lang, VOICE_EN)
 
 
 
317
 
318
- print(f"Text for TTS: {text[:50]}...") # Debug log
319
- # CHANGE: Call sync bilingual_tts_optimized (no async needed for gTTS)
320
- output = bilingual_tts_optimized(text, audio_path, lang_to_use, max_concurrent=5)
321
 
322
  if output and os.path.exists(audio_path):
323
- audio = MP3(audio_path)
324
- duration = audio.info.length
325
- print(f"Audio duration: {duration}s, path: {audio_path}") # Debug log
326
- return duration, audio_path
 
 
 
 
327
 
328
- print(f"Audio generation failed for id {id}") # Debug log
329
  return None, None
330
 
331
  def audio_func(id, lines, lang):
332
- """Synchronous wrapper for audio generation (unchanged, but now calls sync TTS)."""
333
- # CHANGE: No asyncio.run needed since generate_tts_optimized is now sync
334
- return generate_tts_optimized(id, lines, lang)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  #-----------------------------
336
  #---------------------------------
337
  def video_func(id, lines, lang):
 
46
  import unicodedata
47
  import tempfile
48
  import os
 
 
49
  from functools import lru_cache
50
+ from gtts import gTTS
51
  from pydub import AudioSegment
52
  from pydub.effects import normalize
53
  from mutagen.mp3 import MP3
54
+ from concurrent.futures import ThreadPoolExecutor
55
 
56
+ # Default voice/language settings
57
+ DEFAULT_LANG = "en"
 
 
58
 
59
+
60
+ # Pre-compiled regex patterns for speed
61
  URL_PATTERN = re.compile(r'https?://[^\s<>"\']+|www\.[^\s<>"\']+')
62
  TAG_PATTERN = re.compile(r'<[^>]*>|[<>]')
63
  BRACKET_PATTERN = re.compile(r'[\{\}\[\]]')
 
66
  SENTENCE_PATTERN = re.compile(r'(?<=[.!?])\s+')
67
  SUB_PATTERN = re.compile(r'(?<=[,;:])\s+')
68
 
69
+ # gTTS language mappings (ISO 639-1 codes)
70
+ LANGUAGE_MAP = {
71
+ "English": "en",
72
+ "Tamil": "ta",
73
+ "Hindi": "hi",
74
+ "Malayalam": "ml",
75
+ "Kannada": "kn",
76
+ "Telugu": "te",
77
+ "Bengali": "bn",
78
+ "Marathi": "mr",
79
+ "Gujarati": "gu",
80
+ "Punjabi": "pa",
81
+ "Urdu": "ur",
82
+ "French": "fr",
83
+ "German": "de",
84
+ "Spanish": "es",
85
+ "Italian": "it",
86
+ "Russian": "ru",
87
+ "Japanese": "ja",
88
+ "Korean": "ko",
89
+ "Chinese": "zh-CN",
90
+ "Arabic": "ar",
91
+ "Portuguese": "pt",
92
+ "Dutch": "nl",
93
+ "Greek": "el",
94
+ "Hebrew": "he",
95
+ "Turkish": "tr",
96
+ "Polish": "pl",
97
+ "Thai": "th",
98
+ "Vietnamese": "vi",
99
+ "Swedish": "sv",
100
+ "Finnish": "fi",
101
+ "Czech": "cs",
102
+ "Hungarian": "hu"
103
+ }
104
+
105
+ # Unicode ranges for language detection
106
+ LANGUAGE_UNICODE_RANGES = {
107
+ 'ta': ('\u0B80', '\u0BFF'), # Tamil
108
+ 'hi': ('\u0900', '\u097F'), # Hindi/Devanagari
109
+ 'te': ('\u0C00', '\u0C7F'), # Telugu
110
+ 'kn': ('\u0C80', '\u0CFF'), # Kannada
111
+ 'ml': ('\u0D00', '\u0D7F'), # Malayalam
112
+ 'bn': ('\u0980', '\u09FF'), # Bengali
113
+ 'gu': ('\u0A80', '\u0AFF'), # Gujarati
114
+ 'pa': ('\u0A00', '\u0A7F'), # Punjabi
115
+ }
116
+
117
  @lru_cache(maxsize=1024)
118
  def clean_text_for_tts(text):
119
  """Cleans text before TTS with optimized regex and caching."""
120
+ if not text:
121
+ return ""
122
  text = str(text).strip()
123
  text = html.unescape(text)
124
 
125
+ # Use pre-compiled patterns
126
  text = URL_PATTERN.sub('', text)
127
  text = TAG_PATTERN.sub('', text)
128
  text = BRACKET_PATTERN.sub('', text)
129
  text = SPECIAL_CHAR_PATTERN.sub('', text)
130
  text = text.replace('\\n', ' ').replace('\\t', ' ').replace('\\r', ' ')
131
 
132
+ # Remove TTS-specific keywords
133
  for keyword in ['voice', 'speak', 'prosody', 'ssml', 'xmlns']:
134
  text = text.replace(keyword, '').replace(keyword.upper(), '')
135
 
136
  text = unicodedata.normalize('NFKD', text)
137
  text = WHITESPACE_PATTERN.sub(' ', text)
138
+ return text.strip()
 
 
 
139
 
140
+ def detect_language(text):
141
+ """Detect language from text based on Unicode ranges."""
142
+ for lang_code, (start, end) in LANGUAGE_UNICODE_RANGES.items():
143
+ if any(start <= char <= end for char in text):
144
+ return lang_code
145
+ return 'en' # Default to English
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  @lru_cache(maxsize=256)
148
+ def smart_text_chunking(text, max_chars=100):
149
+ """Cached text chunking optimized for gTTS."""
150
  text = clean_text_for_tts(text)
151
  if not text:
152
+ return tuple()
153
+
154
  sentences = SENTENCE_PATTERN.split(text)
155
  chunks = []
156
 
 
184
  if current_chunk:
185
  chunks.append(current_chunk.strip())
186
 
187
+ return tuple(chunk for chunk in chunks if chunk.strip())
188
+
189
+ def generate_audio_chunk(args):
190
+ """Generate audio for a single chunk using gTTS."""
191
+ chunk, lang_code, chunk_idx = args
192
+
193
+ try:
194
+ cleaned_text = clean_text_for_tts(chunk)
195
+ if not cleaned_text:
196
+ return None
197
+
198
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
199
+ fname = temp_file.name
200
+ temp_file.close()
201
+
202
+ # Generate TTS with gTTS
203
+ tts = gTTS(text=cleaned_text, lang=lang_code, slow=False)
204
+ tts.save(fname)
205
+
206
+ print(f"Generated chunk {chunk_idx + 1}: {len(cleaned_text)} chars")
207
+ return fname
208
+
209
+ except Exception as e:
210
+ print(f"Error generating audio chunk {chunk_idx}: {e}")
211
+ if os.path.exists(fname):
212
+ os.unlink(fname)
213
+ return None
214
 
215
+ def process_audio_segment(audio_file):
216
+ """Process audio segment with normalization and silence stripping."""
217
  try:
218
  segment = AudioSegment.from_file(audio_file)
219
  segment = normalize(segment)
220
 
221
+ # Strip silence for better quality
222
  if len(segment) > 200:
223
  try:
224
  segment = segment.strip_silence(silence_len=50, silence_thresh=-40)
225
  except:
226
+ pass
227
 
 
228
  return segment
229
  except Exception as e:
230
+ print(f"Warning: Error processing audio segment: {e}")
231
  return None
232
  finally:
 
233
  try:
234
  if os.path.exists(audio_file):
235
  os.unlink(audio_file)
236
  except:
237
  pass
238
 
239
+ def bilingual_tts_gtts(text, output_file="audio0.mp3", target_lang=None, max_workers=8):
240
+ """
241
+ Generate bilingual TTS audio using gTTS with parallel processing.
242
+
243
+ Args:
244
+ text: Input text (can contain multiple languages)
245
+ output_file: Output MP3 file path
246
+ target_lang: Primary language code (auto-detected if None)
247
+ max_workers: Number of parallel workers
248
+
249
+ Returns:
250
+ Path to generated audio file or None on error
251
+ """
252
+ print("Starting gTTS bilingual audio generation...")
253
 
254
  try:
255
+ # Chunk the text
256
+ chunks = smart_text_chunking(text, max_chars=100)
257
  if not chunks:
258
  print("Error: No valid text chunks after cleaning")
259
  return None
260
 
261
+ print(f"Processing {len(chunks)} text chunks...")
262
+
263
+ # Detect languages for each chunk
264
+ chunk_args = []
265
+ for idx, chunk in enumerate(chunks):
266
+ # Detect language for this chunk
267
+ detected_lang = detect_language(chunk)
268
+ # Use target language if specified, otherwise use detected
269
+ lang_code = target_lang if target_lang else detected_lang
270
+ chunk_args.append((chunk, lang_code, idx))
271
 
272
+ # Generate audio chunks in parallel
273
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
274
+ audio_files = list(executor.map(generate_audio_chunk, chunk_args))
275
 
276
+ # Filter successful files
277
+ processed_audio_files = [f for f in audio_files if f and os.path.exists(f)]
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
+ if not processed_audio_files:
280
  print("Error: No audio was successfully generated")
281
  return None
282
 
283
+ print(f"Successfully generated {len(processed_audio_files)} audio segments")
284
 
285
+ # Process audio segments in parallel
286
+ with ThreadPoolExecutor(max_workers=min(len(processed_audio_files), 8)) as executor:
287
+ audio_segments = list(executor.map(process_audio_segment, processed_audio_files))
288
 
289
  # Filter out None segments
290
  audio_segments = [seg for seg in audio_segments if seg is not None]
 
293
  print("Error: No audio segments were successfully processed")
294
  return None
295
 
296
+ # Merge audio segments
297
  print("Merging audio segments...")
298
  merged_audio = audio_segments[0]
299
+ pause = AudioSegment.silent(duration=300) # 300ms pause between segments
300
 
301
  for segment in audio_segments[1:]:
302
  merged_audio += pause + segment
303
 
304
+ # Apply final processing for high quality
305
  print("Applying final audio processing...")
306
+
307
+ # Normalize audio
308
+ merged_audio = normalize(merged_audio)
309
+
310
+ # Apply dynamic range compression for better clarity
311
  merged_audio = merged_audio.compress_dynamic_range(
312
+ threshold=-20.0,
313
+ ratio=3.0,
314
+ attack=5.0,
315
  release=50.0
316
  )
317
+
318
+ # Final normalization
319
  merged_audio = normalize(merged_audio)
320
 
321
+ # Export with high quality settings
322
+ merged_audio.export(
323
+ output_file,
324
+ format="mp3",
325
+ bitrate="192k",
326
+ parameters=["-q:a", "0"] # Highest quality
327
+ )
328
+
329
+ print(f"✅ Audio successfully generated: {output_file}")
330
+ return output_file
331
 
332
  except Exception as main_error:
333
  print(f"Main error in bilingual TTS: {main_error}")
334
+ import traceback
335
+ traceback.print_exc()
336
  return None
337
 
338
+ def generate_tts_gtts(id, lines, lang):
339
+ """
340
+ Generate TTS audio using gTTS.
341
+
342
+ Args:
343
+ id: Audio ID/index
344
+ lines: List of text lines
345
+ lang: Language specification (can include text with "&&&" separator)
346
+
347
+ Returns:
348
+ Tuple of (duration, audio_path) or (None, None) on error
349
+ """
350
+ # Ensure audio directory exists
351
+ os.makedirs(AUDIO_DIR, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
  audio_name = f"audio{id}.mp3"
354
  audio_path = os.path.join(AUDIO_DIR, audio_name)
355
 
356
+ # Parse language specification
357
  if "&&&" in lang:
358
+ parts = lang.split("&&&")
359
+ text = parts[0].strip()
360
+ lang_name = parts[1].strip()
361
+ lang_code = LANGUAGE_MAP.get(lang_name, DEFAULT_LANG)
362
  else:
363
+ text = lines[id] if isinstance(lines, list) and id < len(lines) else lines
364
+ lang_code = LANGUAGE_MAP.get(lang, DEFAULT_LANG)
365
+
366
+ print(f"\nGenerating audio {id} in language: {lang_code}")
367
+ print(f"Text preview: {text[:100]}...")
368
 
369
+ # Generate audio
370
+ output = bilingual_tts_gtts(text, audio_path, lang_code, max_workers=8)
 
371
 
372
  if output and os.path.exists(audio_path):
373
+ try:
374
+ audio = MP3(audio_path)
375
+ duration = audio.info.length
376
+ print(f"Generated audio duration: {duration:.2f} seconds")
377
+ return duration, audio_path
378
+ except Exception as e:
379
+ print(f"Error reading audio file: {e}")
380
+ return None, None
381
 
 
382
  return None, None
383
 
384
  def audio_func(id, lines, lang):
385
+ """
386
+ Main function to generate audio using gTTS.
387
+
388
+ Args:
389
+ id: Audio ID/index
390
+ lines: Text content (string or list)
391
+ lang: Language specification
392
+
393
+ Returns:
394
+ Tuple of (duration, audio_path)
395
+ """
396
+ return generate_tts_gtts(id, lines, lang)
397
+
398
+
399
+ # Example usage
400
+ if __name__ == "__main__":
401
+ # Example 1: Simple English text
402
+ lines = ["Hello, this is a test of the Google Text-to-Speech system."]
403
+ duration, path = audio_func(0, lines, "English")
404
+ print(f"Generated: {path} ({duration}s)")
405
+
406
+ # Example 2: Bilingual text with custom format
407
+ bilingual_text = "Hello, welcome to our service. வணக்கம், எங்கள் சேவைக்கு வரவேற்கிறோம். &&&Tamil"
408
+ duration, path = audio_func(1, bilingual_text, bilingual_text)
409
+ print(f"Generated: {path} ({duration}s)")
410
+
411
+ # Example 3: Tamil text
412
+ tamil_lines = ["வணக்கம், இது தமிழில் ஒரு சோதனை செய்தி."]
413
+ duration, path = audio_func(2, tamil_lines, "Tamil")
414
+ print(f"Generated: {path} ({duration}s)")
415
  #-----------------------------
416
  #---------------------------------
417
  def video_func(id, lines, lang):