Skip to content
Merged
36 changes: 8 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,17 @@
</p>

<p align="center">
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/releases/latest">
<img src="https://img.shields.io/github/v/release/BrainBehaviorAnalyticsLab/PyPLLR_GUI?label=Release&style=flat-square" alt="Release">
</a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/releases">
<img src="https://img.shields.io/github/downloads/BrainBehaviorAnalyticsLab/PyPLLR_GUI/total?style=flat-square" alt="Downloads">
</a>
<img src="./assets/coverage.svg" alt="Coverage">
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/releases/latest"><img src="https://img.shields.io/github/v/release/BrainBehaviorAnalyticsLab/PyPLLR_GUI?label=Release&style=flat-square" alt="Release"></a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/releases"><img src="https://img.shields.io/github/downloads/BrainBehaviorAnalyticsLab/PyPLLR_GUI/total?style=flat-square" alt="Downloads"></a>
<a href="https://github.com/WISCLab/shred-guard"><img src="https://img.shields.io/badge/ShredGuard-ON-06B6D4?logo=git&logoColor=white&style=flat-square" alt="ShredGuard"></a>
<a href="https://voxkit.atlassian.net/jira/software/projects/VOX/boards/2/"><img src="https://img.shields.io/badge/Project-Jira-0052CC?logo=jira&style=flat-square" alt="Jira"></a>
</p>

<p align="center">
<a href="https://github.com/WISCLab/shred-guard">
<img src="https://img.shields.io/badge/SHREDGUARD-Configured-06B6D4?style=for-the-badge&logo=git&logoColor=white" alt="ShredGuard">
</a>
</p>

<p align="center">
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/tests-ubuntu.yml">
<img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/tests-ubuntu.yml?branch=main&label=Ubuntu&logo=ubuntu&style=flat-square" alt="Ubuntu Tests">
</a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/tests-macos.yml">
<img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/tests-macos.yml?branch=main&label=macOS&logo=apple&style=flat-square" alt="macOS Tests">
</a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/tests-windows.yml">
<img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/tests-windows.yml?branch=main&label=Windows&logo=windows&style=flat-square" alt="Windows Tests">
</a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/code-quality.yml">
<img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/code-quality.yml?branch=main&label=Code%20Quality&style=flat-square" alt="Code Quality">
</a>
<a href="https://voxkit.atlassian.net/jira/software/projects/VOX/boards/2/">
<img src="https://img.shields.io/badge/Project-Jira-0052CC?logo=jira&style=flat-square" alt="Jira">
</a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/tests-ubuntu.yml"><img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/tests-ubuntu.yml?branch=main&label=Ubuntu&logo=ubuntu&style=flat-square" alt="Ubuntu Tests"></a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/tests-macos.yml"><img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/tests-macos.yml?branch=main&label=macOS&logo=apple&style=flat-square" alt="macOS Tests"></a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/tests-windows.yml"><img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/tests-windows.yml?branch=main&label=Windows&logo=windows&style=flat-square" alt="Windows Tests"></a>
<a href="https://github.com/BrainBehaviorAnalyticsLab/PyPLLR_GUI/actions/workflows/code-quality.yml"><img src="https://img.shields.io/github/actions/workflow/status/BrainBehaviorAnalyticsLab/PyPLLR_GUI/code-quality.yml?branch=main&label=Code%20Quality&style=flat-square" alt="Code Quality"></a>
</p>

> [!IMPORTANT]
Expand Down
4 changes: 3 additions & 1 deletion src/voxkit/analyzers/audio_format_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from pathlib import Path
from typing import Any, Dict, List

from voxkit.storage.constants import SUPERSET_AUDIO_EXTENSIONS

from .base import DatasetAnalyzer

logger = logging.getLogger(__name__)
Expand All @@ -38,7 +40,7 @@ def analyze(self, dataset_path: str) -> List[Dict[str, Any]]:
import torchaudio

results = []
audio_extensions = {".wav", ".flac", ".mp3", ".ogg", ".m4a"}
audio_extensions = SUPERSET_AUDIO_EXTENSIONS

try:
for entry in os.scandir(dataset_path):
Expand Down
4 changes: 3 additions & 1 deletion src/voxkit/analyzers/clip_duration_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from pathlib import Path
from typing import Any, Dict, List

from voxkit.storage.constants import SUPERSET_AUDIO_EXTENSIONS

from .base import DatasetAnalyzer

logger = logging.getLogger(__name__)
Expand All @@ -37,7 +39,7 @@ def analyze(self, dataset_path: str) -> List[Dict[str, Any]]:
import torchaudio

results = []
audio_extensions = {".wav", ".flac", ".mp3", ".ogg", ".m4a"}
audio_extensions = SUPERSET_AUDIO_EXTENSIONS

try:
for entry in os.scandir(dataset_path):
Expand Down
4 changes: 3 additions & 1 deletion src/voxkit/analyzers/default_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from pathlib import Path
from typing import Any, Dict, List

from voxkit.storage.constants import SUPERSET_AUDIO_EXTENSIONS

from .base import DatasetAnalyzer


Expand All @@ -43,7 +45,7 @@ def analyze(self, dataset_path: str) -> List[Dict[str, Any]]:
``audio_file_count``.
"""
results = []
audio_extensions = {".wav", ".flac", ".mp3", ".ogg", ".m4a"}
audio_extensions = SUPERSET_AUDIO_EXTENSIONS

try:
for entry in os.scandir(dataset_path):
Expand Down
4 changes: 2 additions & 2 deletions src/voxkit/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
get_profile_config_path,
resolve_config_file,
)
from voxkit.config.constants import DEFAULT_HELP_URL
from voxkit.config.logging_config import (
LOG_FILE,
reset_logging,
Expand All @@ -40,7 +41,6 @@
get_pipeline_config,
)
from voxkit.config.startup_config import (
HELP_URL,
STARTUP_SCRIPT,
AppName,
Defaults,
Expand All @@ -63,7 +63,7 @@
"UIConfig",
"get_pipeline_config",
# Startup config
"HELP_URL",
"DEFAULT_HELP_URL",
"AppName",
"Dimensions",
"Defaults",
Expand Down
28 changes: 6 additions & 22 deletions src/voxkit/config/app_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import yaml

from voxkit.config.constants import DEFAULT_HELP_URL


def get_config_root() -> Path:
"""Get the path to the config root directory.
Expand Down Expand Up @@ -96,30 +98,12 @@ def resolve_config_file(filename: str) -> Path:
if default_path.exists():
return default_path

# Fall back to legacy location (config root)
legacy_path = config_root / filename
if legacy_path.exists():
return legacy_path

# Throw error if not found in either location
raise FileNotFoundError(
f"Config file '{filename}' not found in profile '{profile}', "
f"default profile, or config root"
f"Config file '{filename}' not found in profile '{profile}' or default profile"
)


# Legacy alias for backwards compatibility
def get_config_path() -> Path:
"""Get the path to the config directory.

Deprecated: Use get_profile_config_path() for profile-aware loading,
or get_config_root() for the config root directory.

Returns:
Path to the active profile's config directory
"""
return get_profile_config_path()


@dataclass
class AppConfig:
"""Application configuration data class."""
Expand All @@ -128,7 +112,7 @@ class AppConfig:
version: str
description: str
introduction: str
help_url: str = "https://voxkit-web.vercel.app/help"
help_url: str | None = None
release_date: Optional[str] = None
release_notes: Optional[str] = None
log_max_bytes: int = 5 * 1024 * 1024
Expand Down Expand Up @@ -164,7 +148,7 @@ def from_yaml(cls, config_path: Path) -> "AppConfig":
version=version,
description=data.get("description", ""),
introduction=data.get("introduction", ""),
help_url=data.get("help_url", "https://voxkit-web.vercel.app/help"),
help_url=data.get("help_url", DEFAULT_HELP_URL),
release_date=data.get("release_date"),
release_notes=data.get("release_notes"),
log_max_bytes=int(data.get("log_max_bytes", 5 * 1024 * 1024)),
Expand Down
8 changes: 8 additions & 0 deletions src/voxkit/config/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Constants relevant to configuration and setup.

Constants
---------
- **DEFAULT_HELP_URL**: URL for user help documentation
"""

DEFAULT_HELP_URL = "https://voxkit-web.vercel.app/help"
3 changes: 1 addition & 2 deletions src/voxkit/config/startup_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from voxkit.services.mfa import download_acoustic_model
from voxkit.storage import models
from voxkit.storage.config import MODELS_ROOT
from voxkit.storage.constants import MODELS_ROOT
from voxkit.storage.models import download_and_copy_huggingface_model
from voxkit.storage.utils import get_storage_root

Expand All @@ -18,7 +18,6 @@
}

Mode = Literal["MFAENGINE", "W2TGENGINE"]
HELP_URL = "https://voxkit-web.vercel.app/help"


def startup_routine():
Expand Down
9 changes: 5 additions & 4 deletions src/voxkit/engines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- **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
- **ToolType**: Literal type for compatible tool types
- **AVAILABLE_TOOLS**: Literal type for compatible tool types

Available Engines
-----------------
Expand Down Expand Up @@ -48,7 +48,8 @@

from typing import List

from .base import AlignmentEngine, ToolType
from .base import AlignmentEngine
from .constants import AVAILABLE_TOOLS
from .faster_whisper_engine import FasterWhisperEngine
from .mfa_engine import MFAEngine
from .w2tg_engine import W2TGEngine
Expand Down Expand Up @@ -82,7 +83,7 @@ def get_engine(self, engine_id: str) -> AlignmentEngine:
except KeyError:
raise ValueError(f"No engine with id: {engine_id}")

def get_tool_providers(self, tool: ToolType) -> dict[str, AlignmentEngine]:
def get_tool_providers(self, tool: AVAILABLE_TOOLS) -> dict[str, AlignmentEngine]:
"""Return a list of engines that provide the specified tool type."""
engines = {}
for _, engine in self._engines.items():
Expand All @@ -97,4 +98,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", "ToolType"]
__all__ = ["engines", "AVAILABLE_TOOLS"]
17 changes: 8 additions & 9 deletions src/voxkit/engines/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ def align(self, dataset_id: str, model_id: str) -> None:
import json
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Literal
from typing import Any

from voxkit.engines.constants import AVAILABLE_TOOLS
from voxkit.storage.utils import get_storage_root

"""
Expand All @@ -42,18 +43,16 @@ def align(self, dataset_id: str, model_id: str) -> None:
has its own settings that are stored in a JSON file.
"""

ToolType = Literal["train", "align", "transcribe"]


class AlignmentEngine(ABC):
"""
Abstract base class for alignment engines.

Subclasses must implement at least one ToolType operation and provide
Subclasses must implement at least one AVAILABLE_TOOLS operation and provide
specific validation criteria.

Attributes:
settings_configurations (dict[ToolType, Any]): Mapping of
settings_configurations (dict[AVAILABLE_TOOLS, Any]): Mapping of
tool type names ("train"/"align") to their store configuration.
reference_url (str | None): Optional reference URL for the engine.
description (str | None): Human-readable description of the engine.
Expand All @@ -63,7 +62,7 @@ class AlignmentEngine(ABC):

def __init__(
self,
settings_configurations: dict[ToolType, Any],
settings_configurations: dict[AVAILABLE_TOOLS, Any],
reference_url: str | None = None,
description: str | None = None,
human_readable_name: str | None = None,
Expand Down Expand Up @@ -218,7 +217,7 @@ def _get_default_settings(self, cfg: Any) -> dict:
"""
return {field.name: field.default_value for field in (cfg.fields or [])}

def get_settings(self, tool_type: ToolType) -> dict:
def get_settings(self, tool_type: AVAILABLE_TOOLS) -> dict:
"""
Load and validate settings for a specific tool.

Expand Down Expand Up @@ -269,7 +268,7 @@ def get_settings(self, tool_type: ToolType) -> dict:

return settings

def get_settings_config(self, tool_type: ToolType) -> Any:
def get_settings_config(self, tool_type: AVAILABLE_TOOLS) -> Any:
"""
Return the :class:`Any` for a tool type.

Expand All @@ -287,7 +286,7 @@ def get_settings_config(self, tool_type: ToolType) -> Any:
raise ValueError(f"No settings configuration found for tool type: {tool_type}")
return config

def has_tool(self, tool_type: ToolType) -> bool:
def has_tool(self, tool_type: AVAILABLE_TOOLS) -> bool:
"""Check if the engine has a tool of the specified type."""
return tool_type in self.settings_configurations

Expand Down
4 changes: 4 additions & 0 deletions src/voxkit/engines/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from typing import Literal

# New engines can implement these tools or a subset of them
AVAILABLE_TOOLS = Literal["train", "align", "transcribe"]
Comment on lines +1 to +4
3 changes: 2 additions & 1 deletion src/voxkit/gui/pages/pipeline/viewer_stacker.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from voxkit.gui.pages.pipeline.base_stacker import BaseStacker
from voxkit.gui.styles import Buttons, Colors, Containers, Labels
from voxkit.storage import alignments, datasets
from voxkit.storage.constants import SUPERSET_AUDIO_EXTENSIONS

if TYPE_CHECKING:
from PyQt6.QtMultimedia import QAudioOutput, QMediaPlayer
Expand All @@ -52,7 +53,7 @@
MULTIMEDIA_AVAILABLE = False


_AUDIO_EXTENSIONS = {".wav", ".flac", ".mp3", ".ogg", ".m4a"}
_AUDIO_EXTENSIONS = SUPERSET_AUDIO_EXTENSIONS
_SILENCE_LABELS = {"", "sp", "sil", "<eps>", "spn"}

# ---------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions src/voxkit/storage/alignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from pathlib import Path
from typing import List, Literal, Tuple, TypedDict

from .config import ALIGNMENTS_ROOT
from .constants import ALIGNMENTS_ROOT, SUPERSET_AUDIO_EXTENSIONS
from .datasets import _get_dataset_root, get_dataset_metadata
from .models import ModelMetadata, get_model_metadata
from .utils import generate_unique_id, readable_from_unique_id
Expand Down Expand Up @@ -192,7 +192,7 @@ def create_alignment(
return False, f"Failed to create alignment metadata: {str(e)}"


_AUDIO_EXTS = (".wav", ".flac", ".mp3", ".ogg", ".m4a")
_AUDIO_EXTS = SUPERSET_AUDIO_EXTENSIONS


def validate_hand_alignments(dataset_path: Path, hand_path: Path) -> Tuple[bool, str]:
Expand Down
20 changes: 0 additions & 20 deletions src/voxkit/storage/config.py

This file was deleted.

21 changes: 21 additions & 0 deletions src/voxkit/storage/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""This module contains constants for the VoxKit storage layer.

Constants
---------
- **STORAGE_ROOT**: Root directory for all VoxKit storage (~/.voxkit)
- **MODELS_ROOT**: Subdirectory for model storage relative to engine directory
- **DATASETS_ROOT**: Subdirectory for dataset storage relative to STORAGE_ROOT
- **ALIGNMENTS_ROOT**: Subdirectory for alignments relative to dataset directory
- **SUPERSET_AUDIO_EXTENSIONS**: Comprehensive set of audio file extensions

Notes
-----
- STORAGE_ROOT uses tilde (~) notation to reference the user's home directory
- All paths are relative to appropriate parent directories in the hierarchy
"""

STORAGE_ROOT: str = "~/.voxkit" # Root directory for all storage
MODELS_ROOT: str = "train" # Path from STORAGE_ROOT to models
DATASETS_ROOT: str = "datasets" # Path from STORAGE_ROOT to datasets
ALIGNMENTS_ROOT: str = "alignments" # Path from STORAGE_ROOT/DATASETS_ROOT to alignments
SUPERSET_AUDIO_EXTENSIONS: frozenset[str] = frozenset({".wav", ".flac", ".mp3", ".ogg", ".m4a"})
Loading