phrase manager added

This commit is contained in:
stephen 2026-06-13 12:03:58 +10:00
parent bb6dcb7630
commit c7753e6e0e
6 changed files with 220 additions and 9 deletions

Binary file not shown.

78
core/bulk_importer.py Normal file
View file

@ -0,0 +1,78 @@
# core/bulk_importer.py
import re
from database.connection import get_connection
class BulkImporter:
def __init__(self):
# Matches typical patterns: "Spanish word", "English translation", "Unit metadata"
self.row_regex = re.compile(r'"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"')
def clean_text(self, text: str) -> str:
"""Strips newlines and extra spaces from extracted data fields."""
return text.replace('\n', ' ').strip()
def parse_unit(self, context_str: str) -> int:
"""
Extracts the unit integer from codes like 'U5_3D', 'U7 4A', or 'UG_11B'.
Returns None if it's a general marker like 'UT LEX'.
"""
match = re.search(r'U([0-9])', context_str)
if match:
return int(match.group(1))
return None
def import_glossary_file(self, file_path: str, textbook_name: str):
"""Reads the structural text lines and inserts them into the translation database."""
conn = get_connection()
cursor = conn.cursor()
print(f"📖 Starting bulk ingestion for '{file_path}'...")
count = 0
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Find all matching row structures inside the text
matches = self.row_regex.findall(content)
for es_raw, en_raw, ctx_raw in matches:
spanish_text = self.clean_text(es_raw)
english_text = self.clean_text(en_raw)
context_tag = self.clean_text(ctx_raw)
unit_number = self.parse_unit(context_tag)
# Skip header rows or structural markers
if spanish_text.lower() in ["alphabetical glossary", "spanish", "word"]:
continue
# 1. Insert Spanish Term
cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context)
VALUES (?, 'es', ?, ?, ?)
""", (spanish_text, textbook_name, unit_number, context_tag))
es_id = cursor.lastrowid
# 2. Insert English Term
cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context)
VALUES (?, 'en', ?, ?, ?)
""", (english_text, textbook_name, unit_number, context_tag))
en_id = cursor.lastrowid
# 3. Create Bidirectional Bridges
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (es_id, en_id))
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (en_id, es_id))
count += 1
conn.commit()
print(f"🎉 Successfully imported {count} linked glossary pairs into SQLite!")
return count
except Exception as e:
conn.rollback()
print(f"❌ Error during bulk data ingestion: {e}")
return 0
finally:
conn.close()

71
core/phrase_manager.py Normal file
View file

@ -0,0 +1,71 @@
import os
import asyncio
import re
from database.connection import get_connection
from core.asset_generator import AssetGenerator
class PhraseManager:
def __init__(self):
self.asset_gen = AssetGenerator()
def _clean_filename(self, text: str) -> str:
"""Filters forbidden system characters but preserves Spanish diacritics."""
safe_text = re.sub(r'[/\\?%*:|"<>]', '', text)
return safe_text.strip().replace(" ", "_")
async def add_translation_pair(self, spanish_text: str, english_text: str,
textbook: str = None, unit: int = None,
context: str = None, voice_gender: str = "male"):
"""
Takes a multi-lingual pair, automates audio generation,
and securely links them together in the SQLite database.
"""
conn = get_connection()
cursor = conn.cursor()
try:
# 1. Insert Spanish Phrase Row
cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context)
VALUES (?, 'es', ?, ?, ?)
""", (spanish_text, textbook, unit, context))
es_id = cursor.lastrowid
# 2. Insert English Phrase Row
cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context)
VALUES (?, 'en', ?, ?, ?)
""", (english_text, textbook, unit, context))
en_id = cursor.lastrowid
# 3. Create Bidirectional Translation Bridges
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (es_id, en_id))
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (en_id, es_id))
# 4. Automate Castilian Audio Generation
safe_filename = self._clean_filename(spanish_text)
audio_filename = f"{safe_filename}_{voice_gender}.mp3"
output_path = os.path.join("media", audio_filename)
# Fire our cloud TTS utility
await self.asset_gen.generate_speech(spanish_text, output_path, gender=voice_gender)
selected_voice_name = self.asset_gen.voices.get(voice_gender.lower(), "es-ES-AlvaroNeural")
# 5. Log Audio Track to Database
cursor.execute("""
INSERT INTO audio_tracks (phrase_id, voice_gender, voice_name, file_path, is_reference)
VALUES (?, ?, ?, ?, 1)
""", (es_id, voice_gender.lower(), selected_voice_name, output_path))
# Commit everything at once securely
conn.commit()
print(f"✨ Successfully integrated: '{spanish_text}''{english_text}'")
print(f" Audio generated safely at: {output_path}")
return True
except Exception as e:
conn.rollback()
print(f"❌ Error during phrase ingestion transaction: {e}")
return False
finally:
conn.close()

View file

@ -23,6 +23,10 @@
- [12.2. Cask Extension (--cask)](#122-cask-extension---cask)
- [12.3. Why This Is Useful](#123-why-this-is-useful)
- [13. Beekeeper Studio](#13-beekeeper-studio)
- [14. Does edge-tts always connect to the Microsoft Cloud ?](#14-does-edge-tts-always-connect-to-the-microsoft-cloud-)
- [14.1. How edge-tts Works (The Cloud Pipeline)](#141-how-edge-tts-works-the-cloud-pipeline)
- [14.2. Why This is Highly Advantageous for Phase 1](#142-why-this-is-highly-advantageous-for-phase-1)
- [14.3. Future Proofing: Going Fully Offline Down the Track](#143-future-proofing-going-fully-offline-down-the-track)
# 1. spanish-voice-trainer
# 2. Project Summary:
@ -332,4 +336,47 @@ Furthermore, whenever you run your system updates down the road using brew upgra
Has an introduction YouTube video
The main thing is you double click apon the data base.db we created.
![Bee Keeper Interface](./images/image-01.png)
![Bee Keeper Interface](./images/image-01.png)
# 14. Does edge-tts always connect to the Microsoft Cloud ?
The short answer is yes.
edge-tts is a Python library that wraps around the communication protocol used by Microsoft Edge's built-in "Read Aloud" feature. Because it doesn't run neural voice models locally on your MacBook, it must make an active network connection to the Microsoft cloud servers every time you request audio synthesis.
Here is a closer look at what is happening under the hood when you call await communicate.save() on Scotty:
## 14.1. How edge-tts Works (The Cloud Pipeline)
Instead of keeping massive gigabyte-sized neural networks running in your local system memory, edge-tts leverages Microsofts cloud infrastructure using secure WebSockets.
The Request: Your Python script establishes an asymmetrical WebSocket connection to a Microsoft Azure speech endpoint.
The Handshake: It passes your text string along with configuration meta-data (like requesting the specific es-ES-AlvaroNeural Castilian voice).
The Synthesis: Microsoft's remote cloud supercomputers synthesize the text into human-like phonemes in real time.
The Stream: The server streams raw audio binary packets back down to edge-tts, which chunks them cleanly together and writes them to disk as your target .mp3 file.
Because this relies entirely on a remote API endpoint, if Scotty is completely offline or disconnected from Wi-Fi, the edge-tts script will throw a connection error and fail to generate audio.
## 14.2. Why This is Highly Advantageous for Phase 1
While running things offline is fantastic for complete privacy, using a cloud-backed pipeline for your audio assets right now offers three massive benefits for your project development:
Zero Hardware Strain: Deep-learning voice synthesis is computationally heavy. Running a true high-fidelity audio model locally requires significant CPU/GPU processing. edge-tts offloads 100% of that heavy lifting to the cloud, meaning your script runs instantly in seconds without draining your MacBook's battery or spinning up the fans.
Instant, Production-Ready Audio: You don't have to download, compile, or tune massive multi-gigabyte voice weights (like tortoise-tts or Bark) inside your Python environment. You get pristine, studio-quality Castilian speech with two lines of code.
## 14.3. Future Proofing: Going Fully Offline Down the Track
Because you prefer data privacy and local-first software engineering, it's worth noting that your current architecture is completely future-proofed for offline migration when you reach an advanced phase of development.
Because we explicitly decoupled your code—separating the asset creation logic into core/asset_generator.py and the application logic into main.py—you can swap out the backend audio engine later without changing a single line of your GUI code.
If you want to make your voice trainer run 100% locally in the future without hitting the cloud, you can replace the internal logic of AssetGenerator with an offline framework like:
Piper TTS: A fast, local, neural text-to-speech system optimized to run beautifully on local hardware architectures.
Kokoro: A highly lightweight, open-source local voice model that produces incredibly realistic human inflections.
For now, leveraging Microsoft's cloud via edge-tts gives you native-speaker accuracy with zero setup friction, letting you focus entirely on mastering your Aula Internacional curriculum!
I suppose this OK in phase 1 , I may want to migrate Kokoro next

Binary file not shown.

31
main.py
View file

@ -1,18 +1,33 @@
# main.py
import sys
import asyncio
from database.connection import init_db
from core.phrase_manager import PhraseManager
def main():
print("🚀 Booting Castilian Voice Trainer...")
async def test_pipeline():
print("🚀 Booting Castilian Voice Trainer Ingestion Engine...")
# Step 1: Ensure infrastructure is present before doing anything else
# Ensure database is present
init_db()
# Step 2: Initialize Core Engines (AudioEngine, AssetGenerator)
# Step 3: Launch your PyQt6 Graphical User Interface
print("🖥️ Launching GUI...")
# Initialize the phrase controller manager
manager = PhraseManager()
# (PyQt6 window exec loops will go here)
# Test Entry: Let's log an authentic textbook phrase from Aula Internacional
print("\n📥 Processing sample entry...")
success = await manager.add_translation_pair(
spanish_text="¿Cómo se pronuncia esta palabra?",
english_text="How do you pronounce this word?",
textbook="Aula Internacional 1 Plus",
unit=2,
context="Glossary / Lesson Terms",
voice_gender="female" # Let's verify Elvira's voice on this one!
)
if success:
print("\n🎉 Verification Phase 1 Pipeline Test complete!")
else:
print("\n⚠️ Pipeline processing failed.")
if __name__ == "__main__":
main()
asyncio.run(test_pipeline())