Skip to content
30 changes: 13 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
from voxkit.config.app_config import AppConfig, get_app_config, get_profile_config_path
from voxkit.config.logging_config import setup_logging

# Disable Qt emoji support to prevent crashes in frozen builds

# Minimal early config so frozen-env messages below are emitted before
# setup_logging() runs in main(); setup_logging() will reconfigure handlers.
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
log = logging.getLogger("voxkit.main")

# CRITICAL: Must be at the top for frozen apps using multiprocessing
if __name__ == "__main__":
Expand Down Expand Up @@ -55,10 +57,10 @@
if conda_bin:
existing_path = os.environ.get('PATH', '/usr/bin:/bin:/usr/sbin:/sbin')
minimal_env['PATH'] = f"{conda_bin}:{existing_path}"
print(f"[FROZEN] Added conda to PATH: {conda_bin}")
log.info("[FROZEN] Added conda to PATH: %s", conda_bin)
else:
minimal_env['PATH'] = os.environ.get('PATH', '/usr/bin:/bin:/usr/sbin:/sbin')
print("[FROZEN] Warning: conda not found in standard locations. MFA alignment may fail.")
log.warning("[FROZEN] conda not found in standard locations. MFA alignment may fail.")

# PyInstaller-specific: Add Qt plugin paths
if getattr(sys, '_MEIPASS', None):
Expand All @@ -67,30 +69,26 @@
if os.path.exists(qt_plugins):
minimal_env['QT_PLUGIN_PATH'] = qt_plugins
minimal_env['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(qt_plugins, 'platforms')

# Additional Qt environment for frozen apps
minimal_env['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1'
minimal_env['QT_LOGGING_RULES'] = '*.debug=false;qt.qpa.*=false'
print(f"[FROZEN] Qt plugins directory: {qt_plugins}")
print(f"[FROZEN] Bundle directory: {bundle_dir}")

log.info("[FROZEN] Qt plugins directory: %s", qt_plugins)
log.info("[FROZEN] Bundle directory: %s", bundle_dir)

# IMPORTANT: Don't clear os.environ - preserve system environment
# Just add/override our minimal required variables
for key, value in minimal_env.items():
if value:
os.environ[key] = value

print("[FROZEN] Environment configured for frozen app")


log.info("[FROZEN] Environment configured for frozen app")

from PyQt6.QtWidgets import QApplication
from voxkit.config import STARTUP_SCRIPT
from voxkit.gui import AlignmentGUI
from voxkit.gui import VoxKitGUI
from voxkit.gui.workers.startup import execute_startup_script
from pathlib import Path


def main():
# Initialize logging as early as possible so startup work is captured.
Expand All @@ -108,7 +106,6 @@ def main():
from voxkit.gui.components.log_handler import get_gui_log_handler
get_gui_log_handler()

log = logging.getLogger("voxkit.main")
log.info("VoxKit starting (frozen=%s)", bool(getattr(sys, "frozen", False)))

app = QApplication(sys.argv)
Expand All @@ -128,14 +125,13 @@ def main():
app_config = AppConfig.from_yaml(profile_path / "app_info.yaml")
pipeline_config = PipelineConfig.from_yaml(profile_path / "pipeline_definitions.yaml")

window = AlignmentGUI(pipeline_config=pipeline_config, app_config=app_config)
window = VoxKitGUI(pipeline_config=pipeline_config, app_config=app_config)
window.show()
log.info("Main window shown, entering Qt event loop")
sys.exit(app.exec())


if __name__ == "__main__":
# Prevent multiprocessing from spawning new app windows in frozen builds
multiprocessing.freeze_support()
multiprocessing.set_start_method('spawn', force=True)
main()
13 changes: 5 additions & 8 deletions src/voxkit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
"""VoxKit - Speech Analysis and Forced Alignment Toolkit.

A desktop application bridging AI/ML research and clinical speech-language
pathology. Provides accessible interfaces to forced alignment engines and
flexible dataset analysis tools.
"""Modules for VoxKit; Provides accessible interfaces to forced alignment engines and
flexible dataset analysis and management tools.

Subpackages
-----------
- **engines**: Speech toolkit backends (MFA, Faster-Whisper)
- **engines**: Speech toolkit backends (MFA, W2TG. FasterWhisper, etc.)
- **analyzers**: Dataset metadata extraction
- **storage**: Persistence for datasets, models, and alignments
- **gui**: PyQt6 desktop interface
- **config**: Application and pipeline configuration
"""

__version__ = "0.1.0"
__author__ = "Beckett Frey @beckettfrey.com"
__version__ = "0.4.0"
__author__ = "Beckett Frey - code@beckettfrey.com"

# Import subpackages for pdoc discoverability (not re-exported in __all__)
from . import analyzers, config, engines, gui, storage
Expand Down
14 changes: 4 additions & 10 deletions src/voxkit/analyzers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
"""VoxKit Analyzers Module.

Analyzers observe datasets and link metadata in flexible, abstract ways. They
extract structured information at registration time, producing CSV summaries
"""Analyzers observe datasets and link metadata. They
extract structured information at registration time (ingestion), producing CSV summaries
that can be visualized within VoxKit without rescanning the filesystem.

API
---
- **AnalyzerManager.list_analyzers**: List registered analyzer IDs
- **AnalyzerManager.get_analyzer**: Retrieve analyzer instance by ID
- **AnalyzerManager.get_analyzers**: Get all registered analyzers
- **DatasetAnalyzer**: Abstract base class for all analyzers

Available Analyzers
-------------------
**DefaultAnalyzer** (``default_analyzer.py``)
**DefaultAnalyzer**
Extracts speaker count and audio file counts per speaker directory.
Includes a bar chart visualization for quick dataset overview.

Expand All @@ -28,7 +25,7 @@

Notes
-----
- Analyzers run during dataset registration
- Analyzers run during dataset registration (ingestion)
- Each analyzer's ``name`` property serves as its unique identifier
- Output is a list of dicts where keys become CSV column headers
- Custom visualizations can be provided via the ``visualize`` method
Expand Down Expand Up @@ -78,7 +75,4 @@ def get_analyzer(self, analyzer_id: str) -> DatasetAnalyzer:

__all__ = [
"ManageAnalyzers",
"AnalyzerManager",
"DatasetAnalyzer",
"DefaultAnalyzer",
]
22 changes: 20 additions & 2 deletions src/voxkit/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
"""VoxKit configuration module.
"""This module provides access to configurable info about the application.

This module provides access to application and pipeline configurations.
Configurations
--------------
- App config: application metadata and provenance (``app_config``).
- Pipeline config: pipeline steps and UI wiring (``pipeline_config``).
- Startup config: launch-time constants and defaults (``startup_config``).
- Logging config: rotating file logger setup (``logging_config``).

Only ``app_config`` and ``pipeline_config`` are dynamic post-build; they are
loaded from YAML files under the active profile and can change without a
rebuild. ``startup_config`` and ``logging_config`` are baked in at build time.

Profile System
--------------
Expand All @@ -19,6 +28,11 @@
get_profile_config_path,
resolve_config_file,
)
from voxkit.config.logging_config import (
LOG_FILE,
reset_logging,
setup_logging,
)
from voxkit.config.pipeline_config import (
PipelineConfig,
PipelineStep,
Expand Down Expand Up @@ -55,4 +69,8 @@
"Defaults",
"Mode",
"STARTUP_SCRIPT",
# Logging config
"LOG_FILE",
"setup_logging",
"reset_logging",
]
37 changes: 14 additions & 23 deletions src/voxkit/engines/__init__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
"""VoxKit Engines Module.

Engines are speech toolkit backends that perform alignment, training, and
transcription operations. Each engine provides one or more tools with
configurable settings.
"""Engines are speech toolkit backends. Each engine provides one or more tools
where each tool is a unit of functionality (e.g. alignment, training, transcription).

API
---
- **EngineManager.list_engines**: List registered engine IDs
- **EngineManager.get_engine**: Retrieve engine instance by ID
- **EngineManager.get_tool_providers**: Get engines providing a specific tool type
- **AlignmentEngine**: Abstract base class for all engines
- **ToolType**: Literal type for tool categories ("train", "align", "transcribe")
- **ToolType**: Literal type for compatible tool types

Available Engines
-----------------
**MFAEngine** (``mfa_engine.py``)
**MFAEngine**
Montreal Forced Aligner integration. Provides alignment using pretrained
acoustic models and training via model adaptation.
Tools: ``alignment``, ``training``

**FasterWhisperEngine** (``faster_whisper_engine.py``)
Faster-Whisper integration for transcription. Produces .lab label files
from audio using CTranslate2 backend.
**W2TGEngine**
Wav2TextGrid integration using Wav2Vec 2.0 models.
Tools: ``alignment``, ``training``

**W2TGEngine** (``w2tg_engine.py``) [disabled]
Wav2TextGrid integration using Wav2Vec 2.0 models. Supports both
alignment and from-scratch training.
**FasterWhisperEngine**
Faster-Whisper integration for transcription. Produces .lab transcript files
from audio using CTranslate2 backend.
Tools: ``transcription``

Storage Structure
-----------------
Expand Down Expand Up @@ -60,7 +58,7 @@ class EngineManager:
"""
Manager class for registered engines.

Provides a unified interface to list and retrieve registered alignment engines.
Provides a unified interface to list and retrieve engines.

Methods:
list_engines(): Return a list of registered engine IDs.
Expand Down Expand Up @@ -99,11 +97,4 @@ def get_tool_providers(self, tool: ToolType) -> dict[str, AlignmentEngine]:
faster_whisper = FasterWhisperEngine(id="FASTERWHISPERENGINE")
engines = EngineManager({mfa.id: mfa, faster_whisper.id: faster_whisper, w2tg.id: w2tg})

__all__ = [
"engines",
"EngineManager",
"AlignmentEngine",
"ToolType",
"MFAEngine",
"FasterWhisperEngine",
]
__all__ = ["engines", "ToolType"]
15 changes: 7 additions & 8 deletions src/voxkit/gui/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""VoxKit GUI Module.

PyQt6-based graphical user interface for the VoxKit desktop application.
"""PyQt6-based graphical user interface for interacting with datasets using tools.
Designed for ease of use and extensibility.

API
---
- **AlignmentGUI**: Main application window with toolbar navigation
- **VoxKitGUI**: Main application window with toolbar navigation

Submodules
----------
Expand Down Expand Up @@ -167,13 +166,13 @@
"""


class AlignmentGUI(QMainWindow):
class VoxKitGUI(QMainWindow):
def __init__(
self,
app_config: Optional[AppConfig] = None,
pipeline_config: Optional[PipelineConfig] = None,
):
"""Initialize the AlignmentGUI.
"""Initialize the VoxKitGUI.

Args:
app_config: Application configuration. If None, loads default from config files.
Expand All @@ -186,7 +185,7 @@ def __init__(
self.pipeline_config = pipeline_config or get_pipeline_config()

logger.info(
"AlignmentGUI initialized: app=%s version=%s",
"VoxKitGUI initialized: app=%s version=%s",
self.app_config.app_name,
self.app_config.version,
)
Expand Down Expand Up @@ -450,4 +449,4 @@ def _open_log_viewer(self) -> None:
self._log_viewer.activateWindow()


__all__ = ["AlignmentGUI"]
__all__ = ["VoxKitGUI"]
10 changes: 7 additions & 3 deletions src/voxkit/gui/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""Components Module.

Reusable PyQt6 widgets used throughout the VoxKit application.
"""Reusable PyQt6 widgets taiored functionally and stylistically for the VoxKit application.

API
---
Expand All @@ -10,6 +8,9 @@
- **GripSplitter**: QSplitter with visible grip handle for intuitive resizing
- **HuggingFaceButton**: Branded button with HuggingFace logo
- **LoadingDialog**: Splash screen / loading dialog with animated spinner
- **LogViewerDialog**: Dialog for viewing live application logs
- **QObjectLogHandler**: Qt-aware logging handler that emits records as Qt signals
- **get_gui_log_handler**: Accessor for the singleton GUI log handler
- **ModelSelectionPanel**: Combined engine and model selection panel
- **MultiColumnComboBox**: QComboBox with multi-column dropdown table display
- **OverlayWidget**: Semi-transparent overlay for modal blur effects
Expand All @@ -29,6 +30,7 @@
from .grip_splitter import GripSplitter
from .huggingface_button import HuggingFaceButton
from .loading_dialog import LoadingDialog
from .log_handler import QObjectLogHandler, get_gui_log_handler
from .log_viewer_dialog import LogViewerDialog
from .model_selection_panel import ModelSelectionPanel
from .overlay_effects import OverlayWidget
Expand All @@ -45,5 +47,7 @@
"ModelSelectionPanel",
"MultiColumnComboBox",
"OverlayWidget",
"QObjectLogHandler",
"ToggleSwitch",
"get_gui_log_handler",
]
9 changes: 1 addition & 8 deletions src/voxkit/gui/components/animate_stack.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
"""Animated Stack Module.

QStackedWidget with smooth slide transition animations.

API
---
- **AnimatedStackedWidget**: Stacked widget with animated page transitions
"""
"""QStackedWidget with smooth slide transition animations."""

from PyQt6.QtCore import QEasingCurve, QPoint, QPropertyAnimation
from PyQt6.QtWidgets import QStackedWidget
Expand Down
9 changes: 1 addition & 8 deletions src/voxkit/gui/components/column_dropdown.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
"""Column Dropdown Module.

QComboBox with multi-column table dropdown display.

API
---
- **MultiColumnComboBox**: ComboBox with tabular dropdown (single selection)
"""
"""QComboBox with multi-column table dropdown display."""

import sys

Expand Down
9 changes: 1 addition & 8 deletions src/voxkit/gui/components/csv_viewer_dialog.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
"""CSV Viewer Dialog Module.

Modal dialog for viewing CSV files in a formatted table.

API
---
- **CSVViewerDialog**: Dialog that displays CSV data with blur background effect
"""
"""Modal dialog for viewing CSV files in a formatted table."""

import csv
import os
Expand Down
9 changes: 1 addition & 8 deletions src/voxkit/gui/components/dna_strand.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
"""DNA Strand Widget Module.

Decorative audio waveform visualization for the toolbar.

API
---
- **DNAStrandWidget**: Procedural audio waveform decoration (Wav2Vec homage)
"""
"""Decorative audio waveform visualization for the toolbar."""

import math

Expand Down
Loading
Loading