translateinpy 0.9.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: translateinpy
3
+ Version: 0.9.6
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: google-genai>=1.35.0
8
+ Requires-Dist: pyaudiowpatch>=0.5.2
9
+ Requires-Dist: ffmpeg>=0.2.0
10
+
11
+ # TranslateINPy
12
+ A python translation module for translating live responses using Gemini API inside your application.
13
+
14
+ ## Usage
15
+
16
+ ```python
17
+ from translateinpy import Translator
18
+ import os
19
+
20
+ api_key = os.getenv("GEMINI_API_KEY")
21
+
22
+ Translator("../sample/hi-1.wav", "en", api_key)
23
+ ```
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install TranslateINPY
29
+ ```
30
+
31
+ or via UV:
32
+
33
+ ```bash
34
+ uv add TranslateINPy
35
+ ```
@@ -0,0 +1,25 @@
1
+ # TranslateINPy
2
+ A python translation module for translating live responses using Gemini API inside your application.
3
+
4
+ ## Usage
5
+
6
+ ```python
7
+ from translateinpy import Translator
8
+ import os
9
+
10
+ api_key = os.getenv("GEMINI_API_KEY")
11
+
12
+ Translator("../sample/hi-1.wav", "en", api_key)
13
+ ```
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install TranslateINPY
19
+ ```
20
+
21
+ or via UV:
22
+
23
+ ```bash
24
+ uv add TranslateINPy
25
+ ```
@@ -0,0 +1,11 @@
1
+ [project]
2
+ name = "translateinpy"
3
+ version = "0.9.6"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "google-genai>=1.35.0",
9
+ "pyaudiowpatch>=0.5.2",
10
+ "ffmpeg>=0.2.0",
11
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .fromAudioFile import Translator
@@ -0,0 +1,742 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2026 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """
17
+ ## Setup
18
+
19
+ To install the dependencies for this script, run:
20
+
21
+ ```
22
+ pip install google-genai==2.8.0 pyaudio
23
+ ```
24
+
25
+ This script also requires `ffmpeg` to be installed on your system. For example, on macOS:
26
+
27
+ ```
28
+ brew install ffmpeg
29
+ ```
30
+
31
+ Before running this script, ensure the `GEMINI_API_KEY` environment
32
+ variable is set to the api-key you obtained from Google AI Studio.
33
+
34
+ ## Run
35
+
36
+ To run the script:
37
+
38
+ ```
39
+ python Get_started_LiveTranslate.py --url "https://storage.googleapis.com/generativeai-downloads/gemini-cookbook/audio/gemini-live-translate-sample.wav" --target es
40
+ ```
41
+ """
42
+
43
+ import time
44
+ import argparse
45
+ import array
46
+ import asyncio
47
+ import os
48
+ import traceback
49
+
50
+ import pyaudio
51
+ from google import genai
52
+ from google.genai import types
53
+
54
+
55
+ # Audio configuration
56
+ FORMAT = pyaudio.paInt16
57
+ CHANNELS = 1
58
+
59
+ SEND_SAMPLE_RATE = 16_000
60
+ RECEIVE_SAMPLE_RATE = 24_000
61
+
62
+ # 100 ms at 16 kHz
63
+ CHUNK_SIZE = 1_600
64
+
65
+
66
+ # Queue markers
67
+ _INPUT_END = object()
68
+ _OUTPUT_END = object()
69
+
70
+
71
+ def adjust_volume(pcm_data: bytes, volume: float) -> bytes:
72
+ """Reduce the volume of signed 16-bit PCM audio."""
73
+
74
+ if volume >= 1.0:
75
+ return pcm_data
76
+
77
+ if volume <= 0.0:
78
+ return b"\x00" * len(pcm_data)
79
+
80
+ samples = array.array("h", pcm_data)
81
+
82
+ for index in range(len(samples)):
83
+ samples[index] = int(samples[index] * volume)
84
+
85
+ return samples.tobytes()
86
+
87
+
88
+ async def stream_audio_url(
89
+ url: str,
90
+ audio_queue: asyncio.Queue,
91
+ original_playback_queue: asyncio.Queue | None = None,
92
+ volume: float = 0.08,
93
+ ) -> None:
94
+ """
95
+ Decode an audio file/URL with FFmpeg and place PCM chunks in the queue.
96
+ """
97
+
98
+ print(f"\n[Info] Starting audio stream from: {url}", flush=True)
99
+
100
+ process = await asyncio.create_subprocess_exec(
101
+ "ffmpeg",
102
+ "-hide_banner",
103
+ "-loglevel",
104
+ "error",
105
+ "-i",
106
+ url,
107
+ "-f",
108
+ "s16le",
109
+ "-acodec",
110
+ "pcm_s16le",
111
+ "-ar",
112
+ str(SEND_SAMPLE_RATE),
113
+ "-ac",
114
+ str(CHANNELS),
115
+ "-",
116
+ stdout=asyncio.subprocess.PIPE,
117
+ stderr=asyncio.subprocess.PIPE,
118
+ )
119
+
120
+ chunk_size_bytes = CHUNK_SIZE * 2
121
+ bytes_per_second = SEND_SAMPLE_RATE * 2
122
+
123
+ start_time = asyncio.get_running_loop().time()
124
+ bytes_sent = 0
125
+
126
+ try:
127
+ while True:
128
+ data = await process.stdout.read(chunk_size_bytes)
129
+
130
+ if not data:
131
+ break
132
+
133
+ await audio_queue.put(data)
134
+
135
+ if (
136
+ original_playback_queue is not None
137
+ and volume > 0.0
138
+ ):
139
+ quiet_data = adjust_volume(data, volume)
140
+ await original_playback_queue.put(quiet_data)
141
+
142
+ bytes_sent += len(data)
143
+
144
+ expected_elapsed = bytes_sent / bytes_per_second
145
+ actual_elapsed = (
146
+ asyncio.get_running_loop().time() - start_time
147
+ )
148
+
149
+ sleep_time = expected_elapsed - actual_elapsed
150
+
151
+ if sleep_time > 0:
152
+ await asyncio.sleep(sleep_time)
153
+
154
+ # FFmpeg has reached EOF. Now wait for its exit status.
155
+ stderr_data = await process.stderr.read()
156
+ return_code = await process.wait()
157
+
158
+ if return_code != 0:
159
+ error_text = stderr_data.decode(
160
+ errors="replace"
161
+ ).strip()
162
+
163
+ raise RuntimeError(
164
+ f"FFmpeg failed with exit code {return_code}"
165
+ + (f": {error_text}" if error_text else "")
166
+ )
167
+
168
+ except asyncio.CancelledError:
169
+ raise
170
+
171
+ finally:
172
+ if process.returncode is None:
173
+ try:
174
+ process.terminate()
175
+ await process.wait()
176
+ except Exception:
177
+ pass
178
+
179
+ print("\n[Info] Audio stream finished.", flush=True)
180
+
181
+
182
+ async def send_realtime(
183
+ session,
184
+ audio_queue: asyncio.Queue,
185
+ ) -> None:
186
+ """Send PCM audio chunks to Gemini."""
187
+
188
+ try:
189
+ while True:
190
+ chunk = await audio_queue.get()
191
+
192
+ try:
193
+ if chunk is _INPUT_END:
194
+ await session.send_realtime_input(
195
+ audio_stream_end=True
196
+ )
197
+ return
198
+
199
+ await session.send_realtime_input(
200
+ audio=types.Blob(
201
+ data=chunk,
202
+ mime_type=(
203
+ f"audio/pcm;rate={SEND_SAMPLE_RATE}"
204
+ ),
205
+ )
206
+ )
207
+
208
+ finally:
209
+ audio_queue.task_done()
210
+
211
+ except asyncio.CancelledError:
212
+ raise
213
+
214
+
215
+ async def receive_responses(
216
+ session,
217
+ audio_queue_output: asyncio.Queue,
218
+ response_complete_event: asyncio.Event,
219
+ last_activity: list,
220
+ ) -> None:
221
+ """Receive translated audio and transcription responses."""
222
+
223
+ try:
224
+ async for response in session.receive():
225
+ server_content = getattr(
226
+ response,
227
+ "server_content",
228
+ None,
229
+ )
230
+
231
+ last_activity[0] = time.monotonic()
232
+
233
+ if isinstance(audio_data, bytes):
234
+ last_activity[0] = time.monotonic()
235
+ await audio_queue_output.put(audio_data)
236
+
237
+ if server_content is None:
238
+ continue
239
+
240
+ model_turn = getattr(
241
+ server_content,
242
+ "model_turn",
243
+ None,
244
+ )
245
+
246
+ model_turn = getattr(
247
+ server_content,
248
+ "model_turn",
249
+ None,
250
+ )
251
+
252
+ if model_turn is not None:
253
+ for part in model_turn.parts:
254
+ inline_data = getattr(
255
+ part,
256
+ "inline_data",
257
+ None,
258
+ )
259
+
260
+ if inline_data is None:
261
+ continue
262
+
263
+ audio_data = getattr(
264
+ inline_data,
265
+ "data",
266
+ None,
267
+ )
268
+
269
+ if isinstance(audio_data, bytes):
270
+ last_activity[0] = time.monotonic()
271
+
272
+ await audio_queue_output.put(audio_data)
273
+
274
+ input_transcription = getattr(
275
+ server_content,
276
+ "input_transcription",
277
+ None,
278
+ )
279
+
280
+ if (
281
+ input_transcription is not None
282
+ and input_transcription.text
283
+ ):
284
+ language_code = getattr(
285
+ input_transcription,
286
+ "language_code",
287
+ None,
288
+ )
289
+
290
+ language = (
291
+ f" ({language_code})"
292
+ if language_code
293
+ else ""
294
+ )
295
+
296
+ print(
297
+ f"\n[Source{language}] "
298
+ f"{input_transcription.text}",
299
+ flush=True,
300
+ )
301
+
302
+ output_transcription = getattr(
303
+ server_content,
304
+ "output_transcription",
305
+ None,
306
+ )
307
+
308
+ if (
309
+ output_transcription is not None
310
+ and output_transcription.text
311
+ ):
312
+ language_code = getattr(
313
+ output_transcription,
314
+ "language_code",
315
+ None,
316
+ )
317
+
318
+ language = (
319
+ f" ({language_code})"
320
+ if language_code
321
+ else ""
322
+ )
323
+
324
+ print(
325
+ f"\n[Translation{language}] "
326
+ f"{output_transcription.text}",
327
+ flush=True,
328
+ )
329
+
330
+ turn_complete = getattr(
331
+ server_content,
332
+ "turn_complete",
333
+ False,
334
+ )
335
+
336
+ generation_complete = getattr(
337
+ server_content,
338
+ "generation_complete",
339
+ False,
340
+ )
341
+
342
+ if turn_complete or generation_complete:
343
+ print(
344
+ "\n[Info] Gemini response complete.",
345
+ flush=True,
346
+ )
347
+
348
+ response_complete_event.set()
349
+
350
+ # All audio parts from this response were processed
351
+ # before this marker was inserted.
352
+ await audio_queue_output.put(_OUTPUT_END)
353
+
354
+ return
355
+
356
+ except asyncio.CancelledError:
357
+ raise
358
+
359
+ except Exception:
360
+ print(
361
+ "\n[Error] Receiver crashed:",
362
+ flush=True,
363
+ )
364
+
365
+ traceback.print_exc()
366
+
367
+ response_complete_event.set()
368
+
369
+ # Allow the player to shut down if receiving fails.
370
+ await audio_queue_output.put(_OUTPUT_END)
371
+
372
+
373
+ async def play_audio(
374
+ audio_queue_output: asyncio.Queue,
375
+ ) -> None:
376
+ """Play translated audio until _OUTPUT_END is received."""
377
+
378
+ pya = pyaudio.PyAudio()
379
+ stream = None
380
+
381
+ try:
382
+ stream = await asyncio.to_thread(
383
+ pya.open,
384
+ format=FORMAT,
385
+ channels=CHANNELS,
386
+ rate=RECEIVE_SAMPLE_RATE,
387
+ output=True,
388
+ )
389
+
390
+ while True:
391
+ audio_data = await audio_queue_output.get()
392
+
393
+ try:
394
+ if audio_data is _OUTPUT_END:
395
+ return
396
+
397
+ await asyncio.to_thread(
398
+ stream.write,
399
+ audio_data,
400
+ )
401
+
402
+ finally:
403
+ audio_queue_output.task_done()
404
+
405
+ except asyncio.CancelledError:
406
+ raise
407
+
408
+ finally:
409
+ if stream is not None:
410
+ try:
411
+ stream.stop_stream()
412
+ stream.close()
413
+ except Exception:
414
+ pass
415
+
416
+ pya.terminate()
417
+
418
+
419
+ async def play_original_audio(
420
+ original_playback_queue: asyncio.Queue,
421
+ ) -> None:
422
+ """Play the quiet original audio."""
423
+
424
+ pya = pyaudio.PyAudio()
425
+ stream = None
426
+
427
+ try:
428
+ stream = await asyncio.to_thread(
429
+ pya.open,
430
+ format=FORMAT,
431
+ channels=CHANNELS,
432
+ rate=SEND_SAMPLE_RATE,
433
+ output=True,
434
+ )
435
+
436
+ while True:
437
+ audio_data = await original_playback_queue.get()
438
+
439
+ try:
440
+ await asyncio.to_thread(
441
+ stream.write,
442
+ audio_data,
443
+ )
444
+
445
+ finally:
446
+ original_playback_queue.task_done()
447
+
448
+ except asyncio.CancelledError:
449
+ raise
450
+
451
+ finally:
452
+ if stream is not None:
453
+ try:
454
+ stream.stop_stream()
455
+ stream.close()
456
+ except Exception:
457
+ pass
458
+
459
+ pya.terminate()
460
+
461
+
462
+ async def run(
463
+ url: str,
464
+ target_lang: str,
465
+ api_key: str,
466
+ original_volume: float = 0.08,
467
+ ) -> None:
468
+ """Run the live audio translation."""
469
+
470
+ if not api_key:
471
+ raise ValueError(
472
+ "A Gemini API key is required."
473
+ )
474
+
475
+ client = genai.Client(api_key=api_key)
476
+
477
+ config = types.LiveConnectConfig(
478
+ response_modalities=[types.Modality.AUDIO],
479
+ translation_config=types.TranslationConfig(
480
+ echo_target_language=True,
481
+ target_language_code=target_lang,
482
+ ),
483
+ input_audio_transcription=(
484
+ types.AudioTranscriptionConfig()
485
+ ),
486
+ output_audio_transcription=(
487
+ types.AudioTranscriptionConfig()
488
+ ),
489
+ )
490
+
491
+ audio_queue_input = asyncio.Queue(maxsize=50)
492
+ audio_queue_output = asyncio.Queue()
493
+ original_playback_queue = asyncio.Queue()
494
+
495
+ response_complete_event = asyncio.Event()
496
+ last_activity = [time.monotonic()]
497
+
498
+ model = "gemini-3.5-live-translate-preview"
499
+
500
+ print(
501
+ f"[Info] Connecting to Gemini Live ({model})...",
502
+ flush=True,
503
+ )
504
+
505
+ async with client.aio.live.connect(
506
+ model=model,
507
+ config=config,
508
+ ) as session:
509
+ print(
510
+ "[Info] Connected successfully. Ready to stream!",
511
+ flush=True,
512
+ )
513
+
514
+ async with asyncio.TaskGroup() as task_group:
515
+ stream_task = task_group.create_task(
516
+ stream_audio_url(
517
+ url=url,
518
+ audio_queue=audio_queue_input,
519
+ original_playback_queue=(
520
+ original_playback_queue
521
+ ),
522
+ volume=original_volume,
523
+ )
524
+ )
525
+
526
+ send_task = task_group.create_task(
527
+ send_realtime(
528
+ session=session,
529
+ audio_queue=audio_queue_input,
530
+ )
531
+ )
532
+
533
+ receive_task = task_group.create_task(
534
+ receive_responses(
535
+ session=session,
536
+ audio_queue_output=audio_queue_output,
537
+ response_complete_event=response_complete_event,
538
+ last_activity=last_activity,
539
+ )
540
+ )
541
+
542
+ play_task = task_group.create_task(
543
+ play_audio(
544
+ audio_queue_output=audio_queue_output,
545
+ )
546
+ )
547
+
548
+ original_play_task = task_group.create_task(
549
+ play_original_audio(
550
+ original_playback_queue=(
551
+ original_playback_queue
552
+ ),
553
+ )
554
+ )
555
+
556
+ # Wait until FFmpeg has completely finished reading
557
+ # and queueing the input file.
558
+ await stream_task
559
+
560
+ print(
561
+ "[Info] Input finished. "
562
+ "Sending end-of-stream marker...",
563
+ flush=True,
564
+ )
565
+
566
+ # The sender is still alive here.
567
+ await audio_queue_input.put(_INPUT_END)
568
+
569
+ # Wait until every input chunk and the end marker
570
+ # have been sent to Gemini.
571
+ await audio_queue_input.join()
572
+
573
+ # send_realtime() returns after sending
574
+ # audio_stream_end=True.
575
+ await send_task
576
+
577
+ # Wait for the quiet original audio to finish.
578
+ await original_playback_queue.join()
579
+
580
+ original_play_task.cancel()
581
+
582
+ await asyncio.gather(
583
+ original_play_task,
584
+ return_exceptions=True,
585
+ )
586
+
587
+ # Wait for Gemini's final response signal.
588
+ # Gemini may not send generation_complete or turn_complete
589
+ # for this translation stream. Wait until the response becomes
590
+ # quiet instead.
591
+ last_activity[0] = time.monotonic()
592
+
593
+ quiet_period = 5.0
594
+ maximum_wait = 45.0
595
+ deadline = time.monotonic() + maximum_wait
596
+
597
+ while time.monotonic() < deadline:
598
+ quiet_for = time.monotonic() - last_activity[0]
599
+
600
+ if quiet_for >= quiet_period:
601
+ break
602
+
603
+ await asyncio.sleep(0.25)
604
+
605
+ if time.monotonic() >= deadline:
606
+ print(
607
+ "[Warning] Gemini response timeout reached.",
608
+ flush=True,
609
+ )
610
+ else:
611
+ print(
612
+ "[Info] Gemini response is quiet. "
613
+ "Finishing playback.",
614
+ flush=True,
615
+ )
616
+
617
+ # This is now safe because Gemini has been quiet for 5 seconds.
618
+ await audio_queue_output.put(_OUTPUT_END)
619
+
620
+ # Let the player finish every queued audio chunk.
621
+ try:
622
+ await asyncio.wait_for(
623
+ play_task,
624
+ timeout=30.0,
625
+ )
626
+ except asyncio.TimeoutError:
627
+ print(
628
+ "[Warning] Final audio playback timed out.",
629
+ flush=True,
630
+ )
631
+ play_task.cancel()
632
+
633
+ # The receiver should already have ended after
634
+ # generation_complete or turn_complete.
635
+ if not receive_task.done():
636
+ receive_task.cancel()
637
+
638
+ await asyncio.gather(
639
+ receive_task,
640
+ play_task,
641
+ return_exceptions=True,
642
+ )
643
+
644
+
645
+ def Translator(
646
+ url: str,
647
+ target_lang: str,
648
+ api_key: str,
649
+ original_volume: float = 0.08,
650
+ ):
651
+ """Public synchronous API."""
652
+
653
+ if not url:
654
+ print("[Error] URL cannot be empty.")
655
+ return
656
+
657
+ if not target_lang:
658
+ print("[Error] Target language cannot be empty.")
659
+ return
660
+
661
+ if not api_key:
662
+ print("[Error] API key cannot be empty.")
663
+ return
664
+
665
+ if not 0.0 <= original_volume <= 1.0:
666
+ print(
667
+ "[Error] Original volume must be between 0 and 1."
668
+ )
669
+ return
670
+
671
+ return asyncio.run(
672
+ run(
673
+ url=url,
674
+ target_lang=target_lang,
675
+ api_key=api_key,
676
+ original_volume=original_volume,
677
+ )
678
+ )
679
+
680
+
681
+ if __name__ == "__main__":
682
+ parser = argparse.ArgumentParser(
683
+ description=(
684
+ "Stream audio from a file or URL and translate it "
685
+ "using Gemini Live."
686
+ )
687
+ )
688
+
689
+ parser.add_argument(
690
+ "--url",
691
+ default=(
692
+ "https://storage.googleapis.com/"
693
+ "generativeai-downloads/gemini-cookbook/audio/"
694
+ "gemini-live-translate-sample.wav"
695
+ ),
696
+ help="Audio file path or URL.",
697
+ )
698
+
699
+ parser.add_argument(
700
+ "--target",
701
+ default="bn",
702
+ help="Target language code.",
703
+ )
704
+
705
+ parser.add_argument(
706
+ "--api_key",
707
+ default=os.getenv("GEMINI_API_KEY"),
708
+ help="Gemini API key.",
709
+ )
710
+
711
+ parser.add_argument(
712
+ "--original-volume",
713
+ type=float,
714
+ default=0.08,
715
+ help="Original audio volume from 0.0 to 1.0.",
716
+ )
717
+
718
+ args = parser.parse_args()
719
+
720
+ try:
721
+ asyncio.run(
722
+ run(
723
+ url=args.url,
724
+ target_lang=args.target,
725
+ api_key=args.api_key,
726
+ original_volume=args.original_volume,
727
+ )
728
+ )
729
+
730
+ except KeyboardInterrupt:
731
+ print(
732
+ "\n[Info] Interrupted by user.",
733
+ flush=True,
734
+ )
735
+
736
+ except Exception as error:
737
+ print(
738
+ f"\n[Error] {error}",
739
+ flush=True,
740
+ )
741
+
742
+ traceback.print_exc()
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: translateinpy
3
+ Version: 0.9.6
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: google-genai>=1.35.0
8
+ Requires-Dist: pyaudiowpatch>=0.5.2
9
+ Requires-Dist: ffmpeg>=0.2.0
10
+
11
+ # TranslateINPy
12
+ A python translation module for translating live responses using Gemini API inside your application.
13
+
14
+ ## Usage
15
+
16
+ ```python
17
+ from translateinpy import Translator
18
+ import os
19
+
20
+ api_key = os.getenv("GEMINI_API_KEY")
21
+
22
+ Translator("../sample/hi-1.wav", "en", api_key)
23
+ ```
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install TranslateINPY
29
+ ```
30
+
31
+ or via UV:
32
+
33
+ ```bash
34
+ uv add TranslateINPy
35
+ ```
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/translateinpy/__init__.py
4
+ src/translateinpy/fromAudioFile.py
5
+ src/translateinpy.egg-info/PKG-INFO
6
+ src/translateinpy.egg-info/SOURCES.txt
7
+ src/translateinpy.egg-info/dependency_links.txt
8
+ src/translateinpy.egg-info/requires.txt
9
+ src/translateinpy.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ google-genai>=1.35.0
2
+ pyaudiowpatch>=0.5.2
3
+ ffmpeg>=0.2.0
@@ -0,0 +1 @@
1
+ translateinpy