mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Refactor code structure to move web-related components to the webui module and update references accordingly
This commit is contained in:
@@ -229,7 +229,7 @@ python -m pytest
|
|||||||
Unit tests cover the queue service, web routes, and conversion pipeline helpers. Contributions that add features should include new tests whenever practical.
|
Unit tests cover the queue service, web routes, and conversion pipeline helpers. Contributions that add features should include new tests whenever practical.
|
||||||
|
|
||||||
## Upgrading from the desktop GUI
|
## Upgrading from the desktop GUI
|
||||||
The legacy PyQt5 interface is no longer packaged. Existing scripts that call `abogen.main` should switch to the new web entry point (`abogen.web.app:main`). The new experience works headlessly, plays nicely in Docker, and exposes JSON APIs for automation.
|
The legacy PyQt5 interface is no longer packaged. Existing scripts that call `abogen.main` should switch to the new web entry point (`abogen.webui.app:main`). The new experience works headlessly, plays nicely in Docker, and exposes JSON APIs for automation.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
- Conversion jobs stay pending → ensure the background worker has write access to the upload/output directories.
|
- Conversion jobs stay pending → ensure the background worker has write access to the upload/output directories.
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ import signal
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from abogen.utils import load_config, prevent_sleep_end
|
from abogen.utils import load_config, prevent_sleep_end
|
||||||
from abogen.web.app import main as _run_web_ui
|
from abogen.webui.app import main as _run_web_ui
|
||||||
|
|
||||||
# Configure Hugging Face Hub behaviour (mirrors legacy GUI defaults).
|
# Configure Hugging Face Hub behaviour (mirrors legacy GUI defaults).
|
||||||
os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1")
|
os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1")
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ def create_app(config: Optional[dict[str, Any]] = None) -> Flask:
|
|||||||
)
|
)
|
||||||
app.extensions["conversion_service"] = service
|
app.extensions["conversion_service"] = service
|
||||||
|
|
||||||
from abogen.web.routes import (
|
from abogen.webui.routes import (
|
||||||
main_bp,
|
main_bp,
|
||||||
jobs_bp,
|
jobs_bp,
|
||||||
settings_bp,
|
settings_bp,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from abogen.kokoro_text_normalization import normalize_for_pipeline
|
|||||||
from abogen.normalization_settings import build_apostrophe_config
|
from abogen.normalization_settings import build_apostrophe_config
|
||||||
from abogen.text_extractor import extract_from_path
|
from abogen.text_extractor import extract_from_path
|
||||||
from abogen.voice_cache import ensure_voice_assets
|
from abogen.voice_cache import ensure_voice_assets
|
||||||
from abogen.web.conversion_runner import SAMPLE_RATE, SPLIT_PATTERN, _select_device, _to_float32, _resolve_voice, _spec_to_voice_ids
|
from abogen.webui.conversion_runner import SAMPLE_RATE, SPLIT_PATTERN, _select_device, _to_float32, _resolve_voice, _spec_to_voice_ids
|
||||||
from abogen.utils import load_numpy_kpipeline
|
from abogen.utils import load_numpy_kpipeline
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ def _resolve_voice_setting(value: str) -> tuple[str, Optional[str], Optional[str
|
|||||||
Returns (resolved_voice_spec, profile_name, profile_language).
|
Returns (resolved_voice_spec, profile_name, profile_language).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from abogen.web.routes.utils.voice import resolve_voice_setting
|
from abogen.webui.routes.utils.voice import resolve_voice_setting
|
||||||
|
|
||||||
return resolve_voice_setting(value)
|
return resolve_voice_setting(value)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from abogen.web.routes.main import main_bp
|
from abogen.webui.routes.main import main_bp
|
||||||
from abogen.web.routes.jobs import jobs_bp
|
from abogen.webui.routes.jobs import jobs_bp
|
||||||
from abogen.web.routes.settings import settings_bp
|
from abogen.webui.routes.settings import settings_bp
|
||||||
from abogen.web.routes.voices import voices_bp
|
from abogen.webui.routes.voices import voices_bp
|
||||||
from abogen.web.routes.entities import entities_bp
|
from abogen.webui.routes.entities import entities_bp
|
||||||
from abogen.web.routes.books import books_bp
|
from abogen.webui.routes.books import books_bp
|
||||||
from abogen.web.routes.api import api_bp
|
from abogen.webui.routes.api import api_bp
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"main_bp",
|
"main_bp",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
|||||||
from flask import Blueprint, request, jsonify, send_file, url_for, current_app
|
from flask import Blueprint, request, jsonify, send_file, url_for, current_app
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.routes.utils.settings import (
|
from abogen.webui.routes.utils.settings import (
|
||||||
load_settings,
|
load_settings,
|
||||||
load_integration_settings,
|
load_integration_settings,
|
||||||
coerce_float,
|
coerce_float,
|
||||||
@@ -22,9 +22,9 @@ from abogen.voice_profiles import (
|
|||||||
export_profiles_payload,
|
export_profiles_payload,
|
||||||
normalize_profile_entry,
|
normalize_profile_entry,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.common import split_profile_spec
|
from abogen.webui.routes.utils.common import split_profile_spec
|
||||||
from abogen.web.routes.utils.preview import synthesize_preview, generate_preview_audio
|
from abogen.webui.routes.utils.preview import synthesize_preview, generate_preview_audio
|
||||||
from abogen.web.routes.utils.voice import formula_from_profile
|
from abogen.webui.routes.utils.voice import formula_from_profile
|
||||||
from abogen.normalization_settings import (
|
from abogen.normalization_settings import (
|
||||||
build_llm_configuration,
|
build_llm_configuration,
|
||||||
build_apostrophe_config,
|
build_apostrophe_config,
|
||||||
@@ -37,8 +37,8 @@ from abogen.integrations.calibre_opds import (
|
|||||||
CalibreOPDSClient,
|
CalibreOPDSClient,
|
||||||
CalibreOPDSError,
|
CalibreOPDSError,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.service import get_service
|
from abogen.webui.routes.utils.service import get_service
|
||||||
from abogen.web.routes.utils.form import build_pending_job_from_extraction
|
from abogen.webui.routes.utils.form import build_pending_job_from_extraction
|
||||||
from abogen.text_extractor import extract_from_path
|
from abogen.text_extractor import extract_from_path
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ from typing import Any, Dict
|
|||||||
from flask import Blueprint, render_template
|
from flask import Blueprint, render_template
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.routes.utils.settings import (
|
from abogen.webui.routes.utils.settings import (
|
||||||
load_settings,
|
load_settings,
|
||||||
load_integration_settings,
|
load_integration_settings,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.voice import template_options
|
from abogen.webui.routes.utils.voice import template_options
|
||||||
|
|
||||||
books_bp = Blueprint("books", __name__)
|
books_bp = Blueprint("books", __name__)
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ from typing import Mapping
|
|||||||
from flask import Blueprint, request, jsonify, abort, render_template, redirect, url_for
|
from flask import Blueprint, request, jsonify, abort, render_template, redirect, url_for
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.routes.utils.service import require_pending_job, get_service
|
from abogen.webui.routes.utils.service import require_pending_job, get_service
|
||||||
from abogen.web.routes.utils.entity import (
|
from abogen.webui.routes.utils.entity import (
|
||||||
refresh_entity_summary,
|
refresh_entity_summary,
|
||||||
pending_entities_payload,
|
pending_entities_payload,
|
||||||
upsert_manual_override,
|
upsert_manual_override,
|
||||||
delete_manual_override,
|
delete_manual_override,
|
||||||
search_manual_override_candidates,
|
search_manual_override_candidates,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.settings import coerce_int, load_settings
|
from abogen.webui.routes.utils.settings import coerce_int, load_settings
|
||||||
from abogen.web.routes.utils.voice import template_options
|
from abogen.webui.routes.utils.voice import template_options
|
||||||
from abogen.pronunciation_store import (
|
from abogen.pronunciation_store import (
|
||||||
delete_override as delete_pronunciation_override,
|
delete_override as delete_pronunciation_override,
|
||||||
save_override as save_pronunciation_override,
|
save_override as save_pronunciation_override,
|
||||||
|
|||||||
@@ -6,25 +6,25 @@ from typing import Any, Dict, Optional
|
|||||||
from flask import Blueprint, Response, abort, redirect, render_template, request, url_for, send_file
|
from flask import Blueprint, Response, abort, redirect, render_template, request, url_for, send_file
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.service import (
|
from abogen.webui.service import (
|
||||||
JobStatus,
|
JobStatus,
|
||||||
load_audiobookshelf_chapters,
|
load_audiobookshelf_chapters,
|
||||||
build_audiobookshelf_metadata,
|
build_audiobookshelf_metadata,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.service import get_service
|
from abogen.webui.routes.utils.service import get_service
|
||||||
from abogen.web.routes.utils.form import render_jobs_panel
|
from abogen.webui.routes.utils.form import render_jobs_panel
|
||||||
from abogen.web.routes.utils.voice import template_options
|
from abogen.webui.routes.utils.voice import template_options
|
||||||
from abogen.web.routes.utils.epub import (
|
from abogen.webui.routes.utils.epub import (
|
||||||
job_download_flags,
|
job_download_flags,
|
||||||
locate_job_epub,
|
locate_job_epub,
|
||||||
locate_job_audio,
|
locate_job_audio,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.settings import (
|
from abogen.webui.routes.utils.settings import (
|
||||||
stored_integration_config,
|
stored_integration_config,
|
||||||
build_audiobookshelf_config,
|
build_audiobookshelf_config,
|
||||||
coerce_bool,
|
coerce_bool,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.common import existing_paths
|
from abogen.webui.routes.utils.common import existing_paths
|
||||||
from abogen.integrations.audiobookshelf import AudiobookshelfClient, AudiobookshelfUploadError
|
from abogen.integrations.audiobookshelf import AudiobookshelfClient, AudiobookshelfUploadError
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ from typing import Any, Dict, Optional, cast
|
|||||||
from flask import Blueprint, redirect, render_template, request, url_for, jsonify, current_app
|
from flask import Blueprint, redirect, render_template, request, url_for, jsonify, current_app
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from abogen.web.service import PendingJob, JobStatus
|
from abogen.webui.service import PendingJob, JobStatus
|
||||||
from abogen.web.routes.utils.service import get_service, remove_pending_job, submit_job
|
from abogen.webui.routes.utils.service import get_service, remove_pending_job, submit_job
|
||||||
from abogen.web.routes.utils.settings import load_settings
|
from abogen.webui.routes.utils.settings import load_settings
|
||||||
from abogen.web.routes.utils.voice import template_options
|
from abogen.webui.routes.utils.voice import template_options
|
||||||
from abogen.web.routes.utils.form import (
|
from abogen.webui.routes.utils.form import (
|
||||||
normalize_wizard_step,
|
normalize_wizard_step,
|
||||||
wants_wizard_json,
|
wants_wizard_json,
|
||||||
render_wizard_partial,
|
render_wizard_partial,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from typing import Any
|
|||||||
from flask import Blueprint, current_app, render_template, request, redirect, url_for, flash, send_file, abort
|
from flask import Blueprint, current_app, render_template, request, redirect, url_for, flash, send_file, abort
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.routes.utils.settings import (
|
from abogen.webui.routes.utils.settings import (
|
||||||
load_settings,
|
load_settings,
|
||||||
load_integration_settings,
|
load_integration_settings,
|
||||||
save_settings,
|
save_settings,
|
||||||
@@ -19,8 +19,8 @@ from abogen.web.routes.utils.settings import (
|
|||||||
_NORMALIZATION_STRING_KEYS,
|
_NORMALIZATION_STRING_KEYS,
|
||||||
_DEFAULT_ANALYSIS_THRESHOLD,
|
_DEFAULT_ANALYSIS_THRESHOLD,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.voice import template_options
|
from abogen.webui.routes.utils.voice import template_options
|
||||||
from abogen.web.debug_tts_runner import run_debug_tts_wavs
|
from abogen.webui.debug_tts_runner import run_debug_tts_wavs
|
||||||
from abogen.debug_tts_samples import DEBUG_TTS_SAMPLES
|
from abogen.debug_tts_samples import DEBUG_TTS_SAMPLES
|
||||||
from abogen.utils import get_user_output_path, load_config
|
from abogen.utils import get_user_output_path, load_config
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import time
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict, Iterable, List, Mapping, Optional
|
from typing import Any, Dict, Iterable, List, Mapping, Optional
|
||||||
|
|
||||||
from abogen.web.service import PendingJob
|
from abogen.webui.service import PendingJob
|
||||||
from abogen.entity_analysis import (
|
from abogen.entity_analysis import (
|
||||||
extract_entities,
|
extract_entities,
|
||||||
merge_override,
|
merge_override,
|
||||||
@@ -16,7 +16,7 @@ from abogen.pronunciation_store import (
|
|||||||
save_override as save_pronunciation_override,
|
save_override as save_pronunciation_override,
|
||||||
search_overrides as search_pronunciation_overrides,
|
search_overrides as search_pronunciation_overrides,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.settings import load_settings
|
from abogen.webui.routes.utils.settings import load_settings
|
||||||
from abogen.heteronym_overrides import extract_heteronym_overrides
|
from abogen.heteronym_overrides import extract_heteronym_overrides
|
||||||
|
|
||||||
def collect_pronunciation_overrides(pending: PendingJob) -> List[Dict[str, Any]]:
|
def collect_pronunciation_overrides(pending: PendingJob) -> List[Dict[str, Any]]:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
|||||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Set, Tuple
|
from typing import Any, Dict, Iterable, List, Mapping, Optional, Set, Tuple
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from abogen.web.service import Job, JobStatus
|
from abogen.webui.service import Job, JobStatus
|
||||||
|
|
||||||
def _coerce_path(value: Any) -> Optional[Path]:
|
def _coerce_path(value: Any) -> Optional[Path]:
|
||||||
if isinstance(value, Path):
|
if isinstance(value, Path):
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, cast
|
|||||||
from flask import request, render_template, jsonify
|
from flask import request, render_template, jsonify
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.service import PendingJob, JobStatus
|
from abogen.webui.service import PendingJob, JobStatus
|
||||||
from abogen.web.routes.utils.service import get_service
|
from abogen.webui.routes.utils.service import get_service
|
||||||
from abogen.web.routes.utils.settings import (
|
from abogen.webui.routes.utils.settings import (
|
||||||
load_settings,
|
load_settings,
|
||||||
coerce_bool,
|
coerce_bool,
|
||||||
coerce_int,
|
coerce_int,
|
||||||
@@ -18,7 +18,7 @@ from abogen.web.routes.utils.settings import (
|
|||||||
SAVE_MODE_LABELS,
|
SAVE_MODE_LABELS,
|
||||||
audiobookshelf_manual_available,
|
audiobookshelf_manual_available,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.voice import (
|
from abogen.webui.routes.utils.voice import (
|
||||||
parse_voice_formula,
|
parse_voice_formula,
|
||||||
formula_from_profile,
|
formula_from_profile,
|
||||||
resolve_voice_setting,
|
resolve_voice_setting,
|
||||||
@@ -26,9 +26,9 @@ from abogen.web.routes.utils.voice import (
|
|||||||
prepare_speaker_metadata,
|
prepare_speaker_metadata,
|
||||||
template_options,
|
template_options,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.entity import sync_pronunciation_overrides
|
from abogen.webui.routes.utils.entity import sync_pronunciation_overrides
|
||||||
from abogen.web.routes.utils.epub import job_download_flags
|
from abogen.webui.routes.utils.epub import job_download_flags
|
||||||
from abogen.web.routes.utils.common import split_profile_spec
|
from abogen.webui.routes.utils.common import split_profile_spec
|
||||||
from abogen.utils import calculate_text_length
|
from abogen.utils import calculate_text_length
|
||||||
from abogen.voice_profiles import serialize_profiles, normalize_profile_entry
|
from abogen.voice_profiles import serialize_profiles, normalize_profile_entry
|
||||||
from abogen.chunking import ChunkLevel, build_chunks_for_chapters
|
from abogen.chunking import ChunkLevel, build_chunks_for_chapters
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ def generate_preview_audio(
|
|||||||
source_text = text
|
source_text = text
|
||||||
if pronunciation_overrides or manual_overrides or speakers:
|
if pronunciation_overrides or manual_overrides or speakers:
|
||||||
try:
|
try:
|
||||||
from abogen.web import conversion_runner as runner
|
from abogen.webui import conversion_runner as runner
|
||||||
|
|
||||||
class _PreviewJob:
|
class _PreviewJob:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
from flask import current_app, abort
|
from flask import current_app, abort
|
||||||
from abogen.web.service import ConversionService, PendingJob
|
from abogen.webui.service import ConversionService, PendingJob
|
||||||
|
|
||||||
def get_service() -> ConversionService:
|
def get_service() -> ConversionService:
|
||||||
return current_app.extensions["conversion_service"]
|
return current_app.extensions["conversion_service"]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from abogen.normalization_settings import (
|
|||||||
from abogen.utils import load_config, save_config
|
from abogen.utils import load_config, save_config
|
||||||
from abogen.integrations.calibre_opds import CalibreOPDSClient
|
from abogen.integrations.calibre_opds import CalibreOPDSClient
|
||||||
from abogen.integrations.audiobookshelf import AudiobookshelfConfig
|
from abogen.integrations.audiobookshelf import AudiobookshelfConfig
|
||||||
from abogen.web.routes.utils.common import split_profile_spec
|
from abogen.webui.routes.utils.common import split_profile_spec
|
||||||
|
|
||||||
SAVE_MODE_LABELS = {
|
SAVE_MODE_LABELS = {
|
||||||
"save_next_to_input": "Save next to input file",
|
"save_next_to_input": "Save next to input file",
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import numpy as np
|
|||||||
|
|
||||||
from abogen.speaker_configs import slugify_label
|
from abogen.speaker_configs import slugify_label
|
||||||
from abogen.speaker_analysis import analyze_speakers
|
from abogen.speaker_analysis import analyze_speakers
|
||||||
from abogen.web.routes.utils.settings import load_settings, settings_defaults, _DEFAULT_ANALYSIS_THRESHOLD, _CHUNK_LEVEL_OPTIONS, _APOSTROPHE_MODE_OPTIONS, _NORMALIZATION_GROUPS
|
from abogen.webui.routes.utils.settings import load_settings, settings_defaults, _DEFAULT_ANALYSIS_THRESHOLD, _CHUNK_LEVEL_OPTIONS, _APOSTROPHE_MODE_OPTIONS, _NORMALIZATION_GROUPS
|
||||||
from abogen.web.routes.utils.common import split_profile_spec
|
from abogen.webui.routes.utils.common import split_profile_spec
|
||||||
from abogen.voice_profiles import (
|
from abogen.voice_profiles import (
|
||||||
load_profiles,
|
load_profiles,
|
||||||
serialize_profiles,
|
serialize_profiles,
|
||||||
@@ -21,7 +21,7 @@ from abogen.constants import (
|
|||||||
)
|
)
|
||||||
from abogen.speaker_configs import list_configs
|
from abogen.speaker_configs import list_configs
|
||||||
from abogen.utils import load_numpy_kpipeline
|
from abogen.utils import load_numpy_kpipeline
|
||||||
from abogen.web.conversion_runner import _select_device, _to_float32, SAMPLE_RATE, SPLIT_PATTERN
|
from abogen.webui.conversion_runner import _select_device, _to_float32, SAMPLE_RATE, SPLIT_PATTERN
|
||||||
|
|
||||||
_preview_pipeline_lock = threading.RLock()
|
_preview_pipeline_lock = threading.RLock()
|
||||||
_preview_pipelines: Dict[Tuple[str, str], Any] = {}
|
_preview_pipelines: Dict[Tuple[str, str], Any] = {}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ from typing import Any, Dict, List, Optional
|
|||||||
from flask import Blueprint, render_template, request, jsonify, abort, flash, redirect, url_for
|
from flask import Blueprint, render_template, request, jsonify, abort, flash, redirect, url_for
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.routes.utils.voice import (
|
from abogen.webui.routes.utils.voice import (
|
||||||
template_options,
|
template_options,
|
||||||
resolve_voice_setting,
|
resolve_voice_setting,
|
||||||
resolve_voice_choice,
|
resolve_voice_choice,
|
||||||
parse_voice_formula,
|
parse_voice_formula,
|
||||||
)
|
)
|
||||||
from abogen.web.routes.utils.settings import load_settings, coerce_bool
|
from abogen.webui.routes.utils.settings import load_settings, coerce_bool
|
||||||
from abogen.web.routes.utils.preview import synthesize_preview
|
from abogen.webui.routes.utils.preview import synthesize_preview
|
||||||
from abogen.speaker_configs import (
|
from abogen.speaker_configs import (
|
||||||
list_configs,
|
list_configs,
|
||||||
get_config,
|
get_config,
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Docker Compose for Abogen Web UI (Flask-based interface)
|
||||||
|
#
|
||||||
|
# This configuration runs the web-based Flask UI for Abogen.
|
||||||
|
# For the Qt desktop UI, see the upstream project's docker configuration.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker compose -f docker-compose.webui.yml up --build
|
||||||
|
#
|
||||||
|
# Or set as default:
|
||||||
|
# docker compose up --build
|
||||||
|
#
|
||||||
|
services:
|
||||||
|
abogen-webui:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: abogen/webui/Dockerfile
|
||||||
|
args:
|
||||||
|
TORCH_INDEX_URL: ${TORCH_INDEX_URL:-https://download.pytorch.org/whl/cu124}
|
||||||
|
TORCH_VERSION: ${TORCH_VERSION:-}
|
||||||
|
image: abogen-webui:latest
|
||||||
|
user: "${ABOGEN_UID:-1000}:${ABOGEN_GID:-1000}"
|
||||||
|
ports:
|
||||||
|
- "${ABOGEN_PORT:-8808}:8808"
|
||||||
|
volumes:
|
||||||
|
- ${ABOGEN_DATA:-./data}:/data
|
||||||
|
- ${ABOGEN_SETTINGS_DIR:-./config}:/config
|
||||||
|
- ${ABOGEN_OUTPUT_DIR:-./storage/output}:/data/outputs
|
||||||
|
- ${ABOGEN_TEMP_DIR:-./storage/tmp}:/data/cache
|
||||||
|
environment:
|
||||||
|
ABOGEN_HOST: 0.0.0.0
|
||||||
|
ABOGEN_PORT: 8808
|
||||||
|
ABOGEN_SETTINGS_DIR: "/config"
|
||||||
|
ABOGEN_UPLOAD_ROOT: /data/uploads
|
||||||
|
ABOGEN_OUTPUT_DIR: "/data/outputs"
|
||||||
|
ABOGEN_OUTPUT_ROOT: "/data/outputs"
|
||||||
|
ABOGEN_TEMP_DIR: "/data/cache"
|
||||||
|
ABOGEN_VOICE_CACHE_DIR: "/data/voice-cache"
|
||||||
|
HF_HOME: "/data/huggingface"
|
||||||
|
HUGGINGFACE_HUB_CACHE: "/data/huggingface/hub"
|
||||||
|
HOME: "/tmp/abogen-home"
|
||||||
|
# --- GPU support -----------------------------------------------------
|
||||||
|
# These settings assume the NVIDIA Container Toolkit is installed.
|
||||||
|
# Leave them in place for GPU acceleration; comment out the entire block
|
||||||
|
# below if you are deploying to a CPU-only host.
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '4.0'
|
||||||
|
memory: 8G
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- capabilities: [gpu]
|
||||||
|
# driver: nvidia
|
||||||
|
# count: all
|
||||||
|
# Runtime flag is only honored by legacy docker-compose (v1) CLI.
|
||||||
|
# Uncomment if you're still using it:
|
||||||
|
# runtime: nvidia
|
||||||
|
restart: unless-stopped
|
||||||
+11
-1
@@ -1,8 +1,18 @@
|
|||||||
|
# Docker Compose for Abogen
|
||||||
|
#
|
||||||
|
# This configuration runs the Flask-based Web UI for Abogen.
|
||||||
|
# The Web UI provides a browser-based interface for audiobook generation.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker compose up --build
|
||||||
|
#
|
||||||
|
# Access the web interface at http://localhost:8808
|
||||||
|
#
|
||||||
services:
|
services:
|
||||||
abogen:
|
abogen:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: abogen/Dockerfile
|
dockerfile: abogen/webui/Dockerfile
|
||||||
args:
|
args:
|
||||||
TORCH_INDEX_URL: ${TORCH_INDEX_URL:-https://download.pytorch.org/whl/cu124}
|
TORCH_INDEX_URL: ${TORCH_INDEX_URL:-https://download.pytorch.org/whl/cu124}
|
||||||
TORCH_VERSION: ${TORCH_VERSION:-}
|
TORCH_VERSION: ${TORCH_VERSION:-}
|
||||||
|
|||||||
+5
-5
@@ -62,11 +62,11 @@ allow-direct-references = true
|
|||||||
|
|
||||||
|
|
||||||
[project.gui-scripts]
|
[project.gui-scripts]
|
||||||
abogen = "abogen.web.app:main"
|
abogen = "abogen.webui.app:main"
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
abogen-cli = "abogen.web.app:main"
|
abogen-cli = "abogen.webui.app:main"
|
||||||
abogen-web = "abogen.web.app:main"
|
abogen-web = "abogen.webui.app:main"
|
||||||
|
|
||||||
[tool.hatch.build.targets.sdist]
|
[tool.hatch.build.targets.sdist]
|
||||||
exclude = [
|
exclude = [
|
||||||
@@ -82,8 +82,8 @@ packages = ["abogen"]
|
|||||||
|
|
||||||
[tool.hatch.build]
|
[tool.hatch.build]
|
||||||
include = [
|
include = [
|
||||||
"abogen/web/templates/**",
|
"abogen/webui/templates/**",
|
||||||
"abogen/web/static/**",
|
"abogen/webui/static/**",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.hatch.version]
|
[tool.hatch.version]
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
from werkzeug.datastructures import MultiDict
|
from werkzeug.datastructures import MultiDict
|
||||||
|
|
||||||
from abogen.web.routes.utils.form import apply_book_step_form
|
from abogen.webui.routes.utils.form import apply_book_step_form
|
||||||
from abogen.web.service import PendingJob
|
from abogen.webui.service import PendingJob
|
||||||
|
|
||||||
|
|
||||||
def _make_pending_job() -> PendingJob:
|
def _make_pending_job() -> PendingJob:
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def _install_dependency_stubs() -> None:
|
|||||||
_install_dependency_stubs()
|
_install_dependency_stubs()
|
||||||
|
|
||||||
from abogen.text_extractor import ExtractedChapter
|
from abogen.text_extractor import ExtractedChapter
|
||||||
from abogen.web.conversion_runner import _apply_chapter_overrides, _merge_metadata
|
from abogen.webui.conversion_runner import _apply_chapter_overrides, _merge_metadata
|
||||||
|
|
||||||
|
|
||||||
def _sample_chapters() -> list[ExtractedChapter]:
|
def _sample_chapters() -> list[ExtractedChapter]:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from abogen.chunking import chunk_text
|
from abogen.chunking import chunk_text
|
||||||
from abogen.web.conversion_runner import _chunk_voice_spec, _group_chunks_by_chapter
|
from abogen.webui.conversion_runner import _chunk_voice_spec, _group_chunks_by_chapter
|
||||||
|
|
||||||
|
|
||||||
def test_group_chunks_by_chapter_orders_and_groups() -> None:
|
def test_group_chunks_by_chapter_orders_and_groups() -> None:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from abogen.web.conversion_runner import _chunk_text_for_tts
|
from abogen.webui.conversion_runner import _chunk_text_for_tts
|
||||||
|
|
||||||
|
|
||||||
def test_chunk_text_for_tts_prefers_text_over_normalized_text():
|
def test_chunk_text_for_tts_prefers_text_over_normalized_text():
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ if "bs4" not in sys.modules:
|
|||||||
sys.modules["bs4"] = bs4_stub
|
sys.modules["bs4"] = bs4_stub
|
||||||
|
|
||||||
|
|
||||||
from abogen.web.conversion_runner import (
|
from abogen.webui.conversion_runner import (
|
||||||
_format_spoken_chapter_title,
|
_format_spoken_chapter_title,
|
||||||
_headings_equivalent,
|
_headings_equivalent,
|
||||||
_normalize_chapter_opening_caps,
|
_normalize_chapter_opening_caps,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ if "bs4" not in sys.modules:
|
|||||||
sys.modules["bs4"] = bs4_stub
|
sys.modules["bs4"] = bs4_stub
|
||||||
|
|
||||||
|
|
||||||
from abogen.web.conversion_runner import _build_outro_text, _build_title_intro_text
|
from abogen.webui.conversion_runner import _build_outro_text, _build_title_intro_text
|
||||||
|
|
||||||
|
|
||||||
def test_title_intro_includes_series_sentence() -> None:
|
def test_title_intro_includes_series_sentence() -> None:
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ from types import SimpleNamespace
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from abogen.constants import VOICES_INTERNAL
|
from abogen.constants import VOICES_INTERNAL
|
||||||
from abogen.web.conversion_runner import (
|
from abogen.webui.conversion_runner import (
|
||||||
_chapter_voice_spec,
|
_chapter_voice_spec,
|
||||||
_chunk_voice_spec,
|
_chunk_voice_spec,
|
||||||
_collect_required_voice_ids,
|
_collect_required_voice_ids,
|
||||||
)
|
)
|
||||||
from abogen.web.service import Job
|
from abogen.webui.service import Job
|
||||||
|
|
||||||
|
|
||||||
def _sample_job(formula: str) -> Job:
|
def _sample_job(formula: str) -> Job:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from abogen.debug_tts_samples import DEBUG_TTS_SAMPLES, MARKER_PREFIX, MARKER_SU
|
|||||||
from abogen.kokoro_text_normalization import HAS_NUM2WORDS, normalize_for_pipeline
|
from abogen.kokoro_text_normalization import HAS_NUM2WORDS, normalize_for_pipeline
|
||||||
from abogen.normalization_settings import build_apostrophe_config
|
from abogen.normalization_settings import build_apostrophe_config
|
||||||
from abogen.text_extractor import extract_from_path
|
from abogen.text_extractor import extract_from_path
|
||||||
from abogen.web.app import create_app
|
from abogen.webui.app import create_app
|
||||||
|
|
||||||
|
|
||||||
def test_debug_epub_contains_all_codes():
|
def test_debug_epub_contains_all_codes():
|
||||||
@@ -25,7 +25,7 @@ def test_debug_epub_contains_all_codes():
|
|||||||
|
|
||||||
def test_debug_samples_normalize_smoke():
|
def test_debug_samples_normalize_smoke():
|
||||||
# Use the same defaults as the web UI.
|
# Use the same defaults as the web UI.
|
||||||
from abogen.web.routes.utils.settings import settings_defaults
|
from abogen.webui.routes.utils.settings import settings_defaults
|
||||||
|
|
||||||
settings = settings_defaults()
|
settings = settings_defaults()
|
||||||
apostrophe = build_apostrophe_config(settings=settings)
|
apostrophe = build_apostrophe_config(settings=settings)
|
||||||
@@ -56,7 +56,7 @@ def test_debug_samples_normalize_smoke():
|
|||||||
|
|
||||||
def test_settings_debug_route_writes_manifest(tmp_path, monkeypatch):
|
def test_settings_debug_route_writes_manifest(tmp_path, monkeypatch):
|
||||||
# Avoid pulling Kokoro models in tests: stub the pipeline.
|
# Avoid pulling Kokoro models in tests: stub the pipeline.
|
||||||
from abogen.web import debug_tts_runner as runner
|
from abogen.webui import debug_tts_runner as runner
|
||||||
|
|
||||||
class _Seg:
|
class _Seg:
|
||||||
def __init__(self, audio):
|
def __init__(self, audio):
|
||||||
@@ -122,7 +122,7 @@ def test_debug_samples_have_minimum_per_category():
|
|||||||
|
|
||||||
|
|
||||||
def test_debug_runner_resolves_profile_voice_before_pipeline(tmp_path, monkeypatch):
|
def test_debug_runner_resolves_profile_voice_before_pipeline(tmp_path, monkeypatch):
|
||||||
from abogen.web import debug_tts_runner as runner
|
from abogen.webui import debug_tts_runner as runner
|
||||||
|
|
||||||
# Stub voice setting resolution so we don't depend on the user's profile file.
|
# Stub voice setting resolution so we don't depend on the user's profile file.
|
||||||
monkeypatch.setattr(runner, "_resolve_voice_setting", lambda value: ("af_heart", "AM HQ Alt", None))
|
monkeypatch.setattr(runner, "_resolve_voice_setting", lambda value: ("af_heart", "AM HQ Alt", None))
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from abogen.web.conversion_runner import _render_ffmetadata, _write_ffmetadata_file
|
from abogen.webui.conversion_runner import _render_ffmetadata, _write_ffmetadata_file
|
||||||
|
|
||||||
|
|
||||||
def test_render_ffmetadata_includes_chapters(tmp_path):
|
def test_render_ffmetadata_includes_chapters(tmp_path):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from abogen.web import conversion_runner
|
from abogen.webui import conversion_runner
|
||||||
|
|
||||||
|
|
||||||
class DummyJob:
|
class DummyJob:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abogen.web.routes.api import _opds_metadata_overrides
|
from abogen.webui.routes.api import _opds_metadata_overrides
|
||||||
|
|
||||||
|
|
||||||
def test_opds_metadata_overrides_maps_author_and_subtitle() -> None:
|
def test_opds_metadata_overrides_maps_author_and_subtitle() -> None:
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from abogen.web.conversion_runner import _build_output_path, _prepare_project_layout
|
from abogen.webui.conversion_runner import _build_output_path, _prepare_project_layout
|
||||||
from abogen.web.service import Job
|
from abogen.webui.service import Job
|
||||||
|
|
||||||
|
|
||||||
def _sample_job(tmp_path: Path) -> Job:
|
def _sample_job(tmp_path: Path) -> Job:
|
||||||
@@ -33,7 +33,7 @@ def _sample_job(tmp_path: Path) -> Job:
|
|||||||
def test_prepare_project_layout_uses_timestamped_folder(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
def test_prepare_project_layout_uses_timestamped_folder(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||||
job = _sample_job(tmp_path)
|
job = _sample_job(tmp_path)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"abogen.web.conversion_runner._output_timestamp_token",
|
"abogen.webui.conversion_runner._output_timestamp_token",
|
||||||
lambda: "20250101-120000",
|
lambda: "20250101-120000",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ def test_prepare_project_layout_creates_project_subdirs(monkeypatch: pytest.Monk
|
|||||||
job = _sample_job(tmp_path)
|
job = _sample_job(tmp_path)
|
||||||
job.save_as_project = True
|
job.save_as_project = True
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"abogen.web.conversion_runner._output_timestamp_token",
|
"abogen.webui.conversion_runner._output_timestamp_token",
|
||||||
lambda: "20250101-120500",
|
lambda: "20250101-120500",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from pathlib import Path
|
|||||||
|
|
||||||
from werkzeug.datastructures import MultiDict
|
from werkzeug.datastructures import MultiDict
|
||||||
|
|
||||||
from abogen.web.routes.utils.form import apply_prepare_form
|
from abogen.webui.routes.utils.form import apply_prepare_form
|
||||||
from abogen.web.routes.utils.voice import resolve_voice_setting
|
from abogen.webui.routes.utils.voice import resolve_voice_setting
|
||||||
from abogen.web.service import PendingJob
|
from abogen.webui.service import PendingJob
|
||||||
|
|
||||||
|
|
||||||
def _make_pending_job() -> PendingJob:
|
def _make_pending_job() -> PendingJob:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from abogen.web.routes.utils import preview
|
from abogen.webui.routes.utils import preview
|
||||||
|
|
||||||
|
|
||||||
def test_preview_applies_manual_override_before_normalization(monkeypatch):
|
def test_preview_applies_manual_override_before_normalization(monkeypatch):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import time
|
import time
|
||||||
from abogen.web.service import Job, JobStatus, build_service, _JOB_LOGGER, build_audiobookshelf_metadata
|
from abogen.webui.service import Job, JobStatus, build_service, _JOB_LOGGER, build_audiobookshelf_metadata
|
||||||
|
|
||||||
|
|
||||||
def test_service_processes_job(tmp_path):
|
def test_service_processes_job(tmp_path):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from abogen.utils import load_config, save_config
|
from abogen.utils import load_config, save_config
|
||||||
from abogen.web.app import create_app
|
from abogen.webui.app import create_app
|
||||||
|
|
||||||
|
|
||||||
def test_settings_update_preserves_abs_api_token_when_blank(tmp_path):
|
def test_settings_update_preserves_abs_api_token_when_blank(tmp_path):
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import pytest
|
|||||||
|
|
||||||
from abogen.constants import VOICES_INTERNAL
|
from abogen.constants import VOICES_INTERNAL
|
||||||
from abogen.voice_cache import LocalEntryNotFoundError, _CACHED_VOICES, ensure_voice_assets
|
from abogen.voice_cache import LocalEntryNotFoundError, _CACHED_VOICES, ensure_voice_assets
|
||||||
from abogen.web.conversion_runner import _collect_required_voice_ids
|
from abogen.webui.conversion_runner import _collect_required_voice_ids
|
||||||
from abogen.web.service import Job
|
from abogen.webui.service import Job
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abogen.web.conversion_runner import _resolve_voice, _supertonic_voice_from_spec
|
from abogen.webui.conversion_runner import _resolve_voice, _supertonic_voice_from_spec
|
||||||
from abogen.tts_supertonic import DEFAULT_SUPERTONIC_VOICES
|
from abogen.tts_supertonic import DEFAULT_SUPERTONIC_VOICES
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user