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
github: [jborza, jeremiahsb, mohangk]
github: [jborza, jeremiahsb, mohangk, k0sm0naft]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
+15 -3
View File
@@ -274,16 +274,28 @@ def _process_regex_sentences(
current_sentence = []
word_count = 0
# Add any remaining tokens as a sentence
# Add any remaining tokens as a sentence (split multi-sentence FakeToken)
if current_sentence:
start_time = current_sentence[0]["start"]
end_time = current_sentence[-1]["end"]
# Simplified text joining logic
sentence_text = ""
for t in current_sentence:
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
_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:
local_kwargs = dict(kwargs)
local_kwargs["local_files_only"] = True
hf_hub_download(*args, **local_kwargs)
return hf_hub_download(*args, **local_kwargs)
except Exception:
repo_id = kwargs.get("repo_id", "<unknown repo>")
filename = kwargs.get("filename", "<unknown file>")
+2 -1
View File
@@ -1,6 +1,7 @@
import os
import time
import hashlib # For generating unique cache filenames
from pathlib import Path
from platformdirs import user_desktop_dir
from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer
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_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.audio_buffer import (
create_silence,
+1 -1
View File
@@ -842,7 +842,7 @@ class WordSubstitutionsDialog(QDialog):
self,
)
instructions.setStyleSheet(
"padding: 10px; background-color: #f0f0f0; border-radius: 5px;"
f"padding: 10px; background-color: {COLORS['GREY_BACKGROUND']}; border-radius: 5px;"
)
instructions.setWordWrap(True)
layout.addWidget(instructions)
+4
View File
@@ -46,6 +46,8 @@ except ImportError:
print("PyQt6 not installed.")
from abogen.utils import get_resource_path
# Pre-load "libxcb-cursor" on Linux (fixes #101)
if platform.system() == "Linux":
arch = platform.machine().lower()
@@ -118,6 +120,8 @@ def qt_message_handler(mode, context, message):
return # Suppress this specific message
if "setGrabPopup called with a parent, QtWaylandClient" in message:
return
if "Failed to register with host portal" in message:
return
if mode == QtMsgType.QtWarningMsg:
print(f"Qt Warning: {message}")
+21 -5
View File
@@ -21,6 +21,19 @@ SPACY_MODELS = {
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():
"""Lazy load spaCy module."""
@@ -61,11 +74,14 @@ def get_spacy_model(lang_code, log_callback=None):
# Normalize to Language enum
if not isinstance(lang_code, Language):
try:
lang_code = Language.from_str(lang_code)
except ValueError:
log(f"\nspaCy: Unknown language '{lang_code}'...")
return None
if isinstance(lang_code, str) and lang_code in _KOKORO_TO_LANGUAGE:
lang_code = _KOKORO_TO_LANGUAGE[lang_code]
else:
try:
lang_code = Language.from_str(lang_code)
except ValueError:
log(f"\nspaCy: Unknown language '{lang_code}'...")
return None
# Check if model is cached
if lang_code in _nlp_cache:
+1 -1
View File
@@ -12,7 +12,7 @@ The loader does NOT:
from __future__ import annotations
import importlib
import importlib.util
import re
import sys
import types
+5 -2
View File
@@ -42,8 +42,11 @@ class PluginManager:
plugins_path = Path(plugins_dir)
if not plugins_path.exists():
self._loaded = True
return
if plugins_dir == "plugins":
plugins_path = Path(__file__).resolve().parent.parent.parent / "plugins"
if not plugins_path.exists():
self._loaded = True
return
for entry in plugins_path.iterdir():
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)
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:
if self._session is not None:
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 abogen.webui.routes.utils.settings import (
load_integration_settings,
load_settings,
save_settings,
SAVE_MODE_LABELS,