mirror of
https://github.com/denizsafak/abogen.git
synced 2026-09-20 11:40:57 +02:00
Compare commits
11
Commits
fcec4e9fe5
...
6274a02d5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6274a02d5e | ||
|
|
342ea0dfac | ||
|
|
27f88b759d | ||
|
|
dbcbb1c8a9 | ||
|
|
bd99ee1ba1 | ||
|
|
d5cddb9749 | ||
|
|
ec55918b04 | ||
|
|
14913b45e9 | ||
|
|
a0fdabd81f | ||
|
|
473631b84e | ||
|
|
0f5003dfdd |
@@ -280,16 +280,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)
|
||||||
|
|||||||
@@ -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,6 +2,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
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
|
||||||
@@ -36,7 +37,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 AudioSink, open_audio_sink
|
from abogen.domain.audio_sink import AudioSink, 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,
|
||||||
@@ -550,10 +551,14 @@ class ConversionThread(QThread):
|
|||||||
|
|
||||||
# --- Compile normalization rules (heteronym + pronunciation) ---
|
# --- Compile normalization rules (heteronym + pronunciation) ---
|
||||||
from abogen.domain.normalization import TTSContext
|
from abogen.domain.normalization import TTSContext
|
||||||
pronunciation_overrides = merge_pronunciation_overrides(
|
|
||||||
getattr(self, "pronunciation_overrides", None),
|
class _MergeJob:
|
||||||
getattr(self, "manual_overrides", None),
|
pronunciation_overrides = getattr(self, "pronunciation_overrides", None)
|
||||||
)
|
manual_overrides = getattr(self, "manual_overrides", None)
|
||||||
|
heteronym_overrides = getattr(self, "heteronym_overrides", None)
|
||||||
|
language = self.lang_code
|
||||||
|
|
||||||
|
pronunciation_overrides = merge_pronunciation_overrides(_MergeJob())
|
||||||
self._tts_context = TTSContext(
|
self._tts_context = TTSContext(
|
||||||
split_pattern=self.split_pattern,
|
split_pattern=self.split_pattern,
|
||||||
pronunciation_rules=compile_pronunciation_rules(pronunciation_overrides),
|
pronunciation_rules=compile_pronunciation_rules(pronunciation_overrides),
|
||||||
|
|||||||
+1
-1
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user