# anki_exporter.py import os import tempfile import asyncio import genanki import edge_tts import shutil import database async def generate_edge_audio(text, voice, output_path, rate_modifier="+0%"): """Asynchronously streams data packages via the Microsoft Edge API pipeline.""" try: communicate = edge_tts.Communicate(text=text, voice=voice, rate=rate_modifier) await communicate.save(output_path) return True except Exception as e: print(f"Edge-TTS synthesis anomaly: {e}") return False def compile_anki_package(records, output_path, deck_name): """ Compiles database records into a bidirectional card payload package. Resolves voice models dynamically by gender selection parameters and applies global speed coefficient rates from the active configurations. """ # Incrementing both forces a completely clean slate for both layout and deck container model_id = 1684329015 deck_id = 1684329015 # Global Configuration Pace Resolver Mapping settings = database.load_all_settings() or {} config_speed = settings.get("tts_playback_speed", "1.0") # Transform numeric string floats (e.g., 1.2) into Edge-TTS percentage strings (e.g., +20%) try: pct = int((float(config_speed) - 1.0) * 100) rate_string = f"{'+' if pct >= 0 else ''}{pct}%" except Exception: rate_string = "+0%" anki_model = genanki.Model( model_id, 'Spanish Bidirectional Multi-Note HTML Model', fields=[ {'name': 'EnglishText'}, {'name': 'SpanishText'}, {'name': 'Notes'}, {'name': 'AnkiNotes'}, {'name': 'EnglishAudio'}, {'name': 'SpanishAudio'} ], templates=[ { 'name': 'Card 1: English ➔ Spanish', 'qfmt': '
TRANSLATE TO SPANISH:

' '
{{EnglishText}}
' '
{{EnglishAudio}}
', 'afmt': '{{FrontSide}}
' '
{{SpanishText}}

' '
{{Notes}}
' '{{#AnkiNotes}}
Anki Meta: {{AnkiNotes}}
{{/AnkiNotes}}
' '
{{SpanishAudio}}
', }, { 'name': 'Card 2: Spanish ➔ English', 'qfmt': '
TRANSLATE TO ENGLISH:

' '
{{SpanishText}}
' '
{{SpanishAudio}}
', 'afmt': '{{FrontSide}}
' '
{{EnglishText}}

' '
{{Notes}}
' '{{#AnkiNotes}}
Anki Meta: {{AnkiNotes}}
{{/AnkiNotes}}
' '
{{EnglishAudio}}
', } ] ) deck = genanki.Deck(deck_id, deck_name) media_files_to_pack = [] try: loop = asyncio.get_running_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) with tempfile.TemporaryDirectory() as tmpdir: for idx, record in enumerate(records): en_text = record["en_text"] es_text = record["es_text"] gender_flag = record.get("gender", "Female") # Map native neural voice files matching gender settings spanish_voice = "es-ES-AlvaroNeural" if gender_flag == "Male" else "es-ES-ElviraNeural" english_voice = "en-US-EmmaNeural" raw_tags = record.get("tags") or "" note_tags = [t.strip().replace(" ", "_") for t in raw_tags.split(",") if t.strip()] # Dynamic HTML Notes Mapping Builder notes_html_parts = [] if record.get('notes') and record['notes'].strip(): notes_html_parts.append(f"
{record['notes'].strip()}
") if record.get('source_context') and record['source_context'].strip(): notes_html_parts.append(f"
Context: {record['source_context'].strip()}
") notes_html = "".join(notes_html_parts) anki_notes_html = f"
{record['anki_notes'].strip()}
" if record.get('anki_notes') else "" # Standard Unique Media Filenames en_audio_filename = f"edge_en_{idx}_{model_id}.mp3" es_audio_filename = f"edge_es_{idx}_{model_id}.mp3" en_audio_path = os.path.join(tmpdir, en_audio_filename) es_audio_path = os.path.join(tmpdir, es_audio_filename) # English Audio Synthesis if loop.run_until_complete(generate_edge_audio(en_text, english_voice, en_audio_path, rate_string)): media_files_to_pack.append(en_audio_path) en_audio_field = f"[sound:{en_audio_filename}]" else: en_audio_field = "" # Spanish Audio Synthesis if loop.run_until_complete(generate_edge_audio(es_text, spanish_voice, es_audio_path, rate_string)): media_files_to_pack.append(es_audio_path) es_audio_field = f"[sound:{es_audio_filename}]" else: es_audio_field = "" note = genanki.Note( model=anki_model, fields=[en_text, es_text, notes_html, anki_notes_html, en_audio_field, es_audio_field], tags=note_tags ) deck.add_note(note) # Build Package while media assets are guaranteed contextually active inside tmpdir package = genanki.Package(deck) package.media_files = media_files_to_pack package.write_to_file(output_path)