139_spanish-voice-trainer/main.py

129 lines
5.3 KiB
Python
Raw Normal View History

2026-06-12 11:58:47 +00:00
# main.py
2026-06-14 09:56:43 +00:00
import sys
import os
2026-06-24 05:37:22 +00:00
from PyQt6.QtWidgets import QMainWindow, QTabWidget, QApplication, QMessageBox, QVBoxLayout, QWidget
from PyQt6.QtCore import pyqtSlot
2026-06-24 05:37:22 +00:00
# Import our unified data layer
import database
2026-06-19 05:30:59 +00:00
2026-06-24 05:37:22 +00:00
# Import the UI components from our sub-package
from tabs.sandbox_tab import SandboxTab
from tabs.review_tab import ReviewTab
from tabs.settings_tab import SettingsTab
2026-06-12 11:58:47 +00:00
2026-06-24 05:37:22 +00:00
# Import our background engine runners
import anki_exporter
import video_generator
class MainWindow(QMainWindow):
2026-06-14 09:56:43 +00:00
def __init__(self):
super().__init__()
2026-06-24 05:37:22 +00:00
self.setWindowTitle("Castilian Spanish Voice Trainer")
self.setMinimumSize(900, 650)
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
# 1. Ensure database structure exists on startup
database.ensure_database_populated()
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
# 2. In-memory cache for application configurations
self.app_settings = {}
self.refresh_settings_cache()
2026-06-19 05:09:05 +00:00
2026-06-24 05:37:22 +00:00
# 3. Setup central tabbed layout container
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.main_layout = QVBoxLayout(self.central_widget)
2026-06-19 03:42:28 +00:00
2026-06-24 05:37:22 +00:00
self.tab_widget = QTabWidget()
self.main_layout.addWidget(self.tab_widget)
2026-06-21 13:54:17 +00:00
2026-06-24 05:37:22 +00:00
# 4. Instantiate isolated tab modules
self.sandbox_tab = SandboxTab()
self.review_tab = ReviewTab()
self.settings_tab = SettingsTab()
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
# 5. Mount tabs with Database Sandbox on the far left (Index 0)
self.tab_widget.addTab(self.sandbox_tab, "Database Sandbox")
self.tab_widget.addTab(self.review_tab, "Flashcard Review")
self.tab_widget.addTab(self.settings_tab, "Configuration Settings")
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
# 6. Wire up the cross-module communication channels (Signals)
self.wire_application_signals()
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
def refresh_settings_cache(self):
"""Loads all database settings into an easy-to-read dictionary."""
self.app_settings = database.load_all_settings()
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
def wire_application_signals(self):
"""Connects tab user-actions to execution managers inside main.py."""
# Settings Tab Signal Actions
self.settings_tab.settings_changed.connect(self.handle_settings_update)
self.settings_tab.export_anki_requested.connect(self.execute_anki_export)
self.settings_tab.generate_video_requested.connect(self.execute_video_generation)
2026-06-19 05:09:05 +00:00
2026-06-24 05:37:22 +00:00
# Sandbox / Review Tab synchronization signals
self.sandbox_tab.data_mutated.connect(self.refresh_ui_views)
2026-06-19 05:09:05 +00:00
2026-06-24 05:37:22 +00:00
@pyqtSlot(str, str)
def handle_settings_update(self, key, value):
"""Saves a modified setting value directly down to the database and updates memory cache."""
database.save_setting_to_db(key, value)
self.refresh_settings_cache()
2026-06-19 05:09:05 +00:00
2026-06-24 05:37:22 +00:00
@pyqtSlot(str)
def execute_anki_export(self, full_deck_name):
"""Bridges data coordinates out of SQLite to trigger the text-to-speech anki file compiler."""
self.refresh_settings_cache()
output_dir = self.app_settings.get("anki_export_directory", os.path.expanduser("~"))
output_file = os.path.join(output_dir, f"{full_deck_name.replace('::', '_')}.apkg")
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
# Pull all active translation records out via explicit SQL matching
records = database.get_all_translations_explicit()
2026-06-14 09:56:43 +00:00
2026-06-20 01:55:05 +00:00
if not records:
2026-06-24 05:37:22 +00:00
QMessageBox.warning(self, "Export Failed", "The database contains no translation records to export.")
2026-06-21 13:54:17 +00:00
return
2026-06-22 10:38:45 +00:00
try:
2026-06-24 05:37:22 +00:00
anki_exporter.compile_anki_package(records, output_file, full_deck_name)
QMessageBox.information(self, "Export Success", f"Successfully compiled light-weight TTS deck to:\n{output_file}")
2026-06-21 13:54:17 +00:00
except Exception as e:
2026-06-24 05:37:22 +00:00
QMessageBox.critical(self, "Export Error", f"Failed to parse or write Anki package archive:\n{str(e)}")
2026-06-21 13:54:17 +00:00
2026-06-24 05:37:22 +00:00
@pyqtSlot()
def execute_video_generation(self):
"""Coordinates data streams to invoke the independent video compilation generator."""
self.refresh_settings_cache()
records = database.get_all_translations_explicit()
2026-06-21 13:54:17 +00:00
2026-06-24 05:37:22 +00:00
if not records:
QMessageBox.warning(self, "Generation Failed", "No phrases available to construct a training video playlist loops.")
2026-06-14 09:56:43 +00:00
return
2026-06-24 05:37:22 +00:00
# UI updates can block execution loops, so we notify before processing
self.statusBar().showMessage("Generating training video assets. Please wait...")
QApplication.processEvents()
2026-06-14 09:56:43 +00:00
2026-06-24 05:37:22 +00:00
try:
video_generator.generate_training_video(records, self.app_settings)
QMessageBox.information(self, "Success", "MP4 Video compilation completed successfully.")
self.statusBar().showMessage("Video compilation completed.", 5000)
except Exception as e:
QMessageBox.critical(self, "Video Error", f"An error occurred during frame rendering sequences:\n{str(e)}")
self.statusBar().clearMessage()
2026-06-24 05:37:22 +00:00
@pyqtSlot()
def refresh_ui_views(self):
"""Instructs distinct active tabs to reload data windows after database changes occur."""
self.sandbox_tab.reload_table_display()
self.review_tab.reload_review_pool()
2026-06-19 03:42:28 +00:00
2026-06-12 11:58:47 +00:00
if __name__ == "__main__":
2026-06-21 13:54:17 +00:00
app = QApplication(sys.argv)
2026-06-24 05:37:22 +00:00
# Modern clean styling choices for desktop applications
app.setStyle('Fusion')
window = MainWindow()
2026-06-21 13:54:17 +00:00
window.show()
sys.exit(app.exec())