diff --git a/main.py b/main.py
index 18b9bf3..8dc4bf6 100644
--- a/main.py
+++ b/main.py
@@ -778,33 +778,42 @@ class SpanishTrainerApp(QMainWindow):
self.media_player.setPlaybackRate(rate)
# =====================================================================
- # 📦 ARTIFACT EXPORT GATEWAYS (GENANKI LIVE ENGINE)
+ # 📦 ARTIFACT EXPORT GATEWAYS (GENANKI LIVE ENGINE WITH DUAL AUDIO)
# =====================================================================
def handle_export_anki_deck(self):
- """Compiles active subset into functional .apkg with bundled neural audio tracks."""
+ """Compiles active subset into functional .apkg with bundled Spanish and English audio tracks."""
if not self.flashcard_ids_pool:
QMessageBox.warning(self, "Export Cancelled", "The current study stack is empty. Verify your search filters.")
return
# 1. Generate Stable Cryptographic Note Model ID
- model_hash = hashlib.sha256(b"castilian_voice_trainer_model_v1").hexdigest()
+ model_hash = hashlib.sha256(b"castilian_voice_trainer_model_v2").hexdigest()
model_id = int(model_hash[:13], 16)
- # Standardized Castilian Note Template Structure Definition
+ # Standardized Castilian Note Template Structure Definition (Includes English Audio)
spanish_note_model = genanki.Model(
model_id,
- 'Castilian Audio Flashcard Model',
+ 'Castilian Audio Flashcard Model v2',
fields=[
{'name': 'SpanishPhrase'},
{'name': 'EnglishTranslation'},
{'name': 'GrammarNotes'},
- {'name': 'AudioTrack'}
+ {'name': 'SpanishAudio'},
+ {'name': 'EnglishAudio'}
],
templates=[
{
'name': 'Card 1: Auditory Identification',
- 'qfmt': '
{{SpanishPhrase}}
{{AudioTrack}}
',
- 'afmt': '{{FrontSide}}
{{EnglishTranslation}}
{{GrammarNotes}}
',
+ 'qfmt': (
+ '{{SpanishPhrase}}
'
+ '
{{SpanishAudio}}
'
+ ),
+ 'afmt': (
+ '{{FrontSide}}
'
+ '{{EnglishTranslation}}
'
+ '{{EnglishAudio}}
'
+ '{{GrammarNotes}}
'
+ ),
},
],
css='.card { font-family: arial; font-size: 20px; text-align: center; background-color: #f8f9fa; }'
@@ -827,10 +836,10 @@ class SpanishTrainerApp(QMainWindow):
file_title = "".join([c for c in file_title if c.isalnum() or c in (".", "_", "-")]).strip()
destination_path = os.path.join(self.anki_export_dir, file_title)
- # Dictionary to store separate deck objects dynamically based on the DB assignments
decks_map = {}
media_files_manifest = []
- missing_assets_count = 0
+ missing_es_audio = 0
+ missing_en_audio = 0
# 2. Fetch specific database rows matching the current runtime array pool
conn = get_connection()
@@ -838,7 +847,7 @@ class SpanishTrainerApp(QMainWindow):
placeholders = ",".join(["?"] * len(self.flashcard_ids_pool))
query = f"""
- SELECT t.deck_name, p1.text, p2.text, t.notes, t.tags, p1.language
+ SELECT t.deck_name, p1.text, p2.text, t.notes, t.tags
FROM translations t
JOIN phrases p1 ON t.source_phrase_id = p1.id
JOIN phrases p2 ON t.target_phrase_id = p2.id
@@ -855,20 +864,32 @@ class SpanishTrainerApp(QMainWindow):
en_text = row[2].strip()
notes_text = row[3].strip() if row[3] else ""
tags_string = row[4].strip() if row[4] else ""
- lang = row[5]
- # Reconstruct local media naming signature
- safe_audio_name = "".join([c for c in es_text if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
- relative_audio_path = f"media/{safe_audio_name}_{lang}_female.mp3"
- filename_only = f"{safe_audio_name}_{lang}_female.mp3"
+ # --- Handle Spanish Audio Mapping ---
+ safe_es_audio_name = "".join([c for c in es_text if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
+ relative_es_path = f"media/{safe_es_audio_name}_es_female.mp3"
+ es_filename_only = f"{safe_es_audio_name}_es_female.mp3"
- # Check if target file exists. If missing, flag but don't crash
- if os.path.exists(relative_audio_path):
- media_files_manifest.append(relative_audio_path)
- anki_audio_field = f"[sound:{filename_only}]"
+ if os.path.exists(relative_es_path):
+ if relative_es_path not in media_files_manifest:
+ media_files_manifest.append(relative_es_path)
+ anki_es_audio_field = f"[sound:{es_filename_only}]"
else:
- missing_assets_count += 1
- anki_audio_field = "" # Fallback to empty if not generated yet
+ missing_es_audio += 1
+ anki_es_audio_field = ""
+
+ # --- Handle English Audio Mapping ---
+ safe_en_audio_name = "".join([c for c in en_text if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
+ relative_en_path = f"media/{safe_en_audio_name}_en_female.mp3"
+ en_filename_only = f"{safe_en_audio_name}_en_female.mp3"
+
+ if os.path.exists(relative_en_path):
+ if relative_en_path not in media_files_manifest:
+ media_files_manifest.append(relative_en_path)
+ anki_en_audio_field = f"[sound:{en_filename_only}]"
+ else:
+ missing_en_audio += 1
+ anki_en_audio_field = ""
# Handle dynamic deck assignment structure
if db_deck_name not in decks_map:
@@ -879,10 +900,10 @@ class SpanishTrainerApp(QMainWindow):
# Split tags string by spaces into an array list for genanki
parsed_tags = [t for t in tags_string.replace(",", " ").split(" ") if t]
- # Construct unique note entry template matching properties
+ # Construct unique note entry template matching updated fields array
flash_note = genanki.Note(
model=spanish_note_model,
- fields=[es_text, en_text, notes_text, anki_audio_field],
+ fields=[es_text, en_text, notes_text, anki_es_audio_field, anki_en_audio_field],
tags=parsed_tags
)
decks_map[db_deck_name].add_note(flash_note)
@@ -893,9 +914,17 @@ class SpanishTrainerApp(QMainWindow):
package.media_files = media_files_manifest
package.write_to_file(destination_path)
- success_msg = f"✨ Packaged complete!\n\nFile Output: {file_title}\nDestination: {self.anki_export_dir}\nTotal Cards Built: {len(records)}\nDecks Created: {len(decks_map)}"
- if missing_assets_count > 0:
- success_msg += f"\n\n⚠️ Note: {missing_assets_count} cards were bundled without audio tracks because their voice files hadn't been generated in the review window yet."
+ success_msg = (
+ f"✨ Packaging complete!\n\n"
+ f"File Output: {file_title}\n"
+ f"Destination: {self.anki_export_dir}\n"
+ f"Total Cards Built: {len(records)}\n"
+ f"Decks Created: {len(decks_map)}"
+ )
+
+ if missing_es_audio > 0 or missing_en_audio > 0:
+ success_msg += f"\n\n⚠️ Note: Missing files detected (Spanish: {missing_es_audio}, English: {missing_en_audio}). " \
+ f"Cards were bundled without corresponding audio fields if they hadn't been triggered in the review window yet."
QMessageBox.information(self, "Export Complete", success_msg)