Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 870438afd4 |
2 changed files with 59 additions and 24 deletions
83
main.py
83
main.py
|
|
@ -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.
Loading…
Reference in a new issue