Compare commits

..

1 commit
v19.0 ... main

Author SHA1 Message Date
870438afd4 before refactoring 2026-06-22 20:38:45 +10:00
2 changed files with 59 additions and 24 deletions

83
main.py
View file

@ -115,6 +115,7 @@ class SpanishTrainerApp(QMainWindow):
self.video_first_lang = "English First (en -> es)" self.video_first_lang = "English First (en -> es)"
self.video_repeats_count = "3" self.video_repeats_count = "3"
self.video_pause_duration = "4.0" self.video_pause_duration = "4.0"
self.default_deck_name = "Trainer" # Unified structural configuration fallback
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
@ -132,6 +133,8 @@ class SpanishTrainerApp(QMainWindow):
self.video_repeats_count = row[1] self.video_repeats_count = row[1]
elif row[0] == "video_pause_duration": elif row[0] == "video_pause_duration":
self.video_pause_duration = row[1] self.video_pause_duration = row[1]
elif row[0] == "default_deck_name":
self.default_deck_name = row[1]
except Exception as e: except Exception as e:
print(f"⚠️ Failed to read application settings from database: {e}") print(f"⚠️ Failed to read application settings from database: {e}")
finally: finally:
@ -151,6 +154,8 @@ class SpanishTrainerApp(QMainWindow):
self.video_repeats_count = value self.video_repeats_count = value
elif key == "video_pause_duration": elif key == "video_pause_duration":
self.video_pause_duration = value self.video_pause_duration = value
elif key == "default_deck_name":
self.default_deck_name = value
except Exception as e: except Exception as e:
print(f"❌ Critical: Failed to save setting '{key}': {e}") print(f"❌ Critical: Failed to save setting '{key}': {e}")
finally: finally:
@ -280,7 +285,7 @@ class SpanishTrainerApp(QMainWindow):
self.input_tags.setPlaceholderText("e.g., irregular_er boots_verb") self.input_tags.setPlaceholderText("e.g., irregular_er boots_verb")
self.input_deck_tag = QLineEdit() self.input_deck_tag = QLineEdit()
self.input_deck_tag.setPlaceholderText("Anki Sub-deck Hierarchy") self.input_deck_tag.setPlaceholderText("Overrides System Default Workspace Deck")
button_qss = """ button_qss = """
QPushButton { QPushButton {
@ -357,7 +362,7 @@ class SpanishTrainerApp(QMainWindow):
form_layout.addRow("Classification Profile:", self.combo_type) form_layout.addRow("Classification Profile:", self.combo_type)
form_layout.addRow("Source Context ID (Raw):", self.input_context) form_layout.addRow("Source Context ID (Raw):", self.input_context)
form_layout.addRow("Anki Note Tags:", self.input_tags) form_layout.addRow("Anki Note Tags:", self.input_tags)
form_layout.addRow("<b>Target Deck Scope:</b>", self.input_deck_tag) form_layout.addRow("<b>Target Deck Scope (Optional):</b>", self.input_deck_tag)
crud_buttons = QHBoxLayout() crud_buttons = QHBoxLayout()
self.btn_save = QPushButton(" Create Pair") self.btn_save = QPushButton(" Create Pair")
@ -513,6 +518,11 @@ class SpanishTrainerApp(QMainWindow):
video_layout.addWidget(self.line_video_dir) video_layout.addWidget(self.line_video_dir)
video_layout.addWidget(btn_browse_video) video_layout.addWidget(btn_browse_video)
# New Workspace Target Settings Directive Entry
self.line_default_deck = QLineEdit(self.default_deck_name)
self.line_default_deck.setPlaceholderText("e.g., Spanish::Aula_Plus_1")
self.line_default_deck.textChanged.connect(lambda v: self.save_setting_to_db("default_deck_name", v.strip()))
self.combo_first_lang = QComboBox() self.combo_first_lang = QComboBox()
self.combo_first_lang.addItems(["English First (en -> es)", "Spanish First (es -> en)"]) self.combo_first_lang.addItems(["English First (en -> es)", "Spanish First (es -> en)"])
self.combo_first_lang.setCurrentText(self.video_first_lang) self.combo_first_lang.setCurrentText(self.video_first_lang)
@ -528,6 +538,7 @@ class SpanishTrainerApp(QMainWindow):
form_layout.addRow("<b>Anki Deck Export Destination:</b>", anki_layout) form_layout.addRow("<b>Anki Deck Export Destination:</b>", anki_layout)
form_layout.addRow("<b>Video Assembly Output Target:</b>", video_layout) form_layout.addRow("<b>Video Assembly Output Target:</b>", video_layout)
form_layout.addRow("<b>Default Target Deck String:</b>", self.line_default_deck)
form_layout.addRow("<b>Introductory Anchor Audio Language:</b>", self.combo_first_lang) form_layout.addRow("<b>Introductory Anchor Audio Language:</b>", self.combo_first_lang)
form_layout.addRow("<b>Target Translation Loop Multiplier (Repeats):</b>", self.spin_video_repeats) form_layout.addRow("<b>Target Translation Loop Multiplier (Repeats):</b>", self.spin_video_repeats)
form_layout.addRow("<b>User Recall Repetition Frame Intermission (Seconds):</b>", self.spin_pause_duration) form_layout.addRow("<b>User Recall Repetition Frame Intermission (Seconds):</b>", self.spin_pause_duration)
@ -621,7 +632,6 @@ class SpanishTrainerApp(QMainWindow):
self.lbl_card_text.setText(self.current_active_en_text) self.lbl_card_text.setText(self.current_active_en_text)
self.btn_flip_card.setText("👁️ Reveal Translation") self.btn_flip_card.setText("👁️ Reveal Translation")
# --- t.notes (record[5]) has been intentionally omitted from this layout context ---
meta_str = f"Link ID: {record[0]} | Context: {record[3] or 'N/A'}" meta_str = f"Link ID: {record[0]} | Context: {record[3] or 'N/A'}"
if record[4]: if record[4]:
meta_str += f" | Tags: {record[4]}" meta_str += f" | Tags: {record[4]}"
@ -676,6 +686,10 @@ class SpanishTrainerApp(QMainWindow):
(Card 2) forward-reverse pairs cleanly. Explicitly maps 4 fields: EnglishText, (Card 2) forward-reverse pairs cleanly. Explicitly maps 4 fields: EnglishText,
EnglishAudio, SpanishText, SpanishAudio. Context and notes are fully removed. EnglishAudio, SpanishText, SpanishAudio. Context and notes are fully removed.
""" """
# Architectural Fallback: If pool is empty, run an internal update pass first to grab current matrix records
if not self.flashcard_ids_pool:
self.refresh_review_table()
targets = self.flashcard_ids_pool targets = self.flashcard_ids_pool
if not targets: if not targets:
QMessageBox.warning(self, "Export Aborted", "The active flashcard pool filter is completely empty. Nothing to export.") QMessageBox.warning(self, "Export Aborted", "The active flashcard pool filter is completely empty. Nothing to export.")
@ -746,11 +760,21 @@ class SpanishTrainerApp(QMainWindow):
css='.card { font-family: arial; font-size: 20px; text-align: center; background-color: #fafafa; padding: 25px; border-radius: 8px; }' css='.card { font-family: arial; font-size: 20px; text-align: center; background-color: #fafafa; padding: 25px; border-radius: 8px; }'
) )
deck_name_fallback = records[0][6] if records[0][6] else "Castilian Voice Trainer" # --- RESTRUCTURED STRINGS SAFEGUARD BLOCK ---
anki_deck = genanki.Deck(DECK_ID, f"Spanish::{deck_name_fallback}") # Safeguard checks to normalize index 6 data entry and prevent generation string crashes
raw_deck_entry = records[0][6]
if raw_deck_entry and str(raw_deck_entry).strip():
deck_name_fallback = str(raw_deck_entry).strip()
else:
deck_name_fallback = self.default_deck_name if self.default_deck_name else "Trainer"
# Explicitly ensure the Anki namespace structural hierarchy sequence is intact
full_deck_string = f"Spanish::{deck_name_fallback}" if "Spanish::" not in deck_name_fallback else deck_name_fallback
anki_deck = genanki.Deck(DECK_ID, full_deck_string)
media_files_bundle = [] media_files_bundle = []
print(f"📦 Assembling Anki audio package for {len(records)} notes...") print(f"📦 Assembling Anki audio package for {len(records)} notes under deck: {full_deck_string}...")
for row in records: for row in records:
tx_id, es_text, en_text, context, tags, notes, deck_group = row tx_id, es_text, en_text, context, tags, notes, deck_group = row
@ -792,8 +816,6 @@ class SpanishTrainerApp(QMainWindow):
if context: if context:
tag_list.append(str(context).replace(" ", "_").replace(".", "_")) tag_list.append(str(context).replace(" ", "_").replace(".", "_"))
# Populate note fields sequentially matching model specification:
# EnglishText, EnglishAudio, SpanishText, SpanishAudio
anki_note = genanki.Note( anki_note = genanki.Note(
model=spanish_model, model=spanish_model,
fields=[ fields=[
@ -806,19 +828,28 @@ class SpanishTrainerApp(QMainWindow):
) )
anki_deck.add_note(anki_note) anki_deck.add_note(anki_note)
# Output compilation assembly # Output file name normalization optimization to sweep unmapped character sets
export_output_path = os.path.join(self.anki_export_dir, f"{deck_name_fallback.replace('::', '_')}.apkg") safe_file_name = deck_name_fallback.replace('::', '_').replace('/', '_').strip()
export_output_path = os.path.join(self.anki_export_dir, f"{safe_file_name}.apkg")
package = genanki.Package(anki_deck) try:
package.media_files = list(set(media_files_bundle)) # Excludes duplicate track instances package = genanki.Package(anki_deck)
package.write_to_file(export_output_path) package.media_files = list(set(media_files_bundle))
package.write_to_file(export_output_path)
print(f"✅ Success! Balanced text-audio cards exported cleanly: {export_output_path}") print(f"✅ Success! Balanced text-audio cards exported cleanly: {export_output_path}")
QMessageBox.information( QMessageBox.information(
self, self,
"Anki Package Compiled", "Anki Package Compiled",
f"Successfully compiled {len(records)} balanced text-audio translation flashcard nodes.\n\nDestination:\n{export_output_path}" f"Successfully compiled {len(records)} balanced text-audio translation flashcard nodes.\n\nDestination:\n{export_output_path}"
) )
except Exception as export_err:
print(f"❌ Genanki Write Failure Exception Error: {export_err}")
QMessageBox.critical(
self,
"Export Failure Error",
f"The packaging sub-engine failed to write file to disk:\n{export_err}"
)
def handle_export_video_assets(self): def handle_export_video_assets(self):
pass pass
@ -840,9 +871,12 @@ class SpanishTrainerApp(QMainWindow):
""", (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip())) """, (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip()))
en_id = cursor.lastrowid en_id = cursor.lastrowid
# Pulls from self.input_deck_tag if typed, cleanly defaults to global persistent variable self.default_deck_name
target_deck = self.input_deck_tag.text().strip() or self.default_deck_name
cursor.execute(""" cursor.execute("""
INSERT INTO translations (source_phrase_id, target_phrase_id, deck_name, notes, tags) VALUES (?, ?, ?, ?, ?) INSERT INTO translations (source_phrase_id, target_phrase_id, deck_name, notes, tags) VALUES (?, ?, ?, ?, ?)
""", (es_id, en_id, self.input_deck_tag.text().strip() or "General", self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip())) """, (es_id, en_id, target_deck, self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip()))
tx_id = cursor.lastrowid tx_id = cursor.lastrowid
conn.commit() conn.commit()
@ -881,11 +915,14 @@ class SpanishTrainerApp(QMainWindow):
WHERE id = ? WHERE id = ?
""", (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip(), self.current_sandbox_en_id)) """, (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip(), self.current_sandbox_en_id))
# Updated field configuration using system preferences cache fallback assignment
target_deck = self.input_deck_tag.text().strip() or self.default_deck_name
cursor.execute(""" cursor.execute("""
UPDATE translations UPDATE translations
SET deck_name = ?, notes = ?, tags = ? SET deck_name = ?, notes = ?, tags = ?
WHERE translation_id = ? WHERE translation_id = ?
""", (self.input_deck_tag.text().strip() or "General", self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip(), int(tx_id_str))) """, (target_deck, self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip(), int(tx_id_str)))
conn.commit() conn.commit()
except Exception as e: except Exception as e:
@ -1038,7 +1075,7 @@ class SpanishTrainerApp(QMainWindow):
self.combo_type.setCurrentText(str(record[3]) if record[3] else "phrase") self.combo_type.setCurrentText(str(record[3]) if record[3] else "phrase")
self.input_context.setText(str(record[4]) if record[4] else "") self.input_context.setText(str(record[4]) if record[4] else "")
self.input_tags.setText(str(record[7]) if record[7] is not None else "") self.input_tags.setText(str(record[7]) if record[7] is not None else "")
self.input_deck_tag.setText(str(record[5]) if record[5] else "General") self.input_deck_tag.setText(str(record[5]) if record[5] else "")
self.input_grammar_note.setPlainText(str(record[6]) if record[6] is not None else "") self.input_grammar_note.setPlainText(str(record[6]) if record[6] is not None else "")
self.current_sandbox_es_id = record[8] self.current_sandbox_es_id = record[8]
self.current_sandbox_en_id = record[9] self.current_sandbox_en_id = record[9]
@ -1064,8 +1101,6 @@ class SpanishTrainerApp(QMainWindow):
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setApplicationName("Spanish Voice Trainer")
app.setOrganizationName("Oxnee Pty. Ltd.")
window = SpanishTrainerApp() window = SpanishTrainerApp()
window.show() window.show()
sys.exit(app.exec()) sys.exit(app.exec())

Binary file not shown.