merge: resolve conflicts with origin/main

- Import run_tts_segment_loop (used in pyqt/conversion.py)
- Use build_tts_context() instead of manual _MergeJob + TTSContext
- Drop unused AudioSink import
This commit is contained in:
Artem Akymenko
2026-07-24 18:34:49 +00:00
11 changed files with 58 additions and 15 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [jborza, jeremiahsb, mohangk] github: [jborza, jeremiahsb, mohangk, k0sm0naft]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
+15 -3
View File
@@ -274,16 +274,28 @@ def _process_regex_sentences(
current_sentence = [] current_sentence = []
word_count = 0 word_count = 0
# Add any remaining tokens as a sentence # Add any remaining tokens as a sentence (split multi-sentence FakeToken)
if current_sentence: if current_sentence:
start_time = current_sentence[0]["start"] start_time = current_sentence[0]["start"]
end_time = current_sentence[-1]["end"] end_time = current_sentence[-1]["end"]
# Simplified text joining logic
sentence_text = "" sentence_text = ""
for t in current_sentence: for t in current_sentence:
sentence_text += t["text"] + (t.get("whitespace") or "") sentence_text += t["text"] + (t.get("whitespace") or "")
subtitle_entries.append((start_time, end_time, sentence_text.strip())) sentence_text = sentence_text.strip()
if len(current_sentence) == 1:
parts = re.split(rf"(?<={separator})\s+", sentence_text)
if len(parts) > 1:
d = end_time - start_time
for i, p in enumerate(parts):
e = end_time if i == len(parts) - 1 else start_time + d * len(p) / len(sentence_text)
subtitle_entries.append((start_time, e, p.strip()))
start_time = e
current_sentence = []
if current_sentence:
subtitle_entries.append((start_time, end_time, sentence_text))
# Fallback for last entry # Fallback for last entry
_apply_fallback_end_time(subtitle_entries, fallback_end_time) _apply_fallback_end_time(subtitle_entries, fallback_end_time)
+1 -1
View File
@@ -19,7 +19,7 @@ def tracked_hf_hub_download(*args, **kwargs):
try: try:
local_kwargs = dict(kwargs) local_kwargs = dict(kwargs)
local_kwargs["local_files_only"] = True local_kwargs["local_files_only"] = True
hf_hub_download(*args, **local_kwargs) return hf_hub_download(*args, **local_kwargs)
except Exception: except Exception:
repo_id = kwargs.get("repo_id", "<unknown repo>") repo_id = kwargs.get("repo_id", "<unknown repo>")
filename = kwargs.get("filename", "<unknown file>") filename = kwargs.get("filename", "<unknown file>")
+2 -1
View File
@@ -1,6 +1,7 @@
import os import os
import time import time
import hashlib # For generating unique cache filenames import hashlib # For generating unique cache filenames
from pathlib import Path
from platformdirs import user_desktop_dir from platformdirs import user_desktop_dir
from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer
from PyQt6.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox from PyQt6.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox
@@ -32,7 +33,7 @@ from abogen.domain.output_paths import (
) )
from abogen.domain.audio_helpers import build_ffmpeg_command, to_float32 from abogen.domain.audio_helpers import build_ffmpeg_command, to_float32
from abogen.domain.audio_sink import open_audio_sink from abogen.domain.audio_sink import open_audio_sink
from abogen.domain.conversion_engine import synthesize_text, SynthParams, SegmentStats, SegmentInfo from abogen.domain.conversion_engine import run_tts_segment_loop, synthesize_text, SynthParams, SegmentStats, SegmentInfo
from abogen.domain.intro_outro import resolve_intro, resolve_outro from abogen.domain.intro_outro import resolve_intro, resolve_outro
from abogen.domain.audio_buffer import ( from abogen.domain.audio_buffer import (
create_silence, create_silence,
+1 -1
View File
@@ -842,7 +842,7 @@ class WordSubstitutionsDialog(QDialog):
self, self,
) )
instructions.setStyleSheet( instructions.setStyleSheet(
"padding: 10px; background-color: #f0f0f0; border-radius: 5px;" f"padding: 10px; background-color: {COLORS['GREY_BACKGROUND']}; border-radius: 5px;"
) )
instructions.setWordWrap(True) instructions.setWordWrap(True)
layout.addWidget(instructions) layout.addWidget(instructions)
+4
View File
@@ -46,6 +46,8 @@ except ImportError:
print("PyQt6 not installed.") print("PyQt6 not installed.")
from abogen.utils import get_resource_path
# Pre-load "libxcb-cursor" on Linux (fixes #101) # Pre-load "libxcb-cursor" on Linux (fixes #101)
if platform.system() == "Linux": if platform.system() == "Linux":
arch = platform.machine().lower() arch = platform.machine().lower()
@@ -118,6 +120,8 @@ def qt_message_handler(mode, context, message):
return # Suppress this specific message return # Suppress this specific message
if "setGrabPopup called with a parent, QtWaylandClient" in message: if "setGrabPopup called with a parent, QtWaylandClient" in message:
return return
if "Failed to register with host portal" in message:
return
if mode == QtMsgType.QtWarningMsg: if mode == QtMsgType.QtWarningMsg:
print(f"Qt Warning: {message}") print(f"Qt Warning: {message}")
+21 -5
View File
@@ -21,6 +21,19 @@ SPACY_MODELS = {
Language.HI: "xx_sent_ud_sm", Language.HI: "xx_sent_ud_sm",
} }
# Kokoro single-letter codes -> Language enum (inverse of pipeline_factory._KOKORO_LANG_MAP)
_KOKORO_TO_LANGUAGE = {
"a": Language.EN_US,
"b": Language.EN_GB,
"e": Language.ES,
"f": Language.FR,
"h": Language.HI,
"i": Language.IT,
"j": Language.JA,
"p": Language.PT_BR,
"z": Language.ZH,
}
def _load_spacy(): def _load_spacy():
"""Lazy load spaCy module.""" """Lazy load spaCy module."""
@@ -61,11 +74,14 @@ def get_spacy_model(lang_code, log_callback=None):
# Normalize to Language enum # Normalize to Language enum
if not isinstance(lang_code, Language): if not isinstance(lang_code, Language):
try: if isinstance(lang_code, str) and lang_code in _KOKORO_TO_LANGUAGE:
lang_code = Language.from_str(lang_code) lang_code = _KOKORO_TO_LANGUAGE[lang_code]
except ValueError: else:
log(f"\nspaCy: Unknown language '{lang_code}'...") try:
return None lang_code = Language.from_str(lang_code)
except ValueError:
log(f"\nspaCy: Unknown language '{lang_code}'...")
return None
# Check if model is cached # Check if model is cached
if lang_code in _nlp_cache: if lang_code in _nlp_cache:
+1 -1
View File
@@ -12,7 +12,7 @@ The loader does NOT:
from __future__ import annotations from __future__ import annotations
import importlib import importlib.util
import re import re
import sys import sys
import types import types
+5 -2
View File
@@ -42,8 +42,11 @@ class PluginManager:
plugins_path = Path(plugins_dir) plugins_path = Path(plugins_dir)
if not plugins_path.exists(): if not plugins_path.exists():
self._loaded = True if plugins_dir == "plugins":
return plugins_path = Path(__file__).resolve().parent.parent.parent / "plugins"
if not plugins_path.exists():
self._loaded = True
return
for entry in plugins_path.iterdir(): for entry in plugins_path.iterdir():
if entry.is_dir() and (entry / "__init__.py").exists(): if entry.is_dir() and (entry / "__init__.py").exists():
+6
View File
@@ -179,6 +179,12 @@ class Pipeline:
yield Segment(graphemes=text, audio=audio_array) yield Segment(graphemes=text, audio=audio_array)
def load_single_voice(self, voice_name: str) -> Any:
engine_pipeline = getattr(self._engine, '_pipeline', None)
if engine_pipeline is not None and hasattr(engine_pipeline, 'load_single_voice'):
return engine_pipeline.load_single_voice(voice_name)
raise AttributeError(f"load_single_voice not available on {type(self._engine).__name__}")
def dispose(self) -> None: def dispose(self) -> None:
if self._session is not None: if self._session is not None:
try: try:
+1
View File
@@ -7,6 +7,7 @@ from flask import Blueprint, current_app, render_template, request, redirect, ur
from flask.typing import ResponseReturnValue from flask.typing import ResponseReturnValue
from abogen.webui.routes.utils.settings import ( from abogen.webui.routes.utils.settings import (
load_integration_settings,
load_settings, load_settings,
save_settings, save_settings,
SAVE_MODE_LABELS, SAVE_MODE_LABELS,