this version replaced edge-tts with kokoro locally based tts model
This commit is contained in:
parent
4a4d16c093
commit
2346bacd5d
37 changed files with 383 additions and 1263 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -25,4 +25,7 @@ media/*
|
||||||
# IDE and Environment Configurations
|
# IDE and Environment Configurations
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
*.onnx
|
||||||
|
*.bin
|
||||||
51
Spanish Voice Trainer.spec
Normal file
51
Spanish Voice Trainer.spec
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['main.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
[],
|
||||||
|
exclude_binaries=True,
|
||||||
|
name='Spanish Voice Trainer',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
console=False,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
icon=['app.icns'],
|
||||||
|
)
|
||||||
|
coll = COLLECT(
|
||||||
|
exe,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
name='Spanish Voice Trainer',
|
||||||
|
)
|
||||||
|
app = BUNDLE(
|
||||||
|
coll,
|
||||||
|
name='Spanish Voice Trainer.app',
|
||||||
|
icon='app.icns',
|
||||||
|
bundle_identifier=None,
|
||||||
|
)
|
||||||
|
|
@ -1,32 +1,20 @@
|
||||||
# anki_exporter.py
|
# anki_exporter.py
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import asyncio
|
|
||||||
import time
|
import time
|
||||||
import genanki
|
import genanki
|
||||||
import edge_tts
|
|
||||||
import database
|
import database
|
||||||
from tts_utils import parse_text_for_edgetts, get_configured_tts_rate
|
from tts_utils import parse_text_for_edgetts
|
||||||
|
from kokoro_utils import generate_local_speech
|
||||||
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):
|
def compile_anki_package(records, output_path, deck_name):
|
||||||
"""
|
"""
|
||||||
Compiles database records into a bidirectional card payload package.
|
Compiles database records into a bidirectional card payload package.
|
||||||
Resolves voice models dynamically by gender selection parameters and applies
|
Resolves voice models dynamically by gender selection parameters and applies
|
||||||
global speed coefficient rates from the active configurations.
|
speed multipliers from active configurations using local Kokoro TTS.
|
||||||
Injects sort_order as field 0 for structured card sequencing.
|
Injects sort_order as field 0 for structured card sequencing.
|
||||||
"""
|
"""
|
||||||
# Generate deterministic positive 32-bit integers from deck name and model name
|
# Generate deterministic positive 32-bit integers from deck name and model name
|
||||||
# to avoid collisions across different decks while keeping imports stable
|
|
||||||
deck_id = abs(hash(deck_name)) % (2**31)
|
deck_id = abs(hash(deck_name)) % (2**31)
|
||||||
model_id = abs(hash("Spanish Bidirectional Multi-Note HTML Model")) % (2**31)
|
model_id = abs(hash("Spanish Bidirectional Multi-Note HTML Model")) % (2**31)
|
||||||
|
|
||||||
|
|
@ -35,7 +23,10 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
|
|
||||||
# Global Configuration Pace Resolver Mapping
|
# Global Configuration Pace Resolver Mapping
|
||||||
settings = database.load_all_settings() or {}
|
settings = database.load_all_settings() or {}
|
||||||
rate_string = get_configured_tts_rate(settings)
|
try:
|
||||||
|
speed_multiplier = float(settings.get("tts_playback_speed", 1.0))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
speed_multiplier = 1.0
|
||||||
|
|
||||||
anki_model = genanki.Model(
|
anki_model = genanki.Model(
|
||||||
model_id,
|
model_id,
|
||||||
|
|
@ -52,8 +43,8 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
{
|
{
|
||||||
'name': 'Card 1: English ➔ Spanish',
|
'name': 'Card 1: English ➔ Spanish',
|
||||||
'qfmt': '<div style="font-family: Arial; font-size: 13px; font-weight: bold; color: #BDC3C7; text-align: center; letter-spacing: 1px;">TRANSLATE TO SPANISH:</div><br>'
|
'qfmt': '<div style="font-family: Arial; font-size: 13px; font-weight: bold; color: #BDC3C7; text-align: center; letter-spacing: 1px;">TRANSLATE TO SPANISH:</div><br>'
|
||||||
'<div style="font-family: Arial; font-size: 20px; text-align: center; color: #34495E;"><b>{{EnglishText}}</b></div>'
|
'<div style="font-family: Arial; font-size: 20px; text-align: center; color: #34495E;"><b>{{EnglishText}}</b></div><br>'
|
||||||
'<div style="display:none;">{{EnglishAudio}}</div>',
|
'<div style="text-align: center;">{{EnglishAudio}}</div>',
|
||||||
'afmt': '{{FrontSide}}<hr id="answer">'
|
'afmt': '{{FrontSide}}<hr id="answer">'
|
||||||
'<div style="font-family: Arial; font-size: 28px; text-align: center; color: #2980B9; font-weight: bold;">{{SpanishText}}</div><br>'
|
'<div style="font-family: Arial; font-size: 28px; text-align: center; color: #2980B9; font-weight: bold;">{{SpanishText}}</div><br>'
|
||||||
'{{#AnkiNotes}}<div style="font-family: Arial; font-size: 13px; text-align: center; color: #8E44AD; border-top: 1px dashed #E5E7E9; padding-top: 6px; margin-top: 6px;"><b>Anki Meta:</b> {{{AnkiNotes}}}</div>{{/AnkiNotes}}<br>'
|
'{{#AnkiNotes}}<div style="font-family: Arial; font-size: 13px; text-align: center; color: #8E44AD; border-top: 1px dashed #E5E7E9; padding-top: 6px; margin-top: 6px;"><b>Anki Meta:</b> {{{AnkiNotes}}}</div>{{/AnkiNotes}}<br>'
|
||||||
|
|
@ -62,8 +53,8 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
{
|
{
|
||||||
'name': 'Card 2: Spanish ➔ English',
|
'name': 'Card 2: Spanish ➔ English',
|
||||||
'qfmt': '<div style="font-family: Arial; font-size: 13px; font-weight: bold; color: #E67E22; text-align: center; letter-spacing: 1px;">TRANSLATE TO ENGLISH:</div><br>'
|
'qfmt': '<div style="font-family: Arial; font-size: 13px; font-weight: bold; color: #E67E22; text-align: center; letter-spacing: 1px;">TRANSLATE TO ENGLISH:</div><br>'
|
||||||
'<div style="font-family: Arial; font-size: 26px; text-align: center; color: #2980B9; font-weight: bold;"><b>{{SpanishText}}</b></div>'
|
'<div style="font-family: Arial; font-size: 26px; text-align: center; color: #2980B9; font-weight: bold;"><b>{{SpanishText}}</b></div><br>'
|
||||||
'<div style="display:none;">{{SpanishAudio}}</div>',
|
'<div style="text-align: center;">{{SpanishAudio}}</div>',
|
||||||
'afmt': '{{FrontSide}}<hr id="answer">'
|
'afmt': '{{FrontSide}}<hr id="answer">'
|
||||||
'<div style="font-family: Arial; font-size: 22px; text-align: center; color: #2C3E50; font-weight: 500;">{{EnglishText}}</div><br>'
|
'<div style="font-family: Arial; font-size: 22px; text-align: center; color: #2C3E50; font-weight: 500;">{{EnglishText}}</div><br>'
|
||||||
'{{#AnkiNotes}}<div style="font-family: Arial; font-size: 13px; text-align: center; color: #8E44AD; border-top: 1px dashed #E5E7E9; padding-top: 6px; margin-top: 6px;"><b>Anki Meta:</b> {{{AnkiNotes}}}</div>{{/AnkiNotes}}<br>'
|
'{{#AnkiNotes}}<div style="font-family: Arial; font-size: 13px; text-align: center; color: #8E44AD; border-top: 1px dashed #E5E7E9; padding-top: 6px; margin-top: 6px;"><b>Anki Meta:</b> {{{AnkiNotes}}}</div>{{/AnkiNotes}}<br>'
|
||||||
|
|
@ -75,12 +66,6 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
deck = genanki.Deck(deck_id, deck_name)
|
deck = genanki.Deck(deck_id, deck_name)
|
||||||
media_files_to_pack = []
|
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:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
for idx, record in enumerate(records):
|
for idx, record in enumerate(records):
|
||||||
en_raw = record["en_text"]
|
en_raw = record["en_text"]
|
||||||
|
|
@ -96,38 +81,57 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
en_tts_text = parse_text_for_edgetts(en_raw)
|
en_tts_text = parse_text_for_edgetts(en_raw)
|
||||||
es_tts_text = parse_text_for_edgetts(es_raw)
|
es_tts_text = parse_text_for_edgetts(es_raw)
|
||||||
|
|
||||||
# Map native neural voice files matching gender settings
|
# Map Kokoro voices matching gender settings
|
||||||
spanish_voice = "es-ES-AlvaroNeural" if gender_flag == "Male" else "es-ES-ElviraNeural"
|
spanish_voice = "em_alex" if gender_flag == "Male" else "ef_dora"
|
||||||
english_voice = "en-US-EmmaNeural"
|
english_voice = "af_bella"
|
||||||
|
|
||||||
raw_tags = record.get("tags") or ""
|
raw_tags = record.get("tags") or ""
|
||||||
note_tags = [t.strip().replace(" ", "_") for t in raw_tags.split(",") if t.strip()]
|
note_tags = [t.strip().replace(" ", "_") for t in raw_tags.split(",") if t.strip()]
|
||||||
|
|
||||||
# Safely isolate the raw text/HTML data string down to Anki notes field
|
|
||||||
anki_notes_html = record['anki_notes'].strip() if record.get('anki_notes') else ""
|
anki_notes_html = record['anki_notes'].strip() if record.get('anki_notes') else ""
|
||||||
|
|
||||||
# Unique Media Filenames combining execution timestamp and index
|
# Unique Media Filenames combining execution timestamp and index (.wav output from soundfile)
|
||||||
en_audio_filename = f"edge_en_{run_prefix}_{idx}.mp3"
|
en_audio_filename = f"kokoro_en_{run_prefix}_{idx}.wav"
|
||||||
es_audio_filename = f"edge_es_{run_prefix}_{idx}.mp3"
|
es_audio_filename = f"kokoro_es_{run_prefix}_{idx}.wav"
|
||||||
|
|
||||||
en_audio_path = os.path.join(tmpdir, en_audio_filename)
|
en_audio_path = os.path.join(tmpdir, en_audio_filename)
|
||||||
es_audio_path = os.path.join(tmpdir, es_audio_filename)
|
es_audio_path = os.path.join(tmpdir, es_audio_filename)
|
||||||
|
|
||||||
# English Audio Synthesis
|
# English Audio Synthesis via Kokoro
|
||||||
if en_tts_text.strip() and loop.run_until_complete(generate_edge_audio(en_tts_text, english_voice, en_audio_path, rate_string)):
|
try:
|
||||||
media_files_to_pack.append(en_audio_path)
|
if en_tts_text.strip():
|
||||||
en_audio_field = f"[sound:{en_audio_filename}]"
|
generated_path = generate_local_speech(en_tts_text, voice_name=english_voice, speed=speed_multiplier)
|
||||||
else:
|
if os.path.exists(generated_path):
|
||||||
|
# Copy temp file to designated exporter temp directory path
|
||||||
|
import shutil
|
||||||
|
shutil.copyfile(generated_path, en_audio_path)
|
||||||
|
media_files_to_pack.append(en_audio_path)
|
||||||
|
en_audio_field = f"[sound:{en_audio_filename}]"
|
||||||
|
else:
|
||||||
|
en_audio_field = ""
|
||||||
|
else:
|
||||||
|
en_audio_field = ""
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Anki Exporter] English TTS error for card {idx}: {e}")
|
||||||
en_audio_field = ""
|
en_audio_field = ""
|
||||||
|
|
||||||
# Spanish Audio Synthesis
|
# Spanish Audio Synthesis via Kokoro
|
||||||
if es_tts_text.strip() and loop.run_until_complete(generate_edge_audio(es_tts_text, spanish_voice, es_audio_path, rate_string)):
|
try:
|
||||||
media_files_to_pack.append(es_audio_path)
|
if es_tts_text.strip():
|
||||||
es_audio_field = f"[sound:{es_audio_filename}]"
|
generated_path = generate_local_speech(es_tts_text, voice_name=spanish_voice, speed=speed_multiplier)
|
||||||
else:
|
if os.path.exists(generated_path):
|
||||||
|
import shutil
|
||||||
|
shutil.copyfile(generated_path, es_audio_path)
|
||||||
|
media_files_to_pack.append(es_audio_path)
|
||||||
|
es_audio_field = f"[sound:{es_audio_filename}]"
|
||||||
|
else:
|
||||||
|
es_audio_field = ""
|
||||||
|
else:
|
||||||
|
es_audio_field = ""
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Anki Exporter] Spanish TTS error for card {idx}: {e}")
|
||||||
es_audio_field = ""
|
es_audio_field = ""
|
||||||
|
|
||||||
# Clean sequential matching fields array matching schema mapping above
|
|
||||||
note = genanki.Note(
|
note = genanki.Note(
|
||||||
model=anki_model,
|
model=anki_model,
|
||||||
fields=[sort_order_str, en_raw, es_raw, anki_notes_html, en_audio_field, es_audio_field],
|
fields=[sort_order_str, en_raw, es_raw, anki_notes_html, en_audio_field, es_audio_field],
|
||||||
|
|
@ -135,7 +139,7 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
)
|
)
|
||||||
deck.add_note(note)
|
deck.add_note(note)
|
||||||
|
|
||||||
# Build Package while media assets are guaranteed contextually active inside tmpdir
|
|
||||||
package = genanki.Package(deck)
|
package = genanki.Package(deck)
|
||||||
package.media_files = media_files_to_pack
|
package.media_files = media_files_to_pack
|
||||||
package.write_to_file(output_path)
|
package.write_to_file(output_path)
|
||||||
|
|
||||||
BIN
audio-samples/sample_af_bella.wav
Normal file
BIN
audio-samples/sample_af_bella.wav
Normal file
Binary file not shown.
BIN
audio-samples/sample_af_nicole.wav
Normal file
BIN
audio-samples/sample_af_nicole.wav
Normal file
Binary file not shown.
BIN
audio-samples/sample_af_sky.wav
Normal file
BIN
audio-samples/sample_af_sky.wav
Normal file
Binary file not shown.
BIN
audio-samples/sample_ef_dora.wav
Normal file
BIN
audio-samples/sample_ef_dora.wav
Normal file
Binary file not shown.
BIN
audio-samples/sample_em_alex.wav
Normal file
BIN
audio-samples/sample_em_alex.wav
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 13 MiB After Width: | Height: | Size: 13 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
|
|
@ -64,4 +64,11 @@ tree --gitignore
|
||||||
* **Build & Bundle Outputs:** `build/`, `dist/`, `main.app`
|
* **Build & Bundle Outputs:** `build/`, `dist/`, `main.app`
|
||||||
* **Python Caches:** `__pycache__/`, `*.pyc`
|
* **Python Caches:** `__pycache__/`, `*.pyc`
|
||||||
* **Static / Temp Media:** `*.png`, `*.jpg`, `*.pdf`, `app_icon.iconset/`
|
* **Static / Temp Media:** `*.png`, `*.jpg`, `*.pdf`, `app_icon.iconset/`
|
||||||
* **Legacy / Backups:** `database_legacy/` (containing database backups like `*.db`)
|
* **Legacy / Backups:** `database_legacy/` (containing database backups like `*.db`)
|
||||||
|
|
||||||
|
|
||||||
|
There is a problem with edge-tts
|
||||||
|
so we are going to install kokoro
|
||||||
|
|
||||||
|
39_spanish-voice-trainer % git tag -a V37.0 -m "this version replaced edge-tts with kokoro locally based tts model"
|
||||||
|
(139_spanish-voice-trainer) stephenlohning
|
||||||
|
|
|
||||||
BIN
doc/Notes.pdf
BIN
doc/Notes.pdf
Binary file not shown.
47
kokoro_utils.py
Normal file
47
kokoro_utils.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# kokoro_utils.py
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import soundfile as sf
|
||||||
|
from kokoro_onnx import Kokoro
|
||||||
|
|
||||||
|
# Resolve paths relative to project root
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
MODEL_PATH = os.path.join(BASE_DIR, "kokoro-v1.0.onnx")
|
||||||
|
VOICES_PATH = os.path.join(BASE_DIR, "voices-v1.0.bin")
|
||||||
|
|
||||||
|
_kokoro_instance = None
|
||||||
|
|
||||||
|
def get_kokoro_engine():
|
||||||
|
global _kokoro_instance
|
||||||
|
if _kokoro_instance is None:
|
||||||
|
if os.path.exists(MODEL_PATH) and os.path.exists(VOICES_PATH):
|
||||||
|
_kokoro_instance = Kokoro(MODEL_PATH, VOICES_PATH)
|
||||||
|
return _kokoro_instance
|
||||||
|
|
||||||
|
def generate_local_speech(text: str, voice_name: str = "af_bella", speed: float = 1.0) -> str:
|
||||||
|
"""
|
||||||
|
Synthesizes speech locally using Kokoro and saves to a temp file,
|
||||||
|
returning the path to the generated audio file with correct language routing.
|
||||||
|
"""
|
||||||
|
engine = get_kokoro_engine()
|
||||||
|
if not engine:
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"Kokoro model files not found at root directory.\nExpected:\n- {MODEL_PATH}\n- {VOICES_PATH}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Route language code explicitly based on voice prefix
|
||||||
|
if voice_name.startswith("es") or voice_name.startswith("ef") or voice_name.startswith("em"):
|
||||||
|
lang_code = "es"
|
||||||
|
else:
|
||||||
|
lang_code = "en-us"
|
||||||
|
|
||||||
|
samples, sample_rate = engine.create(
|
||||||
|
text,
|
||||||
|
voice=voice_name,
|
||||||
|
speed=speed,
|
||||||
|
lang=lang_code
|
||||||
|
)
|
||||||
|
|
||||||
|
temp_file = os.path.join(tempfile.gettempdir(), "kokoro_review_audio.wav")
|
||||||
|
sf.write(temp_file, samples, sample_rate)
|
||||||
|
return temp_file
|
||||||
129
main.py
129
main.py
|
|
@ -1,7 +1,12 @@
|
||||||
# main.py
|
# main.py
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from PyQt6.QtWidgets import QMainWindow, QTabWidget, QApplication, QMessageBox, QVBoxLayout, QWidget
|
import shutil
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QMainWindow, QTabWidget, QApplication, QMessageBox,
|
||||||
|
QVBoxLayout, QWidget, QFileDialog
|
||||||
|
)
|
||||||
|
from PyQt6.QtGui import QAction, QKeySequence
|
||||||
from PyQt6.QtCore import pyqtSlot
|
from PyQt6.QtCore import pyqtSlot
|
||||||
|
|
||||||
# Import our unified data layer
|
# Import our unified data layer
|
||||||
|
|
@ -19,7 +24,7 @@ import video_generator
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle("Castilian Spanish Voice Trainer")
|
self.setWindowTitle("Spanish Voice Trainer")
|
||||||
self.setMinimumSize(900, 650)
|
self.setMinimumSize(900, 650)
|
||||||
|
|
||||||
# 1. Ensure database structure exists on startup
|
# 1. Ensure database structure exists on startup
|
||||||
|
|
@ -47,9 +52,121 @@ class MainWindow(QMainWindow):
|
||||||
self.tab_widget.addTab(self.review_tab, "Flashcard Review")
|
self.tab_widget.addTab(self.review_tab, "Flashcard Review")
|
||||||
self.tab_widget.addTab(self.settings_tab, "Configuration Settings")
|
self.tab_widget.addTab(self.settings_tab, "Configuration Settings")
|
||||||
|
|
||||||
# 6. Wire up the cross-module communication channels (Signals)
|
# 6. Build application Menu Bar
|
||||||
|
self.create_menu_bar()
|
||||||
|
|
||||||
|
# 7. Wire up the cross-module communication channels (Signals)
|
||||||
self.wire_application_signals()
|
self.wire_application_signals()
|
||||||
|
|
||||||
|
def create_menu_bar(self):
|
||||||
|
"""Constructs native macOS Menu Bar integrating directly with the primary system process menu."""
|
||||||
|
menu_bar = self.menuBar()
|
||||||
|
|
||||||
|
# --- 1. APP MENU (Spanish Voice Trainer) ---
|
||||||
|
# An empty string title merges actions directly into the native primary macOS Application Menu
|
||||||
|
app_menu = menu_bar.addMenu("")
|
||||||
|
|
||||||
|
about_action = QAction("About Spanish Voice Trainer", self)
|
||||||
|
about_action.setMenuRole(QAction.MenuRole.AboutRole)
|
||||||
|
about_action.triggered.connect(self.show_about_dialog)
|
||||||
|
app_menu.addAction(about_action)
|
||||||
|
|
||||||
|
update_action = QAction("Check for Updates...", self)
|
||||||
|
update_action.setMenuRole(QAction.MenuRole.ApplicationSpecificRole)
|
||||||
|
update_action.triggered.connect(self.check_for_updates)
|
||||||
|
app_menu.addAction(update_action)
|
||||||
|
|
||||||
|
quit_action = QAction("Quit Spanish Voice Trainer", self)
|
||||||
|
quit_action.setMenuRole(QAction.MenuRole.QuitRole)
|
||||||
|
quit_action.setShortcut(QKeySequence.StandardKey.Quit)
|
||||||
|
quit_action.triggered.connect(QApplication.instance().quit)
|
||||||
|
app_menu.addAction(quit_action)
|
||||||
|
|
||||||
|
# --- 2. TOOLS MENU ---
|
||||||
|
tools_menu = menu_bar.addMenu("Tools")
|
||||||
|
|
||||||
|
backup_action = QAction("Backup Database...", self)
|
||||||
|
backup_action.triggered.connect(self.handle_backup_database)
|
||||||
|
tools_menu.addAction(backup_action)
|
||||||
|
|
||||||
|
# --- 3. HELP MENU ---
|
||||||
|
help_menu = menu_bar.addMenu("Help")
|
||||||
|
|
||||||
|
help_instructions_action = QAction("User Guide & Instructions", self)
|
||||||
|
help_instructions_action.setMenuRole(QAction.MenuRole.ApplicationSpecificRole)
|
||||||
|
help_instructions_action.setShortcut(QKeySequence.StandardKey.HelpContents)
|
||||||
|
help_instructions_action.triggered.connect(self.show_help_dialog)
|
||||||
|
help_menu.addAction(help_instructions_action)
|
||||||
|
|
||||||
|
# --- Menu Action Handlers ---
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def show_about_dialog(self):
|
||||||
|
"""Displays application details and copyright information."""
|
||||||
|
QMessageBox.about(
|
||||||
|
self,
|
||||||
|
"About Spanish Voice Trainer",
|
||||||
|
"<h3>Spanish Voice Trainer</h3>"
|
||||||
|
"<p><b>Version:</b> 1.0.0</p>"
|
||||||
|
"<p>A local-first application for Castilian Spanish vocabulary practice, "
|
||||||
|
"neural TTS voice previewing, and Anki deck generation.</p>"
|
||||||
|
)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def check_for_updates(self):
|
||||||
|
"""Placeholder action for update checks."""
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Check for Updates",
|
||||||
|
"Check for Updates is not implemented yet in this version."
|
||||||
|
)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def handle_backup_database(self):
|
||||||
|
"""Copies the primary SQLite database file to a user-selected location."""
|
||||||
|
db_path = database.DB_PATH if hasattr(database, 'DB_PATH') else "spanish_app.db"
|
||||||
|
|
||||||
|
if not os.path.exists(db_path):
|
||||||
|
QMessageBox.warning(self, "Backup Error", f"Database file '{db_path}' could not be located.")
|
||||||
|
return
|
||||||
|
|
||||||
|
save_path, _ = QFileDialog.getSaveFileName(
|
||||||
|
self,
|
||||||
|
"Save Database Backup",
|
||||||
|
os.path.expanduser("~/Desktop/spanish_app_backup.db"),
|
||||||
|
"SQLite Database (*.db *.sqlite);;All Files (*)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if save_path:
|
||||||
|
try:
|
||||||
|
shutil.copy2(db_path, save_path)
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Backup Complete",
|
||||||
|
f"Successfully created a backup copy of your database at:\n{save_path}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Backup Failed", f"Could not create database backup:\n{str(e)}")
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def show_help_dialog(self):
|
||||||
|
"""Displays user guide and usage instructions."""
|
||||||
|
instructions_text = (
|
||||||
|
"<h3>Spanish Voice Trainer - Quick Start Guide</h3>"
|
||||||
|
"<ul>"
|
||||||
|
"<li><b>Database Sandbox:</b> Add, edit, sort, or tag your English/Spanish phrase translations. "
|
||||||
|
"Sort order dictates sequence in flashcards and exports.</li>"
|
||||||
|
"<li><b>Flashcard Review:</b> Practice interactive flashcards. Click <i>Flip Card</i> to see and automatically hear "
|
||||||
|
"the Spanish phrase. Click <i>Next Card</i> to advance and automatically hear the English prompt.</li>"
|
||||||
|
"<li><b>Filter Cards:</b> Use the top filter fields (Context or Tags) to isolate specific review topics or decks.</li>"
|
||||||
|
"<li><b>Generate Deck:</b> Click <i>Generate Deck</i> to compile your active phrases into a `.apkg` file ready for Anki import.</li>"
|
||||||
|
"<li><b>Configuration Settings:</b> Adjust default export folders, speech speed rate, and Anki deck hierarchy settings.</li>"
|
||||||
|
"</ul>"
|
||||||
|
)
|
||||||
|
QMessageBox.information(self, "Instructions & Help", instructions_text)
|
||||||
|
|
||||||
|
# --- Standard Application Signal & Cache Logic ---
|
||||||
|
|
||||||
def refresh_settings_cache(self):
|
def refresh_settings_cache(self):
|
||||||
"""Loads all database settings into an easy-to-read dictionary."""
|
"""Loads all database settings into an easy-to-read dictionary."""
|
||||||
self.app_settings = database.load_all_settings()
|
self.app_settings = database.load_all_settings()
|
||||||
|
|
@ -97,7 +214,7 @@ class MainWindow(QMainWindow):
|
||||||
records = database.get_all_translations_explicit()
|
records = database.get_all_translations_explicit()
|
||||||
|
|
||||||
if not records:
|
if not records:
|
||||||
QMessageBox.warning(self, "Generation Failed", "No phrases available to construct a training video playlist loops.")
|
QMessageBox.warning(self, "Generation Failed", "No phrases available to construct training video playlist loops.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# UI updates can block execution loops, so we notify before processing
|
# UI updates can block execution loops, so we notify before processing
|
||||||
|
|
@ -121,6 +238,10 @@ class MainWindow(QMainWindow):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# Explicitly set application identity for macOS menu bar and dock integration
|
||||||
|
app.setApplicationName("Spanish Voice Trainer")
|
||||||
|
app.setApplicationDisplayName("Spanish Voice Trainer")
|
||||||
|
|
||||||
# Modern clean styling choices for desktop applications
|
# Modern clean styling choices for desktop applications
|
||||||
app.setStyle('Fusion')
|
app.setStyle('Fusion')
|
||||||
|
|
||||||
|
|
|
||||||
138
requirements.txt
138
requirements.txt
|
|
@ -1,138 +0,0 @@
|
||||||
accelerate==1.14.0
|
|
||||||
aiohappyeyeballs==2.6.2
|
|
||||||
aiohttp==3.14.0
|
|
||||||
aiosignal==1.4.0
|
|
||||||
annotated-doc==0.0.4
|
|
||||||
annotated-types==0.7.0
|
|
||||||
antlr4-python3-runtime==4.9.3
|
|
||||||
anyio==4.13.0
|
|
||||||
attrs==26.1.0
|
|
||||||
audioop-lts==0.2.2
|
|
||||||
audioread==3.1.0
|
|
||||||
beautifulsoup4==4.15.0
|
|
||||||
cached-property==2.0.1
|
|
||||||
certifi==2026.5.20
|
|
||||||
cffi==2.0.0
|
|
||||||
charset-normalizer==3.4.7
|
|
||||||
chevron==0.14.0
|
|
||||||
click==8.4.1
|
|
||||||
colorlog==6.10.1
|
|
||||||
decorator==5.3.1
|
|
||||||
defusedxml==0.7.1
|
|
||||||
dill==0.4.1
|
|
||||||
docling==2.102.1
|
|
||||||
docling-core==2.82.0
|
|
||||||
docling-ibm-models==3.13.3
|
|
||||||
docling-parse==6.2.0
|
|
||||||
docling-slim==2.102.1
|
|
||||||
edge-tts==7.2.8
|
|
||||||
et-xmlfile==2.0.0
|
|
||||||
faker==40.23.0
|
|
||||||
fastdtw==0.3.4
|
|
||||||
filelock==3.29.3
|
|
||||||
filetype==1.2.0
|
|
||||||
frozendict==2.4.7
|
|
||||||
frozenlist==1.8.0
|
|
||||||
fsspec==2026.4.0
|
|
||||||
genanki==0.13.1
|
|
||||||
h11==0.16.0
|
|
||||||
hf-xet==1.5.1
|
|
||||||
httpcore==1.0.9
|
|
||||||
httpx==0.28.1
|
|
||||||
huggingface-hub==1.19.0
|
|
||||||
idna==3.18
|
|
||||||
jinja2==3.1.6
|
|
||||||
joblib==1.5.3
|
|
||||||
jsonlines==4.0.0
|
|
||||||
jsonref==1.1.0
|
|
||||||
jsonschema==4.26.0
|
|
||||||
jsonschema-specifications==2025.9.1
|
|
||||||
latex2mathml==3.81.0
|
|
||||||
lazy-loader==0.5
|
|
||||||
librosa==0.11.0
|
|
||||||
llvmlite==0.47.0
|
|
||||||
lxml==6.1.1
|
|
||||||
mail-parser==4.4.0
|
|
||||||
markdown-it-py==4.2.0
|
|
||||||
marko==2.2.3
|
|
||||||
markupsafe==3.0.3
|
|
||||||
mdurl==0.1.2
|
|
||||||
mpire==2.10.2
|
|
||||||
mpmath==1.3.0
|
|
||||||
msgpack==1.1.2
|
|
||||||
multidict==6.7.1
|
|
||||||
multiprocess==0.70.19
|
|
||||||
narwhals==2.22.1
|
|
||||||
networkx==3.6.1
|
|
||||||
numba==0.65.1
|
|
||||||
numpy==2.4.6
|
|
||||||
omegaconf==2.3.1
|
|
||||||
opencv-python==4.13.0.92
|
|
||||||
openpyxl==3.1.5
|
|
||||||
packaging==26.2
|
|
||||||
pandas==3.0.3
|
|
||||||
pillow==12.2.0
|
|
||||||
platformdirs==4.10.0
|
|
||||||
pluggy==1.6.0
|
|
||||||
polyfactory==3.3.0
|
|
||||||
pooch==1.9.0
|
|
||||||
propcache==0.5.2
|
|
||||||
psutil==7.2.2
|
|
||||||
pyclipper==1.4.0
|
|
||||||
pycparser==3.0
|
|
||||||
pydantic==2.13.4
|
|
||||||
pydantic-core==2.46.4
|
|
||||||
pydantic-settings==2.14.1
|
|
||||||
pygments==2.20.0
|
|
||||||
pylatexenc==2.10
|
|
||||||
pypdfium2==5.9.0
|
|
||||||
pyqt6==6.11.0
|
|
||||||
pyqt6-qt6==6.11.1
|
|
||||||
pyqt6-sip==13.11.1
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
python-docx==1.2.0
|
|
||||||
python-dotenv==1.2.2
|
|
||||||
python-pptx==1.0.2
|
|
||||||
pyyaml==6.0.3
|
|
||||||
rapidocr==3.8.3
|
|
||||||
referencing==0.37.0
|
|
||||||
regex==2026.5.9
|
|
||||||
requests==2.34.2
|
|
||||||
rich==15.0.0
|
|
||||||
rpds-py==2026.5.1
|
|
||||||
rtree==1.4.1
|
|
||||||
safetensors==0.8.0
|
|
||||||
scikit-learn==1.9.0
|
|
||||||
scipy==1.17.1
|
|
||||||
semchunk==3.2.5
|
|
||||||
setuptools==81.0.0
|
|
||||||
shapely==2.1.2
|
|
||||||
shellingham==1.5.4
|
|
||||||
six==1.17.0
|
|
||||||
sounddevice==0.5.5
|
|
||||||
soundfile==0.14.0
|
|
||||||
soupsieve==2.8.4
|
|
||||||
soxr==1.1.0
|
|
||||||
standard-aifc==3.13.0
|
|
||||||
standard-chunk==3.13.0
|
|
||||||
standard-sunau==3.13.0
|
|
||||||
sympy==1.14.0
|
|
||||||
tabulate==0.10.0
|
|
||||||
threadpoolctl==3.6.0
|
|
||||||
tokenizers==0.22.2
|
|
||||||
torch==2.12.0
|
|
||||||
torchvision==0.27.0
|
|
||||||
tqdm==4.68.2
|
|
||||||
transformers==5.8.1
|
|
||||||
tree-sitter==0.25.2
|
|
||||||
tree-sitter-c==0.24.2
|
|
||||||
tree-sitter-javascript==0.25.0
|
|
||||||
tree-sitter-python==0.25.0
|
|
||||||
tree-sitter-typescript==0.23.2
|
|
||||||
typer==0.21.2
|
|
||||||
typing-extensions==4.15.0
|
|
||||||
typing-inspection==0.4.2
|
|
||||||
urllib3==2.7.0
|
|
||||||
websockets==16.0
|
|
||||||
xlsxwriter==3.2.9
|
|
||||||
yarl==1.24.2
|
|
||||||
|
|
@ -4,7 +4,6 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import asyncio
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||||
|
|
@ -12,10 +11,10 @@ from PyQt6.QtWidgets import (
|
||||||
QHeaderView, QFormLayout, QMessageBox, QFrame, QStackedWidget
|
QHeaderView, QFormLayout, QMessageBox, QFrame, QStackedWidget
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, pyqtSlot
|
from PyQt6.QtCore import Qt, pyqtSlot
|
||||||
import edge_tts
|
|
||||||
import database
|
import database
|
||||||
import anki_exporter
|
import anki_exporter
|
||||||
from tts_utils import parse_text_for_edgetts, get_configured_tts_rate
|
from tts_utils import parse_text_for_edgetts
|
||||||
|
from kokoro_utils import generate_local_speech
|
||||||
|
|
||||||
class ReviewTab(QWidget):
|
class ReviewTab(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
|
@ -277,99 +276,82 @@ class ReviewTab(QWidget):
|
||||||
# Automatically play English neural audio on advancing to next card
|
# Automatically play English neural audio on advancing to next card
|
||||||
self.play_card_audio()
|
self.play_card_audio()
|
||||||
|
|
||||||
def _async_edge_speech_worker(self, text, voice, rate_modifier):
|
def _local_kokoro_speech_worker(self, text, voice_name, speed_multiplier):
|
||||||
"""Background thread worker to render neural speech with terminal debug logging."""
|
"""Background thread worker to render local neural speech via Kokoro without network calls."""
|
||||||
async def stream_audio():
|
|
||||||
temp_file = os.path.join(tempfile.gettempdir(), "review_card_audio.mp3")
|
|
||||||
|
|
||||||
# Clean up old file if present
|
|
||||||
if os.path.exists(temp_file):
|
|
||||||
try:
|
|
||||||
os.remove(temp_file)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
print(f"[TTS Debug] Generating TTS -> Voice: {voice} | Rate: {rate_modifier} | Text: '{text}'")
|
|
||||||
communicate = edge_tts.Communicate(text=text, voice=voice, rate=rate_modifier)
|
|
||||||
await communicate.save(temp_file)
|
|
||||||
|
|
||||||
if os.path.exists(temp_file) and os.path.getsize(temp_file) > 0:
|
|
||||||
print(f"[TTS Debug] Audio ready ({os.path.getsize(temp_file)} bytes). Playing via afplay...")
|
|
||||||
result = subprocess.run(["afplay", temp_file], capture_output=True, text=True)
|
|
||||||
if result.returncode != 0:
|
|
||||||
print(f"[TTS Debug] afplay failed: {result.stderr}")
|
|
||||||
else:
|
|
||||||
print("[TTS Debug] Playback finished successfully.")
|
|
||||||
else:
|
|
||||||
print("[TTS Debug] Error: Audio file was not created or is 0 bytes.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[TTS Debug] Exception during speech synthesis: {e}")
|
|
||||||
|
|
||||||
# Explicitly set up and run a clean event loop for this thread
|
|
||||||
try:
|
try:
|
||||||
loop = asyncio.new_event_loop()
|
print(f"[Kokoro Debug] Generating local TTS -> Voice: {voice_name} | Speed: {speed_multiplier} | Text: '{text}'")
|
||||||
asyncio.set_event_loop(loop)
|
temp_file = generate_local_speech(text, voice_name=voice_name, speed=speed_multiplier)
|
||||||
loop.run_until_complete(stream_audio())
|
|
||||||
loop.close()
|
if os.path.exists(temp_file) and os.path.getsize(temp_file) > 0:
|
||||||
|
print(f"[Kokoro Debug] Audio ready. Playing via afplay...")
|
||||||
|
result = subprocess.run(["afplay", temp_file], capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"[Kokoro Debug] afplay failed: {result.stderr}")
|
||||||
|
else:
|
||||||
|
print("[Kokoro Debug] Playback finished successfully.")
|
||||||
|
else:
|
||||||
|
print("[Kokoro Debug] Error: Audio file was not created or is 0 bytes.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[TTS Debug] Event loop error: {e}")
|
print(f"[Kokoro Debug] Exception during local speech synthesis: {e}")
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def play_card_audio(self):
|
def play_card_audio(self):
|
||||||
"""Auditions neural edge-tts voice based on active flashcard side."""
|
"""Triggers local Kokoro neural voice generation based on active flashcard side."""
|
||||||
if not (0 <= self.current_index < len(self.filtered_review_pool)):
|
if not (0 <= self.current_index < len(self.filtered_review_pool)):
|
||||||
return
|
return
|
||||||
|
|
||||||
record = self.filtered_review_pool[self.current_index]
|
record = self.filtered_review_pool[self.current_index]
|
||||||
settings = database.load_all_settings() or {}
|
settings = database.load_all_settings() or {}
|
||||||
rate_string = get_configured_tts_rate(settings)
|
|
||||||
|
# Load playback speed setting from database cache (default to 1.0)
|
||||||
|
try:
|
||||||
|
speed_multiplier = float(settings.get("tts_playback_speed", 1.0))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
speed_multiplier = 1.0
|
||||||
|
|
||||||
if not self.is_flipped:
|
if not self.is_flipped:
|
||||||
raw_text = record.get("en_text", "")
|
raw_text = record.get("en_text", "")
|
||||||
spoken_text = parse_text_for_edgetts(raw_text)
|
spoken_text = parse_text_for_edgetts(raw_text)
|
||||||
voice = "en-US-EmmaNeural"
|
#voice = "af_sarah" # English voice reference in Kokoro
|
||||||
|
#voice = "af_heart"
|
||||||
|
voice = "af_bella" # Default English voice for testing
|
||||||
else:
|
else:
|
||||||
raw_text = record.get("es_text", "")
|
raw_text = record.get("es_text", "")
|
||||||
spoken_text = parse_text_for_edgetts(raw_text)
|
spoken_text = parse_text_for_edgetts(raw_text)
|
||||||
is_male = (record.get("gender") == "Male")
|
is_male = (record.get("gender") == "Male")
|
||||||
voice = "es-ES-AlvaroNeural" if is_male else "es-ES-ElviraNeural"
|
# Kokoro Spanish voices: 'ef_dora' (female) / 'em_alex' (male)
|
||||||
|
voice = "em_alex" if is_male else "ef_dora"
|
||||||
|
|
||||||
# Respect <meta sound-off> flags or empty entries
|
|
||||||
if not spoken_text.strip():
|
if not spoken_text.strip():
|
||||||
print("[TTS Debug] Skipped: Parsed text is empty or muted via sound-off tag.")
|
print("[Kokoro Debug] Skipped: Parsed text is empty.")
|
||||||
return
|
return
|
||||||
|
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=self._async_edge_speech_worker,
|
target=self._local_kokoro_speech_worker,
|
||||||
args=(spoken_text, voice, rate_string),
|
args=(spoken_text, voice, speed_multiplier),
|
||||||
daemon=True
|
daemon=True
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def generate_deck_action(self):
|
def generate_deck_action(self):
|
||||||
"""Generates a specialized .apkg Anki deck matching active filter parameters,
|
"""Generates a specialized .apkg Anki deck matching active filter parameters,
|
||||||
prepending yyyy-mm-dd-hhmm timestamp and matching Sub-Deck-Namespace-Hierarchy without brackets/parentheses."""
|
prepressing yyyy-mm-dd-hhmm timestamp and matching Sub-Deck-Namespace-Hierarchy without brackets/parentheses."""
|
||||||
if not self.filtered_review_pool:
|
if not self.filtered_review_pool:
|
||||||
QMessageBox.warning(self, "Export Aborted", "The current matching review deck queue is empty. Cannot compile an empty deck.")
|
QMessageBox.warning(self, "Export Aborted", "The current matching review deck queue is empty. Cannot compile an empty deck.")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Load active settings dictionary directly from database configurations
|
|
||||||
settings = database.load_all_settings() or {}
|
settings = database.load_all_settings() or {}
|
||||||
|
|
||||||
# Extract configurations targeting exact database schema names found in settings
|
|
||||||
target_dir = settings.get("anki_export_directory")
|
target_dir = settings.get("anki_export_directory")
|
||||||
root_deck_name = settings.get("anki_root_deck_name")
|
root_deck_name = settings.get("anki_root_deck_name")
|
||||||
sub_deck_hierarchy = settings.get("anki_sub_deck_name")
|
sub_deck_hierarchy = settings.get("anki_sub_deck_name")
|
||||||
|
|
||||||
# Fallback handling to verify directories exist safely
|
|
||||||
if not target_dir or not os.path.isdir(str(target_dir)):
|
if not target_dir or not os.path.isdir(str(target_dir)):
|
||||||
target_dir = os.path.expanduser("~/Desktop")
|
target_dir = os.path.expanduser("~/Desktop")
|
||||||
else:
|
else:
|
||||||
target_dir = str(target_dir)
|
target_dir = str(target_dir)
|
||||||
|
|
||||||
# --- Compile Full Namespace Tree Path ---
|
|
||||||
deck_tree_parts = []
|
deck_tree_parts = []
|
||||||
|
|
||||||
if root_deck_name and str(root_deck_name).strip():
|
if root_deck_name and str(root_deck_name).strip():
|
||||||
|
|
@ -381,23 +363,17 @@ class ReviewTab(QWidget):
|
||||||
if not deck_tree_parts:
|
if not deck_tree_parts:
|
||||||
deck_tree_parts.append("DefaultDeck")
|
deck_tree_parts.append("DefaultDeck")
|
||||||
|
|
||||||
# Join parts using Anki double-colon syntax (::) for internal Anki hierarchy
|
|
||||||
full_deck_namespace = "::".join(deck_tree_parts)
|
full_deck_namespace = "::".join(deck_tree_parts)
|
||||||
|
|
||||||
# --- Format Timestamp and Clean Filename ---
|
|
||||||
# Format: YYYY-MM-DD-HHMM
|
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d-%H%M")
|
timestamp = datetime.now().strftime("%Y-%m-%d-%H%M")
|
||||||
|
|
||||||
# Explicitly strip out parentheses/brackets before regex normalization
|
|
||||||
clean_namespace = full_deck_namespace.replace('(', '').replace(')', '').replace('[', '').replace(']', '')
|
clean_namespace = full_deck_namespace.replace('(', '').replace(')', '').replace('[', '').replace(']', '')
|
||||||
clean_namespace = re.sub(r'[^a-zA-Z0-9]', '-', clean_namespace)
|
clean_namespace = re.sub(r'[^a-zA-Z0-9]', '-', clean_namespace)
|
||||||
clean_namespace = re.sub(r'-+', '-', clean_namespace).strip('-')
|
clean_namespace = re.sub(r'-+', '-', clean_namespace).strip('-')
|
||||||
|
|
||||||
# Complete output filename pattern: yyyy-mm-dd-hhmm-Sub-Deck-Namespace-Hierarchy.apkg
|
|
||||||
filename = f"{timestamp}-{clean_namespace}.apkg"
|
filename = f"{timestamp}-{clean_namespace}.apkg"
|
||||||
file_path = os.path.join(target_dir, filename)
|
file_path = os.path.join(target_dir, filename)
|
||||||
|
|
||||||
# Execute actual compilation algorithm pipeline mapping filtered records cleanly
|
|
||||||
anki_exporter.compile_anki_package(self.filtered_review_pool, file_path, full_deck_namespace)
|
anki_exporter.compile_anki_package(self.filtered_review_pool, file_path, full_deck_namespace)
|
||||||
|
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import asyncio
|
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||||
QLineEdit, QPushButton, QTableWidget, QTableWidgetItem,
|
QLineEdit, QPushButton, QTableWidget, QTableWidgetItem,
|
||||||
|
|
@ -11,9 +10,9 @@ from PyQt6.QtWidgets import (
|
||||||
QRadioButton, QButtonGroup, QSpinBox
|
QRadioButton, QButtonGroup, QSpinBox
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt
|
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt
|
||||||
import edge_tts
|
|
||||||
import database
|
import database
|
||||||
from tts_utils import parse_text_for_edgetts, get_configured_tts_rate
|
from tts_utils import parse_text_for_edgetts
|
||||||
|
from kokoro_utils import generate_local_speech
|
||||||
|
|
||||||
|
|
||||||
class TextEditorDialog(QDialog):
|
class TextEditorDialog(QDialog):
|
||||||
|
|
@ -120,7 +119,7 @@ class SandboxTab(QWidget):
|
||||||
self.txt_tags.setStyleSheet("padding: 6px; font-size: 14px;")
|
self.txt_tags.setStyleSheet("padding: 6px; font-size: 14px;")
|
||||||
self.txt_tags.textChanged.connect(self.apply_live_grid_filter)
|
self.txt_tags.textChanged.connect(self.apply_live_grid_filter)
|
||||||
|
|
||||||
# --- Sort Order Field (Added after Tags) ---
|
# --- Sort Order Field ---
|
||||||
self.spn_sort_order = QSpinBox()
|
self.spn_sort_order = QSpinBox()
|
||||||
self.spn_sort_order.setRange(0, 999999)
|
self.spn_sort_order.setRange(0, 999999)
|
||||||
self.spn_sort_order.setValue(0)
|
self.spn_sort_order.setValue(0)
|
||||||
|
|
@ -367,19 +366,20 @@ class SandboxTab(QWidget):
|
||||||
self.table.setItem(visible_row_idx, 6, QTableWidgetItem(r.get("gender", "Female")))
|
self.table.setItem(visible_row_idx, 6, QTableWidgetItem(r.get("gender", "Female")))
|
||||||
visible_row_idx += 1
|
visible_row_idx += 1
|
||||||
|
|
||||||
def _async_edge_speech_worker(self, text, voice, rate_modifier):
|
def _local_kokoro_speech_worker(self, text, voice_name, speed_multiplier):
|
||||||
"""Background thread worker to download neural audio and play it without freezing the UI."""
|
"""Background thread worker to render local neural speech via Kokoro without network calls."""
|
||||||
async def stream_audio():
|
try:
|
||||||
temp_file = os.path.join(tempfile.gettempdir(), "sandbox_audition.mp3")
|
print(f"[Kokoro Debug] Sandbox Audition -> Voice: {voice_name} | Speed: {speed_multiplier} | Text: '{text}'")
|
||||||
try:
|
temp_file = generate_local_speech(text, voice_name=voice_name, speed=speed_multiplier)
|
||||||
communicate = edge_tts.Communicate(text=text, voice=voice, rate=rate_modifier)
|
|
||||||
await communicate.save(temp_file)
|
if os.path.exists(temp_file) and os.path.getsize(temp_file) > 0:
|
||||||
if os.path.exists(temp_file):
|
result = subprocess.run(["afplay", temp_file], capture_output=True, text=True)
|
||||||
subprocess.run(["afplay", temp_file])
|
if result.returncode != 0:
|
||||||
except Exception as e:
|
print(f"[Kokoro Debug] afplay failed: {result.stderr}")
|
||||||
print(f"Sandbox Audition Error: {e}")
|
else:
|
||||||
|
print("[Kokoro Debug] Error: Audio file was not created or is 0 bytes.")
|
||||||
asyncio.run(stream_audio())
|
except Exception as e:
|
||||||
|
print(f"Sandbox Audition Error: {e}")
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def audition_english(self):
|
def audition_english(self):
|
||||||
|
|
@ -390,11 +390,14 @@ class SandboxTab(QWidget):
|
||||||
return
|
return
|
||||||
|
|
||||||
settings = database.load_all_settings() or {}
|
settings = database.load_all_settings() or {}
|
||||||
rate_string = get_configured_tts_rate(settings)
|
try:
|
||||||
|
speed_multiplier = float(settings.get("tts_playback_speed", 1.0))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
speed_multiplier = 1.0
|
||||||
|
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=self._async_edge_speech_worker,
|
target=self._local_kokoro_speech_worker,
|
||||||
args=(spoken_text, "en-US-EmmaNeural", rate_string),
|
args=(spoken_text, "af_bella", speed_multiplier),
|
||||||
daemon=True
|
daemon=True
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
|
|
@ -406,12 +409,15 @@ class SandboxTab(QWidget):
|
||||||
if not spoken_text.strip():
|
if not spoken_text.strip():
|
||||||
return
|
return
|
||||||
|
|
||||||
voice = "es-ES-AlvaroNeural" if self.rb_male.isChecked() else "es-ES-ElviraNeural"
|
voice = "em_alex" if self.rb_male.isChecked() else "ef_dora"
|
||||||
settings = database.load_all_settings() or {}
|
settings = database.load_all_settings() or {}
|
||||||
rate_string = get_configured_tts_rate(settings)
|
try:
|
||||||
|
speed_multiplier = float(settings.get("tts_playback_speed", 1.0))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
speed_multiplier = 1.0
|
||||||
|
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=self._async_edge_speech_worker,
|
target=self._local_kokoro_speech_worker,
|
||||||
args=(spoken_text, voice, rate_string),
|
args=(spoken_text, voice, speed_multiplier),
|
||||||
daemon=True
|
daemon=True
|
||||||
).start()
|
).start()
|
||||||
29
test-scripts/test_voices.py
Normal file
29
test-scripts/test_voices.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# test_voices.py
|
||||||
|
import soundfile as sf
|
||||||
|
from kokoro_onnx import Kokoro
|
||||||
|
|
||||||
|
kokoro = Kokoro("kokoro-v1.0.onnx", "voices-v1.0.bin")
|
||||||
|
|
||||||
|
# Test phrases
|
||||||
|
#english_text = "Welcome to the Spanish voice trainer. Let's practice key phrases together."
|
||||||
|
#spanish_text = "Bienvenido al entrenador de voz en español. Practiquemos frases clave juntos."
|
||||||
|
english_text = "surname"
|
||||||
|
spanish_text = "apellido"
|
||||||
|
|
||||||
|
|
||||||
|
# Voices to test (English and Spanish presets)
|
||||||
|
voices_to_test = [
|
||||||
|
("af_bella", "en-us", english_text),
|
||||||
|
("af_nicole", "en-us", english_text),
|
||||||
|
("af_sky", "en-us", english_text),
|
||||||
|
("ef_dora", "es", spanish_text),
|
||||||
|
("em_alex", "es", spanish_text),
|
||||||
|
]
|
||||||
|
|
||||||
|
for voice, lang, text in voices_to_test:
|
||||||
|
print(f"Generating sample for {voice} ({lang})...")
|
||||||
|
samples, sample_rate = kokoro.create(text, voice=voice, speed=1.0, lang=lang)
|
||||||
|
filename = f"sample_{voice}.wav"
|
||||||
|
sf.write(filename, samples, sample_rate)
|
||||||
|
|
||||||
|
print("Done! You can play sample_af_bella.wav, sample_ef_dora.wav, etc. using afplay.")
|
||||||
981
tree.txt
981
tree.txt
|
|
@ -1,981 +0,0 @@
|
||||||
.
|
|
||||||
├── LICENSE
|
|
||||||
├── README.md
|
|
||||||
├── README.pdf
|
|
||||||
├── Santiago_cathedral_2021_Sunset.jpg
|
|
||||||
├── __pycache__
|
|
||||||
│ ├── anki_exporter.cpython-313.pyc
|
|
||||||
│ ├── database.cpython-313.pyc
|
|
||||||
│ ├── tts_utils.cpython-313.pyc
|
|
||||||
│ └── video_generator.cpython-313.pyc
|
|
||||||
├── anki_exporter.py
|
|
||||||
├── app.icns
|
|
||||||
├── app_icon.iconset
|
|
||||||
│ ├── icon_128x128.png
|
|
||||||
│ ├── icon_128x128@2x.png
|
|
||||||
│ ├── icon_16x16.png
|
|
||||||
│ ├── icon_16x16@2x.png
|
|
||||||
│ ├── icon_256x256.png
|
|
||||||
│ ├── icon_256x256@2x.png
|
|
||||||
│ ├── icon_32x32.png
|
|
||||||
│ ├── icon_32x32@2x.png
|
|
||||||
│ ├── icon_512x512.png
|
|
||||||
│ └── icon_512x512@2x.png
|
|
||||||
├── aula_int_plus_1_glos_en_alfa.pdf
|
|
||||||
├── base_icon.png
|
|
||||||
├── build
|
|
||||||
│ └── main
|
|
||||||
│ ├── Analysis-00.toc
|
|
||||||
│ ├── BUNDLE-00.toc
|
|
||||||
│ ├── COLLECT-00.toc
|
|
||||||
│ ├── EXE-00.toc
|
|
||||||
│ ├── PKG-00.toc
|
|
||||||
│ ├── PYZ-00.pyz
|
|
||||||
│ ├── PYZ-00.toc
|
|
||||||
│ ├── base_library.zip
|
|
||||||
│ ├── localpycs
|
|
||||||
│ │ ├── pyimod01_archive.pyc
|
|
||||||
│ │ ├── pyimod02_importers.pyc
|
|
||||||
│ │ ├── pyimod03_ctypes.pyc
|
|
||||||
│ │ └── struct.pyc
|
|
||||||
│ ├── main
|
|
||||||
│ ├── main.pkg
|
|
||||||
│ ├── warn-main.txt
|
|
||||||
│ └── xref-main.html
|
|
||||||
├── core
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── __pycache__
|
|
||||||
│ │ ├── __init__.cpython-313.pyc
|
|
||||||
│ │ ├── asset_generator.cpython-313.pyc
|
|
||||||
│ │ ├── bulk_importer.cpython-313.pyc
|
|
||||||
│ │ ├── clean_glossary.cpython-313.pyc
|
|
||||||
│ │ └── phrase_manager.cpython-313.pyc
|
|
||||||
│ ├── asset_generator.py
|
|
||||||
│ ├── audio_engine.py
|
|
||||||
│ ├── bulk_importer.py
|
|
||||||
│ ├── clean_glossary.py
|
|
||||||
│ └── phrase_manager.py
|
|
||||||
├── database.py
|
|
||||||
├── database_legacy
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── __pycache__
|
|
||||||
│ │ ├── __init__.cpython-313.pyc
|
|
||||||
│ │ └── connection.cpython-313.pyc
|
|
||||||
│ ├── connection.py
|
|
||||||
│ ├── trainer_backup_20260618_220043.db
|
|
||||||
│ └── trainer_backup_20260618_224811.db
|
|
||||||
├── dist
|
|
||||||
│ ├── main
|
|
||||||
│ │ ├── _internal
|
|
||||||
│ │ │ ├── 81d243bd2c585b0f4821__mypyc.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── PIL
|
|
||||||
│ │ │ │ ├── _avif.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _imaging.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _imagingcms.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _imagingft.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _imagingmath.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _imagingtk.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── _webp.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── PyQt6
|
|
||||||
│ │ │ │ ├── Qt6
|
|
||||||
│ │ │ │ │ ├── lib
|
|
||||||
│ │ │ │ │ │ ├── QtCore.framework
|
|
||||||
│ │ │ │ │ │ │ ├── QtCore -> Versions/Current/QtCore
|
|
||||||
│ │ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ │ ├── QtCore
|
|
||||||
│ │ │ │ │ │ │ │ └── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ │ ├── QtDBus.framework
|
|
||||||
│ │ │ │ │ │ │ ├── QtDBus -> Versions/Current/QtDBus
|
|
||||||
│ │ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ │ ├── QtDBus
|
|
||||||
│ │ │ │ │ │ │ │ └── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ │ ├── QtGui.framework
|
|
||||||
│ │ │ │ │ │ │ ├── QtGui -> Versions/Current/QtGui
|
|
||||||
│ │ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ │ ├── QtGui
|
|
||||||
│ │ │ │ │ │ │ │ └── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ │ ├── QtNetwork.framework
|
|
||||||
│ │ │ │ │ │ │ ├── QtNetwork -> Versions/Current/QtNetwork
|
|
||||||
│ │ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ │ ├── QtNetwork
|
|
||||||
│ │ │ │ │ │ │ │ └── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ │ ├── QtPdf.framework
|
|
||||||
│ │ │ │ │ │ │ ├── QtPdf -> Versions/Current/QtPdf
|
|
||||||
│ │ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ │ ├── QtPdf
|
|
||||||
│ │ │ │ │ │ │ │ └── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ │ ├── QtSvg.framework
|
|
||||||
│ │ │ │ │ │ │ ├── QtSvg -> Versions/Current/QtSvg
|
|
||||||
│ │ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ │ ├── QtSvg
|
|
||||||
│ │ │ │ │ │ │ │ └── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ │ └── QtWidgets.framework
|
|
||||||
│ │ │ │ │ │ ├── QtWidgets -> Versions/Current/QtWidgets
|
|
||||||
│ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ ├── QtWidgets
|
|
||||||
│ │ │ │ │ │ │ └── Resources
|
|
||||||
│ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ ├── plugins
|
|
||||||
│ │ │ │ │ │ ├── generic
|
|
||||||
│ │ │ │ │ │ │ └── libqtuiotouchplugin.dylib
|
|
||||||
│ │ │ │ │ │ ├── iconengines
|
|
||||||
│ │ │ │ │ │ │ └── libqsvgicon.dylib
|
|
||||||
│ │ │ │ │ │ ├── imageformats
|
|
||||||
│ │ │ │ │ │ │ ├── libqgif.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqicns.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqico.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqjpeg.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqmacheif.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqmacjp2.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqpdf.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqsvg.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqtga.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqtiff.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqwbmp.dylib
|
|
||||||
│ │ │ │ │ │ │ └── libqwebp.dylib
|
|
||||||
│ │ │ │ │ │ ├── platforms
|
|
||||||
│ │ │ │ │ │ │ ├── libqcocoa.dylib
|
|
||||||
│ │ │ │ │ │ │ ├── libqminimal.dylib
|
|
||||||
│ │ │ │ │ │ │ └── libqoffscreen.dylib
|
|
||||||
│ │ │ │ │ │ └── styles
|
|
||||||
│ │ │ │ │ │ └── libqmacstyle.dylib
|
|
||||||
│ │ │ │ │ └── translations
|
|
||||||
│ │ │ │ │ ├── qt_ar.qm
|
|
||||||
│ │ │ │ │ ├── qt_bg.qm
|
|
||||||
│ │ │ │ │ ├── qt_ca.qm
|
|
||||||
│ │ │ │ │ ├── qt_cs.qm
|
|
||||||
│ │ │ │ │ ├── qt_da.qm
|
|
||||||
│ │ │ │ │ ├── qt_de.qm
|
|
||||||
│ │ │ │ │ ├── qt_en.qm
|
|
||||||
│ │ │ │ │ ├── qt_es.qm
|
|
||||||
│ │ │ │ │ ├── qt_fa.qm
|
|
||||||
│ │ │ │ │ ├── qt_fi.qm
|
|
||||||
│ │ │ │ │ ├── qt_fr.qm
|
|
||||||
│ │ │ │ │ ├── qt_gd.qm
|
|
||||||
│ │ │ │ │ ├── qt_gl.qm
|
|
||||||
│ │ │ │ │ ├── qt_he.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_ar.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_bg.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_ca.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_cs.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_da.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_de.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_en.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_es.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_fr.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_gl.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_hr.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_hu.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_it.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_ja.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_ka.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_ko.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_nl.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_nn.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_pl.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_pt_BR.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_ru.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_sk.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_sl.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_sv.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_tr.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_uk.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_zh_CN.qm
|
|
||||||
│ │ │ │ │ ├── qt_help_zh_TW.qm
|
|
||||||
│ │ │ │ │ ├── qt_hr.qm
|
|
||||||
│ │ │ │ │ ├── qt_hu.qm
|
|
||||||
│ │ │ │ │ ├── qt_it.qm
|
|
||||||
│ │ │ │ │ ├── qt_ja.qm
|
|
||||||
│ │ │ │ │ ├── qt_ka.qm
|
|
||||||
│ │ │ │ │ ├── qt_ko.qm
|
|
||||||
│ │ │ │ │ ├── qt_lg.qm
|
|
||||||
│ │ │ │ │ ├── qt_lt.qm
|
|
||||||
│ │ │ │ │ ├── qt_lv.qm
|
|
||||||
│ │ │ │ │ ├── qt_nl.qm
|
|
||||||
│ │ │ │ │ ├── qt_nn.qm
|
|
||||||
│ │ │ │ │ ├── qt_pl.qm
|
|
||||||
│ │ │ │ │ ├── qt_pt_BR.qm
|
|
||||||
│ │ │ │ │ ├── qt_pt_PT.qm
|
|
||||||
│ │ │ │ │ ├── qt_ru.qm
|
|
||||||
│ │ │ │ │ ├── qt_sk.qm
|
|
||||||
│ │ │ │ │ ├── qt_sl.qm
|
|
||||||
│ │ │ │ │ ├── qt_sv.qm
|
|
||||||
│ │ │ │ │ ├── qt_tr.qm
|
|
||||||
│ │ │ │ │ ├── qt_uk.qm
|
|
||||||
│ │ │ │ │ ├── qt_zh_CN.qm
|
|
||||||
│ │ │ │ │ ├── qt_zh_TW.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_ar.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_bg.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_ca.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_cs.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_da.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_de.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_en.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_es.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_fa.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_fi.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_fr.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_gd.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_he.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_hr.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_hu.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_it.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_ja.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_ka.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_ko.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_lg.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_lv.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_nl.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_nn.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_pl.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_pt_BR.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_ru.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_sk.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_sv.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_tr.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_uk.qm
|
|
||||||
│ │ │ │ │ ├── qtbase_zh_CN.qm
|
|
||||||
│ │ │ │ │ └── qtbase_zh_TW.qm
|
|
||||||
│ │ │ │ ├── QtCore.abi3.so
|
|
||||||
│ │ │ │ ├── QtDBus.abi3.so
|
|
||||||
│ │ │ │ ├── QtGui.abi3.so
|
|
||||||
│ │ │ │ ├── QtWidgets.abi3.so
|
|
||||||
│ │ │ │ └── sip.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── QtCore -> PyQt6/Qt6/lib/QtCore.framework/Versions/A/QtCore
|
|
||||||
│ │ │ ├── QtDBus -> PyQt6/Qt6/lib/QtDBus.framework/Versions/A/QtDBus
|
|
||||||
│ │ │ ├── QtGui -> PyQt6/Qt6/lib/QtGui.framework/Versions/A/QtGui
|
|
||||||
│ │ │ ├── QtNetwork -> PyQt6/Qt6/lib/QtNetwork.framework/Versions/A/QtNetwork
|
|
||||||
│ │ │ ├── QtPdf -> PyQt6/Qt6/lib/QtPdf.framework/Versions/A/QtPdf
|
|
||||||
│ │ │ ├── QtSvg -> PyQt6/Qt6/lib/QtSvg.framework/Versions/A/QtSvg
|
|
||||||
│ │ │ ├── QtWidgets -> PyQt6/Qt6/lib/QtWidgets.framework/Versions/A/QtWidgets
|
|
||||||
│ │ │ ├── aiohttp
|
|
||||||
│ │ │ │ ├── _http_parser.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _http_writer.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── _websocket
|
|
||||||
│ │ │ │ ├── mask.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── reader_c.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── attrs-26.1.0.dist-info
|
|
||||||
│ │ │ │ ├── INSTALLER
|
|
||||||
│ │ │ │ ├── METADATA
|
|
||||||
│ │ │ │ ├── RECORD
|
|
||||||
│ │ │ │ ├── REQUESTED
|
|
||||||
│ │ │ │ ├── WHEEL
|
|
||||||
│ │ │ │ └── licenses
|
|
||||||
│ │ │ │ └── LICENSE
|
|
||||||
│ │ │ ├── base_library.zip
|
|
||||||
│ │ │ ├── certifi
|
|
||||||
│ │ │ │ ├── cacert.pem
|
|
||||||
│ │ │ │ └── py.typed
|
|
||||||
│ │ │ ├── charset_normalizer
|
|
||||||
│ │ │ │ ├── cd.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── md.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── frozenlist
|
|
||||||
│ │ │ │ └── _frozenlist.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── libXau.6.dylib -> PIL/.dylibs/libXau.6.dylib
|
|
||||||
│ │ │ ├── libavif.16.4.1.dylib -> PIL/.dylibs/libavif.16.4.1.dylib
|
|
||||||
│ │ │ ├── libbrotlicommon.1.2.0.dylib -> PIL/.dylibs/libbrotlicommon.1.2.0.dylib
|
|
||||||
│ │ │ ├── libbrotlidec.1.2.0.dylib -> PIL/.dylibs/libbrotlidec.1.2.0.dylib
|
|
||||||
│ │ │ ├── libfreetype.6.dylib -> PIL/.dylibs/libfreetype.6.dylib
|
|
||||||
│ │ │ ├── libharfbuzz.0.dylib -> PIL/.dylibs/libharfbuzz.0.dylib
|
|
||||||
│ │ │ ├── libjpeg.62.4.0.dylib -> PIL/.dylibs/libjpeg.62.4.0.dylib
|
|
||||||
│ │ │ ├── liblcms2.2.dylib -> PIL/.dylibs/liblcms2.2.dylib
|
|
||||||
│ │ │ ├── liblzma.5.dylib -> PIL/.dylibs/liblzma.5.dylib
|
|
||||||
│ │ │ ├── libopenjp2.2.5.4.dylib -> PIL/.dylibs/libopenjp2.2.5.4.dylib
|
|
||||||
│ │ │ ├── libpng16.16.dylib -> PIL/.dylibs/libpng16.16.dylib
|
|
||||||
│ │ │ ├── libpython3.13.dylib
|
|
||||||
│ │ │ ├── libsharpyuv.0.dylib -> PIL/.dylibs/libsharpyuv.0.dylib
|
|
||||||
│ │ │ ├── libtiff.6.dylib -> PIL/.dylibs/libtiff.6.dylib
|
|
||||||
│ │ │ ├── libwebp.7.dylib -> PIL/.dylibs/libwebp.7.dylib
|
|
||||||
│ │ │ ├── libwebpdemux.2.dylib -> PIL/.dylibs/libwebpdemux.2.dylib
|
|
||||||
│ │ │ ├── libwebpmux.3.dylib -> PIL/.dylibs/libwebpmux.3.dylib
|
|
||||||
│ │ │ ├── libxcb.1.1.0.dylib -> PIL/.dylibs/libxcb.1.1.0.dylib
|
|
||||||
│ │ │ ├── libz.1.3.1.zlib-ng.dylib -> PIL/.dylibs/libz.1.3.1.zlib-ng.dylib
|
|
||||||
│ │ │ ├── lxml
|
|
||||||
│ │ │ │ ├── _elementpath.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── builder.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── etree.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── html
|
|
||||||
│ │ │ │ │ ├── _difflib.cpython-313-darwin.so
|
|
||||||
│ │ │ │ │ └── diff.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── isoschematron
|
|
||||||
│ │ │ │ │ └── resources
|
|
||||||
│ │ │ │ │ ├── rng
|
|
||||||
│ │ │ │ │ │ └── iso-schematron.rng
|
|
||||||
│ │ │ │ │ └── xsl
|
|
||||||
│ │ │ │ │ ├── RNG2Schtrn.xsl
|
|
||||||
│ │ │ │ │ ├── XSD2Schtrn.xsl
|
|
||||||
│ │ │ │ │ └── iso-schematron-xslt1
|
|
||||||
│ │ │ │ │ ├── iso_abstract_expand.xsl
|
|
||||||
│ │ │ │ │ ├── iso_dsdl_include.xsl
|
|
||||||
│ │ │ │ │ ├── iso_schematron_message.xsl
|
|
||||||
│ │ │ │ │ ├── iso_schematron_skeleton_for_xslt1.xsl
|
|
||||||
│ │ │ │ │ ├── iso_svrl_for_xslt1.xsl
|
|
||||||
│ │ │ │ │ └── readme.txt
|
|
||||||
│ │ │ │ ├── objectify.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── sax.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── multidict
|
|
||||||
│ │ │ │ └── _multidict.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── numpy
|
|
||||||
│ │ │ │ ├── _core
|
|
||||||
│ │ │ │ │ ├── _multiarray_tests.cpython-313-darwin.so
|
|
||||||
│ │ │ │ │ └── _multiarray_umath.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── fft
|
|
||||||
│ │ │ │ │ └── _pocketfft_umath.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── linalg
|
|
||||||
│ │ │ │ │ └── _umath_linalg.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── random
|
|
||||||
│ │ │ │ ├── _bounded_integers.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _common.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _generator.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _mt19937.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _pcg64.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _philox.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── _sfc64.cpython-313-darwin.so
|
|
||||||
│ │ │ │ ├── bit_generator.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── mtrand.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── numpy-2.4.6.dist-info
|
|
||||||
│ │ │ │ ├── INSTALLER
|
|
||||||
│ │ │ │ ├── METADATA
|
|
||||||
│ │ │ │ ├── RECORD
|
|
||||||
│ │ │ │ ├── REQUESTED
|
|
||||||
│ │ │ │ ├── WHEEL
|
|
||||||
│ │ │ │ ├── entry_points.txt
|
|
||||||
│ │ │ │ └── licenses
|
|
||||||
│ │ │ │ ├── LICENSE.txt
|
|
||||||
│ │ │ │ └── numpy
|
|
||||||
│ │ │ │ ├── _core
|
|
||||||
│ │ │ │ │ ├── include
|
|
||||||
│ │ │ │ │ │ └── numpy
|
|
||||||
│ │ │ │ │ │ └── libdivide
|
|
||||||
│ │ │ │ │ │ └── LICENSE.txt
|
|
||||||
│ │ │ │ │ └── src
|
|
||||||
│ │ │ │ │ ├── common
|
|
||||||
│ │ │ │ │ │ └── pythoncapi-compat
|
|
||||||
│ │ │ │ │ │ └── COPYING
|
|
||||||
│ │ │ │ │ ├── highway
|
|
||||||
│ │ │ │ │ │ └── LICENSE
|
|
||||||
│ │ │ │ │ ├── multiarray
|
|
||||||
│ │ │ │ │ │ └── dragon4_LICENSE.txt
|
|
||||||
│ │ │ │ │ ├── npysort
|
|
||||||
│ │ │ │ │ │ └── x86-simd-sort
|
|
||||||
│ │ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ │ └── umath
|
|
||||||
│ │ │ │ │ └── svml
|
|
||||||
│ │ │ │ │ └── LICENSE
|
|
||||||
│ │ │ │ ├── fft
|
|
||||||
│ │ │ │ │ └── pocketfft
|
|
||||||
│ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ ├── linalg
|
|
||||||
│ │ │ │ │ └── lapack_lite
|
|
||||||
│ │ │ │ │ └── LICENSE.txt
|
|
||||||
│ │ │ │ ├── ma
|
|
||||||
│ │ │ │ │ └── LICENSE
|
|
||||||
│ │ │ │ └── random
|
|
||||||
│ │ │ │ ├── LICENSE.md
|
|
||||||
│ │ │ │ └── src
|
|
||||||
│ │ │ │ ├── distributions
|
|
||||||
│ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ ├── mt19937
|
|
||||||
│ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ ├── pcg64
|
|
||||||
│ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ ├── philox
|
|
||||||
│ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ ├── sfc64
|
|
||||||
│ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ └── splitmix64
|
|
||||||
│ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ ├── propcache
|
|
||||||
│ │ │ │ └── _helpers_c.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── psutil
|
|
||||||
│ │ │ │ └── _psutil_osx.abi3.so
|
|
||||||
│ │ │ ├── pydantic-2.13.4.dist-info
|
|
||||||
│ │ │ │ ├── INSTALLER
|
|
||||||
│ │ │ │ ├── METADATA
|
|
||||||
│ │ │ │ ├── RECORD
|
|
||||||
│ │ │ │ ├── REQUESTED
|
|
||||||
│ │ │ │ ├── WHEEL
|
|
||||||
│ │ │ │ └── licenses
|
|
||||||
│ │ │ │ └── LICENSE
|
|
||||||
│ │ │ ├── pydantic_core
|
|
||||||
│ │ │ │ └── _pydantic_core.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── setuptools
|
|
||||||
│ │ │ │ └── _vendor
|
|
||||||
│ │ │ │ ├── importlib_metadata-8.7.1.dist-info
|
|
||||||
│ │ │ │ │ ├── INSTALLER
|
|
||||||
│ │ │ │ │ ├── METADATA
|
|
||||||
│ │ │ │ │ ├── RECORD
|
|
||||||
│ │ │ │ │ ├── REQUESTED
|
|
||||||
│ │ │ │ │ ├── WHEEL
|
|
||||||
│ │ │ │ │ ├── licenses
|
|
||||||
│ │ │ │ │ │ └── LICENSE
|
|
||||||
│ │ │ │ │ └── top_level.txt
|
|
||||||
│ │ │ │ └── jaraco
|
|
||||||
│ │ │ │ └── text
|
|
||||||
│ │ │ │ └── Lorem ipsum.txt
|
|
||||||
│ │ │ ├── yaml
|
|
||||||
│ │ │ │ └── _yaml.cpython-313-darwin.so
|
|
||||||
│ │ │ └── yarl
|
|
||||||
│ │ │ └── _quoting_c.cpython-313-darwin.so
|
|
||||||
│ │ └── main
|
|
||||||
│ └── main.app
|
|
||||||
│ └── Contents
|
|
||||||
│ ├── Frameworks
|
|
||||||
│ │ ├── 81d243bd2c585b0f4821__mypyc.cpython-313-darwin.so
|
|
||||||
│ │ ├── PIL
|
|
||||||
│ │ │ ├── __dot__dylibs
|
|
||||||
│ │ │ │ ├── libXau.6.dylib
|
|
||||||
│ │ │ │ ├── libavif.16.4.1.dylib
|
|
||||||
│ │ │ │ ├── libbrotlicommon.1.2.0.dylib
|
|
||||||
│ │ │ │ ├── libbrotlidec.1.2.0.dylib
|
|
||||||
│ │ │ │ ├── libfreetype.6.dylib
|
|
||||||
│ │ │ │ ├── libharfbuzz.0.dylib
|
|
||||||
│ │ │ │ ├── libjpeg.62.4.0.dylib
|
|
||||||
│ │ │ │ ├── liblcms2.2.dylib
|
|
||||||
│ │ │ │ ├── liblzma.5.dylib
|
|
||||||
│ │ │ │ ├── libopenjp2.2.5.4.dylib
|
|
||||||
│ │ │ │ ├── libpng16.16.dylib
|
|
||||||
│ │ │ │ ├── libsharpyuv.0.dylib
|
|
||||||
│ │ │ │ ├── libtiff.6.dylib
|
|
||||||
│ │ │ │ ├── libwebp.7.dylib
|
|
||||||
│ │ │ │ ├── libwebpdemux.2.dylib
|
|
||||||
│ │ │ │ ├── libwebpmux.3.dylib
|
|
||||||
│ │ │ │ ├── libxcb.1.1.0.dylib
|
|
||||||
│ │ │ │ └── libz.1.3.1.zlib-ng.dylib
|
|
||||||
│ │ │ ├── _avif.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _imaging.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _imagingcms.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _imagingft.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _imagingmath.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _imagingtk.cpython-313-darwin.so
|
|
||||||
│ │ │ └── _webp.cpython-313-darwin.so
|
|
||||||
│ │ ├── PyQt6
|
|
||||||
│ │ │ ├── Qt6
|
|
||||||
│ │ │ │ ├── lib
|
|
||||||
│ │ │ │ │ ├── QtCore.framework
|
|
||||||
│ │ │ │ │ │ ├── QtCore -> Versions/Current/QtCore
|
|
||||||
│ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ ├── QtCore
|
|
||||||
│ │ │ │ │ │ │ ├── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── _CodeSignature
|
|
||||||
│ │ │ │ │ │ │ └── CodeResources
|
|
||||||
│ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ ├── QtDBus.framework
|
|
||||||
│ │ │ │ │ │ ├── QtDBus -> Versions/Current/QtDBus
|
|
||||||
│ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ ├── QtDBus
|
|
||||||
│ │ │ │ │ │ │ ├── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── _CodeSignature
|
|
||||||
│ │ │ │ │ │ │ └── CodeResources
|
|
||||||
│ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ ├── QtGui.framework
|
|
||||||
│ │ │ │ │ │ ├── QtGui -> Versions/Current/QtGui
|
|
||||||
│ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ ├── QtGui
|
|
||||||
│ │ │ │ │ │ │ ├── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── _CodeSignature
|
|
||||||
│ │ │ │ │ │ │ └── CodeResources
|
|
||||||
│ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ ├── QtNetwork.framework
|
|
||||||
│ │ │ │ │ │ ├── QtNetwork -> Versions/Current/QtNetwork
|
|
||||||
│ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ ├── QtNetwork
|
|
||||||
│ │ │ │ │ │ │ ├── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── _CodeSignature
|
|
||||||
│ │ │ │ │ │ │ └── CodeResources
|
|
||||||
│ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ ├── QtPdf.framework
|
|
||||||
│ │ │ │ │ │ ├── QtPdf -> Versions/Current/QtPdf
|
|
||||||
│ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ ├── QtPdf
|
|
||||||
│ │ │ │ │ │ │ ├── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── _CodeSignature
|
|
||||||
│ │ │ │ │ │ │ └── CodeResources
|
|
||||||
│ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ ├── QtSvg.framework
|
|
||||||
│ │ │ │ │ │ ├── QtSvg -> Versions/Current/QtSvg
|
|
||||||
│ │ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ │ ├── QtSvg
|
|
||||||
│ │ │ │ │ │ │ ├── Resources
|
|
||||||
│ │ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ │ └── _CodeSignature
|
|
||||||
│ │ │ │ │ │ │ └── CodeResources
|
|
||||||
│ │ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ │ └── QtWidgets.framework
|
|
||||||
│ │ │ │ │ ├── QtWidgets -> Versions/Current/QtWidgets
|
|
||||||
│ │ │ │ │ ├── Resources -> Versions/Current/Resources
|
|
||||||
│ │ │ │ │ └── Versions
|
|
||||||
│ │ │ │ │ ├── A
|
|
||||||
│ │ │ │ │ │ ├── QtWidgets
|
|
||||||
│ │ │ │ │ │ ├── Resources
|
|
||||||
│ │ │ │ │ │ │ └── Info.plist
|
|
||||||
│ │ │ │ │ │ └── _CodeSignature
|
|
||||||
│ │ │ │ │ │ └── CodeResources
|
|
||||||
│ │ │ │ │ └── Current -> A
|
|
||||||
│ │ │ │ ├── plugins
|
|
||||||
│ │ │ │ │ ├── generic
|
|
||||||
│ │ │ │ │ │ └── libqtuiotouchplugin.dylib
|
|
||||||
│ │ │ │ │ ├── iconengines
|
|
||||||
│ │ │ │ │ │ └── libqsvgicon.dylib
|
|
||||||
│ │ │ │ │ ├── imageformats
|
|
||||||
│ │ │ │ │ │ ├── libqgif.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqicns.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqico.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqjpeg.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqmacheif.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqmacjp2.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqpdf.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqsvg.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqtga.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqtiff.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqwbmp.dylib
|
|
||||||
│ │ │ │ │ │ └── libqwebp.dylib
|
|
||||||
│ │ │ │ │ ├── platforms
|
|
||||||
│ │ │ │ │ │ ├── libqcocoa.dylib
|
|
||||||
│ │ │ │ │ │ ├── libqminimal.dylib
|
|
||||||
│ │ │ │ │ │ └── libqoffscreen.dylib
|
|
||||||
│ │ │ │ │ └── styles
|
|
||||||
│ │ │ │ │ └── libqmacstyle.dylib
|
|
||||||
│ │ │ │ └── translations -> ../../../Resources/PyQt6/Qt6/translations
|
|
||||||
│ │ │ ├── QtCore.abi3.so
|
|
||||||
│ │ │ ├── QtDBus.abi3.so
|
|
||||||
│ │ │ ├── QtGui.abi3.so
|
|
||||||
│ │ │ ├── QtWidgets.abi3.so
|
|
||||||
│ │ │ └── sip.cpython-313-darwin.so
|
|
||||||
│ │ ├── QtCore -> PyQt6/Qt6/lib/QtCore.framework/Versions/A/QtCore
|
|
||||||
│ │ ├── QtDBus -> PyQt6/Qt6/lib/QtDBus.framework/Versions/A/QtDBus
|
|
||||||
│ │ ├── QtGui -> PyQt6/Qt6/lib/QtGui.framework/Versions/A/QtGui
|
|
||||||
│ │ ├── QtNetwork -> PyQt6/Qt6/lib/QtNetwork.framework/Versions/A/QtNetwork
|
|
||||||
│ │ ├── QtPdf -> PyQt6/Qt6/lib/QtPdf.framework/Versions/A/QtPdf
|
|
||||||
│ │ ├── QtSvg -> PyQt6/Qt6/lib/QtSvg.framework/Versions/A/QtSvg
|
|
||||||
│ │ ├── QtWidgets -> PyQt6/Qt6/lib/QtWidgets.framework/Versions/A/QtWidgets
|
|
||||||
│ │ ├── aiohttp
|
|
||||||
│ │ │ ├── _http_parser.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _http_writer.cpython-313-darwin.so
|
|
||||||
│ │ │ └── _websocket
|
|
||||||
│ │ │ ├── mask.cpython-313-darwin.so
|
|
||||||
│ │ │ └── reader_c.cpython-313-darwin.so
|
|
||||||
│ │ ├── attrs-26.1.0.dist-info -> ../Resources/attrs-26.1.0.dist-info
|
|
||||||
│ │ ├── base_library.zip -> ../Resources/base_library.zip
|
|
||||||
│ │ ├── certifi -> ../Resources/certifi
|
|
||||||
│ │ ├── charset_normalizer
|
|
||||||
│ │ │ ├── cd.cpython-313-darwin.so
|
|
||||||
│ │ │ └── md.cpython-313-darwin.so
|
|
||||||
│ │ ├── frozenlist
|
|
||||||
│ │ │ └── _frozenlist.cpython-313-darwin.so
|
|
||||||
│ │ ├── libXau.6.dylib -> PIL/.dylibs/libXau.6.dylib
|
|
||||||
│ │ ├── libavif.16.4.1.dylib -> PIL/.dylibs/libavif.16.4.1.dylib
|
|
||||||
│ │ ├── libbrotlicommon.1.2.0.dylib -> PIL/.dylibs/libbrotlicommon.1.2.0.dylib
|
|
||||||
│ │ ├── libbrotlidec.1.2.0.dylib -> PIL/.dylibs/libbrotlidec.1.2.0.dylib
|
|
||||||
│ │ ├── libfreetype.6.dylib -> PIL/.dylibs/libfreetype.6.dylib
|
|
||||||
│ │ ├── libharfbuzz.0.dylib -> PIL/.dylibs/libharfbuzz.0.dylib
|
|
||||||
│ │ ├── libjpeg.62.4.0.dylib -> PIL/.dylibs/libjpeg.62.4.0.dylib
|
|
||||||
│ │ ├── liblcms2.2.dylib -> PIL/.dylibs/liblcms2.2.dylib
|
|
||||||
│ │ ├── liblzma.5.dylib -> PIL/.dylibs/liblzma.5.dylib
|
|
||||||
│ │ ├── libopenjp2.2.5.4.dylib -> PIL/.dylibs/libopenjp2.2.5.4.dylib
|
|
||||||
│ │ ├── libpng16.16.dylib -> PIL/.dylibs/libpng16.16.dylib
|
|
||||||
│ │ ├── libpython3.13.dylib
|
|
||||||
│ │ ├── libsharpyuv.0.dylib -> PIL/.dylibs/libsharpyuv.0.dylib
|
|
||||||
│ │ ├── libtiff.6.dylib -> PIL/.dylibs/libtiff.6.dylib
|
|
||||||
│ │ ├── libwebp.7.dylib -> PIL/.dylibs/libwebp.7.dylib
|
|
||||||
│ │ ├── libwebpdemux.2.dylib -> PIL/.dylibs/libwebpdemux.2.dylib
|
|
||||||
│ │ ├── libwebpmux.3.dylib -> PIL/.dylibs/libwebpmux.3.dylib
|
|
||||||
│ │ ├── libxcb.1.1.0.dylib -> PIL/.dylibs/libxcb.1.1.0.dylib
|
|
||||||
│ │ ├── libz.1.3.1.zlib-ng.dylib -> PIL/.dylibs/libz.1.3.1.zlib-ng.dylib
|
|
||||||
│ │ ├── lxml
|
|
||||||
│ │ │ ├── _elementpath.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── builder.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── etree.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── html
|
|
||||||
│ │ │ │ ├── _difflib.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── diff.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── isoschematron -> ../../Resources/lxml/isoschematron
|
|
||||||
│ │ │ ├── objectify.cpython-313-darwin.so
|
|
||||||
│ │ │ └── sax.cpython-313-darwin.so
|
|
||||||
│ │ ├── multidict
|
|
||||||
│ │ │ └── _multidict.cpython-313-darwin.so
|
|
||||||
│ │ ├── numpy
|
|
||||||
│ │ │ ├── _core
|
|
||||||
│ │ │ │ ├── _multiarray_tests.cpython-313-darwin.so
|
|
||||||
│ │ │ │ └── _multiarray_umath.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── fft
|
|
||||||
│ │ │ │ └── _pocketfft_umath.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── linalg
|
|
||||||
│ │ │ │ └── _umath_linalg.cpython-313-darwin.so
|
|
||||||
│ │ │ └── random
|
|
||||||
│ │ │ ├── _bounded_integers.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _common.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _generator.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _mt19937.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _pcg64.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _philox.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── _sfc64.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── bit_generator.cpython-313-darwin.so
|
|
||||||
│ │ │ └── mtrand.cpython-313-darwin.so
|
|
||||||
│ │ ├── numpy-2.4.6.dist-info -> ../Resources/numpy-2.4.6.dist-info
|
|
||||||
│ │ ├── propcache
|
|
||||||
│ │ │ └── _helpers_c.cpython-313-darwin.so
|
|
||||||
│ │ ├── psutil
|
|
||||||
│ │ │ └── _psutil_osx.abi3.so
|
|
||||||
│ │ ├── pydantic-2.13.4.dist-info -> ../Resources/pydantic-2.13.4.dist-info
|
|
||||||
│ │ ├── pydantic_core
|
|
||||||
│ │ │ └── _pydantic_core.cpython-313-darwin.so
|
|
||||||
│ │ ├── setuptools -> ../Resources/setuptools
|
|
||||||
│ │ ├── yaml
|
|
||||||
│ │ │ └── _yaml.cpython-313-darwin.so
|
|
||||||
│ │ └── yarl
|
|
||||||
│ │ └── _quoting_c.cpython-313-darwin.so
|
|
||||||
│ ├── Info.plist
|
|
||||||
│ ├── MacOS
|
|
||||||
│ │ └── main
|
|
||||||
│ ├── Resources
|
|
||||||
│ │ ├── 81d243bd2c585b0f4821__mypyc.cpython-313-darwin.so -> ../Frameworks/81d243bd2c585b0f4821__mypyc.cpython-313-darwin.so
|
|
||||||
│ │ ├── PIL -> ../Frameworks/PIL
|
|
||||||
│ │ ├── PyQt6
|
|
||||||
│ │ │ ├── Qt6
|
|
||||||
│ │ │ │ ├── lib -> ../../../Frameworks/PyQt6/Qt6/lib
|
|
||||||
│ │ │ │ ├── plugins -> ../../../Frameworks/PyQt6/Qt6/plugins
|
|
||||||
│ │ │ │ └── translations
|
|
||||||
│ │ │ │ ├── qt_ar.qm
|
|
||||||
│ │ │ │ ├── qt_bg.qm
|
|
||||||
│ │ │ │ ├── qt_ca.qm
|
|
||||||
│ │ │ │ ├── qt_cs.qm
|
|
||||||
│ │ │ │ ├── qt_da.qm
|
|
||||||
│ │ │ │ ├── qt_de.qm
|
|
||||||
│ │ │ │ ├── qt_en.qm
|
|
||||||
│ │ │ │ ├── qt_es.qm
|
|
||||||
│ │ │ │ ├── qt_fa.qm
|
|
||||||
│ │ │ │ ├── qt_fi.qm
|
|
||||||
│ │ │ │ ├── qt_fr.qm
|
|
||||||
│ │ │ │ ├── qt_gd.qm
|
|
||||||
│ │ │ │ ├── qt_gl.qm
|
|
||||||
│ │ │ │ ├── qt_he.qm
|
|
||||||
│ │ │ │ ├── qt_help_ar.qm
|
|
||||||
│ │ │ │ ├── qt_help_bg.qm
|
|
||||||
│ │ │ │ ├── qt_help_ca.qm
|
|
||||||
│ │ │ │ ├── qt_help_cs.qm
|
|
||||||
│ │ │ │ ├── qt_help_da.qm
|
|
||||||
│ │ │ │ ├── qt_help_de.qm
|
|
||||||
│ │ │ │ ├── qt_help_en.qm
|
|
||||||
│ │ │ │ ├── qt_help_es.qm
|
|
||||||
│ │ │ │ ├── qt_help_fr.qm
|
|
||||||
│ │ │ │ ├── qt_help_gl.qm
|
|
||||||
│ │ │ │ ├── qt_help_hr.qm
|
|
||||||
│ │ │ │ ├── qt_help_hu.qm
|
|
||||||
│ │ │ │ ├── qt_help_it.qm
|
|
||||||
│ │ │ │ ├── qt_help_ja.qm
|
|
||||||
│ │ │ │ ├── qt_help_ka.qm
|
|
||||||
│ │ │ │ ├── qt_help_ko.qm
|
|
||||||
│ │ │ │ ├── qt_help_nl.qm
|
|
||||||
│ │ │ │ ├── qt_help_nn.qm
|
|
||||||
│ │ │ │ ├── qt_help_pl.qm
|
|
||||||
│ │ │ │ ├── qt_help_pt_BR.qm
|
|
||||||
│ │ │ │ ├── qt_help_ru.qm
|
|
||||||
│ │ │ │ ├── qt_help_sk.qm
|
|
||||||
│ │ │ │ ├── qt_help_sl.qm
|
|
||||||
│ │ │ │ ├── qt_help_sv.qm
|
|
||||||
│ │ │ │ ├── qt_help_tr.qm
|
|
||||||
│ │ │ │ ├── qt_help_uk.qm
|
|
||||||
│ │ │ │ ├── qt_help_zh_CN.qm
|
|
||||||
│ │ │ │ ├── qt_help_zh_TW.qm
|
|
||||||
│ │ │ │ ├── qt_hr.qm
|
|
||||||
│ │ │ │ ├── qt_hu.qm
|
|
||||||
│ │ │ │ ├── qt_it.qm
|
|
||||||
│ │ │ │ ├── qt_ja.qm
|
|
||||||
│ │ │ │ ├── qt_ka.qm
|
|
||||||
│ │ │ │ ├── qt_ko.qm
|
|
||||||
│ │ │ │ ├── qt_lg.qm
|
|
||||||
│ │ │ │ ├── qt_lt.qm
|
|
||||||
│ │ │ │ ├── qt_lv.qm
|
|
||||||
│ │ │ │ ├── qt_nl.qm
|
|
||||||
│ │ │ │ ├── qt_nn.qm
|
|
||||||
│ │ │ │ ├── qt_pl.qm
|
|
||||||
│ │ │ │ ├── qt_pt_BR.qm
|
|
||||||
│ │ │ │ ├── qt_pt_PT.qm
|
|
||||||
│ │ │ │ ├── qt_ru.qm
|
|
||||||
│ │ │ │ ├── qt_sk.qm
|
|
||||||
│ │ │ │ ├── qt_sl.qm
|
|
||||||
│ │ │ │ ├── qt_sv.qm
|
|
||||||
│ │ │ │ ├── qt_tr.qm
|
|
||||||
│ │ │ │ ├── qt_uk.qm
|
|
||||||
│ │ │ │ ├── qt_zh_CN.qm
|
|
||||||
│ │ │ │ ├── qt_zh_TW.qm
|
|
||||||
│ │ │ │ ├── qtbase_ar.qm
|
|
||||||
│ │ │ │ ├── qtbase_bg.qm
|
|
||||||
│ │ │ │ ├── qtbase_ca.qm
|
|
||||||
│ │ │ │ ├── qtbase_cs.qm
|
|
||||||
│ │ │ │ ├── qtbase_da.qm
|
|
||||||
│ │ │ │ ├── qtbase_de.qm
|
|
||||||
│ │ │ │ ├── qtbase_en.qm
|
|
||||||
│ │ │ │ ├── qtbase_es.qm
|
|
||||||
│ │ │ │ ├── qtbase_fa.qm
|
|
||||||
│ │ │ │ ├── qtbase_fi.qm
|
|
||||||
│ │ │ │ ├── qtbase_fr.qm
|
|
||||||
│ │ │ │ ├── qtbase_gd.qm
|
|
||||||
│ │ │ │ ├── qtbase_he.qm
|
|
||||||
│ │ │ │ ├── qtbase_hr.qm
|
|
||||||
│ │ │ │ ├── qtbase_hu.qm
|
|
||||||
│ │ │ │ ├── qtbase_it.qm
|
|
||||||
│ │ │ │ ├── qtbase_ja.qm
|
|
||||||
│ │ │ │ ├── qtbase_ka.qm
|
|
||||||
│ │ │ │ ├── qtbase_ko.qm
|
|
||||||
│ │ │ │ ├── qtbase_lg.qm
|
|
||||||
│ │ │ │ ├── qtbase_lv.qm
|
|
||||||
│ │ │ │ ├── qtbase_nl.qm
|
|
||||||
│ │ │ │ ├── qtbase_nn.qm
|
|
||||||
│ │ │ │ ├── qtbase_pl.qm
|
|
||||||
│ │ │ │ ├── qtbase_pt_BR.qm
|
|
||||||
│ │ │ │ ├── qtbase_ru.qm
|
|
||||||
│ │ │ │ ├── qtbase_sk.qm
|
|
||||||
│ │ │ │ ├── qtbase_sv.qm
|
|
||||||
│ │ │ │ ├── qtbase_tr.qm
|
|
||||||
│ │ │ │ ├── qtbase_uk.qm
|
|
||||||
│ │ │ │ ├── qtbase_zh_CN.qm
|
|
||||||
│ │ │ │ └── qtbase_zh_TW.qm
|
|
||||||
│ │ │ ├── QtCore.abi3.so -> ../../Frameworks/PyQt6/QtCore.abi3.so
|
|
||||||
│ │ │ ├── QtDBus.abi3.so -> ../../Frameworks/PyQt6/QtDBus.abi3.so
|
|
||||||
│ │ │ ├── QtGui.abi3.so -> ../../Frameworks/PyQt6/QtGui.abi3.so
|
|
||||||
│ │ │ ├── QtWidgets.abi3.so -> ../../Frameworks/PyQt6/QtWidgets.abi3.so
|
|
||||||
│ │ │ └── sip.cpython-313-darwin.so -> ../../Frameworks/PyQt6/sip.cpython-313-darwin.so
|
|
||||||
│ │ ├── QtCore -> PyQt6/Qt6/lib/QtCore.framework/Versions/A/QtCore
|
|
||||||
│ │ ├── QtDBus -> PyQt6/Qt6/lib/QtDBus.framework/Versions/A/QtDBus
|
|
||||||
│ │ ├── QtGui -> PyQt6/Qt6/lib/QtGui.framework/Versions/A/QtGui
|
|
||||||
│ │ ├── QtNetwork -> PyQt6/Qt6/lib/QtNetwork.framework/Versions/A/QtNetwork
|
|
||||||
│ │ ├── QtPdf -> PyQt6/Qt6/lib/QtPdf.framework/Versions/A/QtPdf
|
|
||||||
│ │ ├── QtSvg -> PyQt6/Qt6/lib/QtSvg.framework/Versions/A/QtSvg
|
|
||||||
│ │ ├── QtWidgets -> PyQt6/Qt6/lib/QtWidgets.framework/Versions/A/QtWidgets
|
|
||||||
│ │ ├── aiohttp -> ../Frameworks/aiohttp
|
|
||||||
│ │ ├── app.icns
|
|
||||||
│ │ ├── attrs-26.1.0.dist-info
|
|
||||||
│ │ │ ├── INSTALLER
|
|
||||||
│ │ │ ├── METADATA
|
|
||||||
│ │ │ ├── RECORD
|
|
||||||
│ │ │ ├── REQUESTED
|
|
||||||
│ │ │ ├── WHEEL
|
|
||||||
│ │ │ └── licenses
|
|
||||||
│ │ │ └── LICENSE
|
|
||||||
│ │ ├── base_library.zip
|
|
||||||
│ │ ├── certifi
|
|
||||||
│ │ │ ├── cacert.pem
|
|
||||||
│ │ │ └── py.typed
|
|
||||||
│ │ ├── charset_normalizer -> ../Frameworks/charset_normalizer
|
|
||||||
│ │ ├── frozenlist -> ../Frameworks/frozenlist
|
|
||||||
│ │ ├── libXau.6.dylib -> PIL/.dylibs/libXau.6.dylib
|
|
||||||
│ │ ├── libavif.16.4.1.dylib -> PIL/.dylibs/libavif.16.4.1.dylib
|
|
||||||
│ │ ├── libbrotlicommon.1.2.0.dylib -> PIL/.dylibs/libbrotlicommon.1.2.0.dylib
|
|
||||||
│ │ ├── libbrotlidec.1.2.0.dylib -> PIL/.dylibs/libbrotlidec.1.2.0.dylib
|
|
||||||
│ │ ├── libfreetype.6.dylib -> PIL/.dylibs/libfreetype.6.dylib
|
|
||||||
│ │ ├── libharfbuzz.0.dylib -> PIL/.dylibs/libharfbuzz.0.dylib
|
|
||||||
│ │ ├── libjpeg.62.4.0.dylib -> PIL/.dylibs/libjpeg.62.4.0.dylib
|
|
||||||
│ │ ├── liblcms2.2.dylib -> PIL/.dylibs/liblcms2.2.dylib
|
|
||||||
│ │ ├── liblzma.5.dylib -> PIL/.dylibs/liblzma.5.dylib
|
|
||||||
│ │ ├── libopenjp2.2.5.4.dylib -> PIL/.dylibs/libopenjp2.2.5.4.dylib
|
|
||||||
│ │ ├── libpng16.16.dylib -> PIL/.dylibs/libpng16.16.dylib
|
|
||||||
│ │ ├── libpython3.13.dylib -> ../Frameworks/libpython3.13.dylib
|
|
||||||
│ │ ├── libsharpyuv.0.dylib -> PIL/.dylibs/libsharpyuv.0.dylib
|
|
||||||
│ │ ├── libtiff.6.dylib -> PIL/.dylibs/libtiff.6.dylib
|
|
||||||
│ │ ├── libwebp.7.dylib -> PIL/.dylibs/libwebp.7.dylib
|
|
||||||
│ │ ├── libwebpdemux.2.dylib -> PIL/.dylibs/libwebpdemux.2.dylib
|
|
||||||
│ │ ├── libwebpmux.3.dylib -> PIL/.dylibs/libwebpmux.3.dylib
|
|
||||||
│ │ ├── libxcb.1.1.0.dylib -> PIL/.dylibs/libxcb.1.1.0.dylib
|
|
||||||
│ │ ├── libz.1.3.1.zlib-ng.dylib -> PIL/.dylibs/libz.1.3.1.zlib-ng.dylib
|
|
||||||
│ │ ├── lxml
|
|
||||||
│ │ │ ├── _elementpath.cpython-313-darwin.so -> ../../Frameworks/lxml/_elementpath.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── builder.cpython-313-darwin.so -> ../../Frameworks/lxml/builder.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── etree.cpython-313-darwin.so -> ../../Frameworks/lxml/etree.cpython-313-darwin.so
|
|
||||||
│ │ │ ├── html -> ../../Frameworks/lxml/html
|
|
||||||
│ │ │ ├── isoschematron
|
|
||||||
│ │ │ │ └── resources
|
|
||||||
│ │ │ │ ├── rng
|
|
||||||
│ │ │ │ │ └── iso-schematron.rng
|
|
||||||
│ │ │ │ └── xsl
|
|
||||||
│ │ │ │ ├── RNG2Schtrn.xsl
|
|
||||||
│ │ │ │ ├── XSD2Schtrn.xsl
|
|
||||||
│ │ │ │ └── iso-schematron-xslt1
|
|
||||||
│ │ │ │ ├── iso_abstract_expand.xsl
|
|
||||||
│ │ │ │ ├── iso_dsdl_include.xsl
|
|
||||||
│ │ │ │ ├── iso_schematron_message.xsl
|
|
||||||
│ │ │ │ ├── iso_schematron_skeleton_for_xslt1.xsl
|
|
||||||
│ │ │ │ ├── iso_svrl_for_xslt1.xsl
|
|
||||||
│ │ │ │ └── readme.txt
|
|
||||||
│ │ │ ├── objectify.cpython-313-darwin.so -> ../../Frameworks/lxml/objectify.cpython-313-darwin.so
|
|
||||||
│ │ │ └── sax.cpython-313-darwin.so -> ../../Frameworks/lxml/sax.cpython-313-darwin.so
|
|
||||||
│ │ ├── multidict -> ../Frameworks/multidict
|
|
||||||
│ │ ├── numpy -> ../Frameworks/numpy
|
|
||||||
│ │ ├── numpy-2.4.6.dist-info
|
|
||||||
│ │ │ ├── INSTALLER
|
|
||||||
│ │ │ ├── METADATA
|
|
||||||
│ │ │ ├── RECORD
|
|
||||||
│ │ │ ├── REQUESTED
|
|
||||||
│ │ │ ├── WHEEL
|
|
||||||
│ │ │ ├── entry_points.txt
|
|
||||||
│ │ │ └── licenses
|
|
||||||
│ │ │ ├── LICENSE.txt
|
|
||||||
│ │ │ └── numpy
|
|
||||||
│ │ │ ├── _core
|
|
||||||
│ │ │ │ ├── include
|
|
||||||
│ │ │ │ │ └── numpy
|
|
||||||
│ │ │ │ │ └── libdivide
|
|
||||||
│ │ │ │ │ └── LICENSE.txt
|
|
||||||
│ │ │ │ └── src
|
|
||||||
│ │ │ │ ├── common
|
|
||||||
│ │ │ │ │ └── pythoncapi-compat
|
|
||||||
│ │ │ │ │ └── COPYING
|
|
||||||
│ │ │ │ ├── highway
|
|
||||||
│ │ │ │ │ └── LICENSE
|
|
||||||
│ │ │ │ ├── multiarray
|
|
||||||
│ │ │ │ │ └── dragon4_LICENSE.txt
|
|
||||||
│ │ │ │ ├── npysort
|
|
||||||
│ │ │ │ │ └── x86-simd-sort
|
|
||||||
│ │ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ │ └── umath
|
|
||||||
│ │ │ │ └── svml
|
|
||||||
│ │ │ │ └── LICENSE
|
|
||||||
│ │ │ ├── fft
|
|
||||||
│ │ │ │ └── pocketfft
|
|
||||||
│ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ ├── linalg
|
|
||||||
│ │ │ │ └── lapack_lite
|
|
||||||
│ │ │ │ └── LICENSE.txt
|
|
||||||
│ │ │ ├── ma
|
|
||||||
│ │ │ │ └── LICENSE
|
|
||||||
│ │ │ └── random
|
|
||||||
│ │ │ ├── LICENSE.md
|
|
||||||
│ │ │ └── src
|
|
||||||
│ │ │ ├── distributions
|
|
||||||
│ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ ├── mt19937
|
|
||||||
│ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ ├── pcg64
|
|
||||||
│ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ ├── philox
|
|
||||||
│ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ ├── sfc64
|
|
||||||
│ │ │ │ └── LICENSE.md
|
|
||||||
│ │ │ └── splitmix64
|
|
||||||
│ │ │ └── LICENSE.md
|
|
||||||
│ │ ├── propcache -> ../Frameworks/propcache
|
|
||||||
│ │ ├── psutil -> ../Frameworks/psutil
|
|
||||||
│ │ ├── pydantic-2.13.4.dist-info
|
|
||||||
│ │ │ ├── INSTALLER
|
|
||||||
│ │ │ ├── METADATA
|
|
||||||
│ │ │ ├── RECORD
|
|
||||||
│ │ │ ├── REQUESTED
|
|
||||||
│ │ │ ├── WHEEL
|
|
||||||
│ │ │ └── licenses
|
|
||||||
│ │ │ └── LICENSE
|
|
||||||
│ │ ├── pydantic_core -> ../Frameworks/pydantic_core
|
|
||||||
│ │ ├── setuptools
|
|
||||||
│ │ │ └── _vendor
|
|
||||||
│ │ │ ├── importlib_metadata-8.7.1.dist-info
|
|
||||||
│ │ │ │ ├── INSTALLER
|
|
||||||
│ │ │ │ ├── METADATA
|
|
||||||
│ │ │ │ ├── RECORD
|
|
||||||
│ │ │ │ ├── REQUESTED
|
|
||||||
│ │ │ │ ├── WHEEL
|
|
||||||
│ │ │ │ ├── licenses
|
|
||||||
│ │ │ │ │ └── LICENSE
|
|
||||||
│ │ │ │ └── top_level.txt
|
|
||||||
│ │ │ └── jaraco
|
|
||||||
│ │ │ └── text
|
|
||||||
│ │ │ └── Lorem ipsum.txt
|
|
||||||
│ │ ├── yaml -> ../Frameworks/yaml
|
|
||||||
│ │ └── yarl -> ../Frameworks/yarl
|
|
||||||
│ └── _CodeSignature
|
|
||||||
│ └── CodeResources
|
|
||||||
├── doc
|
|
||||||
│ ├── Notes.md
|
|
||||||
│ ├── Notes.pdf
|
|
||||||
│ └── images
|
|
||||||
│ ├── image-01.png
|
|
||||||
│ ├── image-02.png
|
|
||||||
│ └── image-03.png
|
|
||||||
├── docling_output.md
|
|
||||||
├── exported_phases.csv
|
|
||||||
├── main.py
|
|
||||||
├── main.spec
|
|
||||||
├── media
|
|
||||||
├── migrate_database.py
|
|
||||||
├── pyproject.toml
|
|
||||||
├── requirements.txt
|
|
||||||
├── spanish_alphabetical.txt
|
|
||||||
├── spanish_glossary_sorted.txt
|
|
||||||
├── spanish_glossary_sorted.xlsx
|
|
||||||
├── spanish_glossary_sorted_B.pdf
|
|
||||||
├── spanish_glossary_sorted_B.txt
|
|
||||||
├── spanish_glossary_sorted_B.xlsx
|
|
||||||
├── spanish_trainer-26-08-21.db
|
|
||||||
├── spanish_trainer-backup.db
|
|
||||||
├── spanish_trainer.db
|
|
||||||
├── spanish_trainer_backup.sql
|
|
||||||
├── spanish_trainer_legacy.db
|
|
||||||
├── tabs
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── __pycache__
|
|
||||||
│ │ ├── __init__.cpython-313.pyc
|
|
||||||
│ │ ├── review_tab.cpython-313.pyc
|
|
||||||
│ │ ├── sandbox_tab.cpython-313.pyc
|
|
||||||
│ │ └── settings_tab.cpython-313.pyc
|
|
||||||
│ ├── review_tab.py
|
|
||||||
│ ├── sandbox_tab.py
|
|
||||||
│ └── settings_tab.py
|
|
||||||
├── test_dual.py
|
|
||||||
├── test_tts.py
|
|
||||||
├── tree.txt
|
|
||||||
├── tts_utils.py
|
|
||||||
├── ui
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── cli
|
|
||||||
│ │ └── interface.py
|
|
||||||
│ └── gui
|
|
||||||
│ ├── create_mode.py
|
|
||||||
│ ├── interface.py
|
|
||||||
│ └── trainer_mode.py
|
|
||||||
├── uv.lock
|
|
||||||
└── video_generator.py
|
|
||||||
|
|
||||||
289 directories, 690 files
|
|
||||||
17
tts_utils.py
17
tts_utils.py
|
|
@ -3,24 +3,19 @@ import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
def get_configured_tts_rate(settings: dict) -> str:
|
def get_configured_tts_rate(settings: dict) -> float:
|
||||||
"""Extracts speed multiplier from settings dict and converts to Edge TTS percentage string (e.g., '-25%')."""
|
"""Extracts speed multiplier from settings dict as a clean float (e.g., 0.75) for Kokoro TTS."""
|
||||||
# Query the exact key name shown in SQLite database: 'tts_playback_speed'
|
|
||||||
raw_val = settings.get("tts_playback_speed", 1.0) if settings else 1.0
|
raw_val = settings.get("tts_playback_speed", 1.0) if settings else 1.0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raw_rate = float(raw_val)
|
speed_rate = float(raw_val)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
raw_rate = 1.0
|
speed_rate = 1.0
|
||||||
|
|
||||||
# Calculate percentage shift relative to baseline 1.0 (e.g., 0.75 -> -25%)
|
|
||||||
pct = int(round((raw_rate - 1.0) * 100))
|
|
||||||
rate_str = f"+{pct}%" if pct >= 0 else f"{pct}%"
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"[TTS Debug] DB Key 'tts_playback_speed': {raw_val} -> Formatted Rate: {rate_str}"
|
f"[TTS Debug] DB Key 'tts_playback_speed': {raw_val} -> Kokoro Speed Multiplier: {speed_rate}"
|
||||||
)
|
)
|
||||||
return rate_str
|
return speed_rate
|
||||||
|
|
||||||
|
|
||||||
def parse_text_for_edgetts(html_content: str) -> str:
|
def parse_text_for_edgetts(html_content: str) -> str:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue