OKIRIA commited on
Commit
4635155
Β·
0 Parent(s):

Initial commit of EAC Translator

Browse files
Files changed (4) hide show
  1. .gitignore +15 -0
  2. README.md +27 -0
  3. app.py +155 -0
  4. requirements.txt +9 -0
.gitignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python cache
2
+ __pycache__/
3
+ *.pyc
4
+
5
+ # Audio output
6
+ *.wav
7
+
8
+ # Virtual environments
9
+ venv/
10
+ env/
11
+ opus-env/
12
+
13
+ # System files
14
+ .DS_Store
15
+ Thumbs.db
README.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🌍 EAC Translator
2
+
3
+ A multilingual translator for English, French, and Swahili with voice input and output. Built with ❀️ by [Eng. Jobbers – Qtrinova Inc](https://eng-jobbers.vercel.app/).
4
+
5
+ ## ✨ Features
6
+
7
+ - πŸŽ™οΈ Voice input via microphone
8
+ - 🧠 Automatic language detection
9
+ - πŸ” English ↔ French ↔ Swahili (with pivot logic)
10
+ - πŸŽ›οΈ Tone control (Neutral, Formal, Casual, Romantic)
11
+ - πŸ”Š Text-to-speech with voice selection (David, Zira, etc.)
12
+ - πŸ“± Mobile-friendly UI
13
+ - πŸ“€ Audio playback and download
14
+
15
+ ## πŸš€ Built With
16
+
17
+ - [Gradio](https://gradio.app)
18
+ - [Hugging Face Transformers](https://huggingface.co/transformers/)
19
+ - [pyttsx3](https://pypi.org/project/pyttsx3/)
20
+ - [SpeechRecognition](https://pypi.org/project/SpeechRecognition/)
21
+ - [langid](https://pypi.org/project/langid/)
22
+
23
+ ## πŸ“¦ Installation (Optional for Local Use, especially VScode env setup, well tested)
24
+
25
+ ```bash
26
+ pip install -r requirements.txt
27
+ python app.py
app.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from transformers import MarianMTModel, MarianTokenizer
3
+ from datetime import datetime
4
+ import langid
5
+ import os
6
+ import pyttsx3
7
+ import time
8
+ import warnings
9
+
10
+ warnings.filterwarnings("ignore", message="Recommended: pip install sacremoses.")
11
+ os.environ["PATH"] += os.pathsep + r"C:\ffmpeg\bin"
12
+ langid.set_languages(['en', 'fr', 'sw'])
13
+
14
+ MODEL_MAP = {
15
+ "English β†’ Swahili": "Helsinki-NLP/opus-mt-en-sw",
16
+ "English β†’ French": "Helsinki-NLP/opus-mt-en-fr",
17
+ "French β†’ English": "Helsinki-NLP/opus-mt-fr-en",
18
+ "French β†’ Swahili (via English)": ["Helsinki-NLP/opus-mt-fr-en", "Helsinki-NLP/opus-mt-en-sw"]
19
+ }
20
+
21
+ TONE_MODIFIERS = {
22
+ "Neutral": "",
23
+ "Romantic": "Express this romantically: ",
24
+ "Formal": "Translate this in a formal tone: ",
25
+ "Casual": "Make this sound casual: "
26
+ }
27
+
28
+ loaded_models = {}
29
+
30
+ def load_model(model_name):
31
+ if model_name not in loaded_models:
32
+ tokenizer = MarianTokenizer.from_pretrained(model_name)
33
+ model = MarianMTModel.from_pretrained(model_name)
34
+ loaded_models[model_name] = (tokenizer, model)
35
+ return loaded_models[model_name]
36
+
37
+ def detect_language(text):
38
+ try:
39
+ lang, score = langid.classify(text)
40
+ return lang
41
+ except:
42
+ return "unknown"
43
+
44
+ def translate(text, direction, tone):
45
+ detected_lang = detect_language(text)
46
+ expected_src = direction.split(" β†’ ")[0].lower()
47
+ warning = ""
48
+ if expected_src.startswith("english") and detected_lang != "en":
49
+ warning = f"⚠️ Detected language is '{detected_lang}', but you selected English as source."
50
+ elif expected_src.startswith("french") and detected_lang != "fr":
51
+ warning = f"⚠️ Detected language is '{detected_lang}', but you selected French as source."
52
+ elif expected_src.startswith("swahili") and detected_lang != "sw":
53
+ warning = f"⚠️ Detected language is '{detected_lang}', but you selected Swahili as source."
54
+
55
+ prompt = TONE_MODIFIERS[tone] + text
56
+ model_info = MODEL_MAP[direction]
57
+
58
+ if isinstance(model_info, list):
59
+ tokenizer1, model1 = load_model(model_info[0])
60
+ encoded1 = tokenizer1(prompt, return_tensors="pt", padding=True, truncation=True)
61
+ intermediate = model1.generate(**encoded1)
62
+ intermediate_text = tokenizer1.decode(intermediate[0], skip_special_tokens=True)
63
+
64
+ tokenizer2, model2 = load_model(model_info[1])
65
+ encoded2 = tokenizer2(intermediate_text, return_tensors="pt", padding=True, truncation=True)
66
+ final = model2.generate(**encoded2)
67
+ translation = tokenizer2.decode(final[0], skip_special_tokens=True)
68
+ else:
69
+ tokenizer, model = load_model(model_info)
70
+ encoded = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True)
71
+ generated = model.generate(**encoded)
72
+ translation = tokenizer.decode(generated[0], skip_special_tokens=True)
73
+
74
+ with open("translation_log.txt", "a", encoding="utf-8") as f:
75
+ f.write(f"[{datetime.now()}] {direction} | Tone: {tone}\n")
76
+ f.write(f"Input: {text}\nOutput: {translation}\n\n")
77
+
78
+ return f"{warning}\n{translation}" if warning else translation
79
+
80
+ engine = pyttsx3.init()
81
+ voices = engine.getProperty('voices')
82
+ voice_names = [voice.name for voice in voices]
83
+
84
+ def speak_text_to_file(text, voice_name):
85
+ try:
86
+ engine = pyttsx3.init()
87
+ engine.setProperty('rate', 150)
88
+ for voice in voices:
89
+ if voice.name == voice_name:
90
+ engine.setProperty('voice', voice.id)
91
+ break
92
+ output_path = "tts_output.wav"
93
+ engine.save_to_file(text, output_path)
94
+ engine.runAndWait()
95
+ return output_path
96
+ except Exception:
97
+ return None
98
+
99
+ def transcribe_and_translate(audio_path, direction, tone):
100
+ import speech_recognition as sr
101
+ recognizer = sr.Recognizer()
102
+ try:
103
+ with sr.AudioFile(audio_path) as source:
104
+ audio = recognizer.record(source)
105
+ if len(audio.frame_data) < 10000:
106
+ return "⚠️ Audio too short or empty. Please try again."
107
+ text = recognizer.recognize_google(audio)
108
+ return translate(text, direction, tone)
109
+ except Exception as e:
110
+ return f"⚠️ Could not transcribe audio: {e}"
111
+
112
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
113
+ gr.Markdown("## 🌍 EAC Translator")
114
+ gr.Markdown("Supports English, French, and Swahili. Includes tone control, language detection, voice input, and speech playback.")
115
+
116
+ with gr.Tabs():
117
+ with gr.Tab("πŸ“ Text Translation"):
118
+ with gr.Column():
119
+ input_text = gr.Textbox(label="Text to Translate", lines=3)
120
+ direction = gr.Dropdown(choices=list(MODEL_MAP.keys()), label="Translation Direction", value="English β†’ Swahili")
121
+ tone = gr.Radio(choices=list(TONE_MODIFIERS.keys()), label="Tone", value="Neutral")
122
+ output_text = gr.Textbox(label="Translated Text", lines=3)
123
+ voice_choice = gr.Dropdown(choices=voice_names, label="Voice for Playback", value=voice_names[0])
124
+ with gr.Row():
125
+ translate_btn = gr.Button("Translate", scale=1)
126
+ speak_btn = gr.Button("πŸ”Š Listen to Translation", scale=1)
127
+ audio_output = gr.Audio(label="Playback", interactive=False)
128
+
129
+ with gr.Tab("πŸŽ™οΈ Voice Translation"):
130
+ with gr.Column():
131
+ audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Speak Now")
132
+ direction_voice = gr.Dropdown(choices=list(MODEL_MAP.keys()), label="Translation Direction", value="English β†’ Swahili")
133
+ tone_voice = gr.Radio(choices=list(TONE_MODIFIERS.keys()), label="Tone", value="Neutral")
134
+ voice_output = gr.Textbox(label="Translated Text")
135
+ voice_choice2 = gr.Dropdown(choices=voice_names, label="Voice for Playback", value=voice_names[0])
136
+ with gr.Row():
137
+ voice_translate_btn = gr.Button("Transcribe & Translate", scale=1)
138
+ voice_speak_btn = gr.Button("πŸ”Š Listen to Translation", scale=1)
139
+ audio_output2 = gr.Audio(label="Playback", interactive=False)
140
+
141
+ translate_btn.click(fn=translate, inputs=[input_text, direction, tone], outputs=output_text)
142
+ speak_btn.click(fn=speak_text_to_file, inputs=[output_text, voice_choice], outputs=audio_output)
143
+ voice_translate_btn.click(fn=transcribe_and_translate, inputs=[audio_input, direction_voice, tone_voice], outputs=voice_output)
144
+ voice_speak_btn.click(fn=speak_text_to_file, inputs=[voice_output, voice_choice2], outputs=audio_output2)
145
+
146
+ gr.Markdown(
147
+ """<div style='text-align: center;'>
148
+ <a href='https://eng-jobbers.vercel.app/' target='_blank' style='text-decoration: none; font-weight: bold;'>
149
+ By Eng. Jobbers – Qtrinova Inc. NLP❀️
150
+ </a>
151
+ </div>""",
152
+ elem_id="footer"
153
+ )
154
+
155
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.35.0
2
+ transformers==4.53.0
3
+ torch==2.7.1
4
+ sentencepiece==0.2.0
5
+ langid
6
+ speechrecognition
7
+ pydub
8
+ pyttsx3
9
+ sacremoses