mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-22 07:10:28 +02:00
extract subtitle writer creation: resolve_subtitle_format() + make_subtitle_writer()
- infrastructure/subtitle_writer.py: add resolve_subtitle_format() that maps format strings (e.g. 'ass_centered_narrow') to (extension, alignment), and make_subtitle_writer() convenience that resolves + creates writer or None - conversion_runner.py: replace _create_subtitle_writer() with make_subtitle_writer() - pyqt/conversion.py: replace _subtitle_alignment_from_format() and 3 manual create_subtitle_writer() call sites with resolve_subtitle_format()/make_subtitle_writer()
This commit is contained in:
@@ -290,6 +290,71 @@ def create_subtitle_writer(
|
|||||||
raise ValueError(f"Unsupported subtitle format: {format}")
|
raise ValueError(f"Unsupported subtitle format: {format}")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_subtitle_format(
|
||||||
|
subtitle_format: str | None,
|
||||||
|
subtitle_mode: str,
|
||||||
|
) -> tuple[str, str]:
|
||||||
|
"""Resolve a subtitle_format setting string to (file_extension, alignment).
|
||||||
|
|
||||||
|
Handles the PyQt convention where format strings encode alignment
|
||||||
|
(e.g. ``"ass_centered_narrow"`` → extension ``"ass"``, alignment
|
||||||
|
``"center_narrow"``).
|
||||||
|
|
||||||
|
Also enforces that ``"Sentence + Highlighting"`` mode requires ASS.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (file_extension, alignment) suitable for
|
||||||
|
:func:`create_subtitle_writer`.
|
||||||
|
"""
|
||||||
|
fmt = (subtitle_format or "srt").lower()
|
||||||
|
|
||||||
|
if subtitle_mode == "Sentence + Highlighting" and fmt == "srt":
|
||||||
|
fmt = "ass"
|
||||||
|
|
||||||
|
if "ass" in fmt:
|
||||||
|
extension = "ass"
|
||||||
|
if "centered_narrow" in fmt:
|
||||||
|
alignment = "center_narrow"
|
||||||
|
elif "centered" in fmt:
|
||||||
|
alignment = "center"
|
||||||
|
elif "narrow" in fmt:
|
||||||
|
alignment = "narrow"
|
||||||
|
else:
|
||||||
|
alignment = "left"
|
||||||
|
else:
|
||||||
|
extension = fmt if fmt in ("srt", "vtt") else "srt"
|
||||||
|
alignment = "left"
|
||||||
|
|
||||||
|
return extension, alignment
|
||||||
|
|
||||||
|
|
||||||
|
def make_subtitle_writer(
|
||||||
|
audio_path: Path,
|
||||||
|
subtitle_format: str | None,
|
||||||
|
subtitle_mode: str,
|
||||||
|
max_words: int = 50,
|
||||||
|
) -> SubtitleWriter | None:
|
||||||
|
"""Convenience: resolve format and create a writer, or return None if disabled.
|
||||||
|
|
||||||
|
Returns ``None`` when ``subtitle_mode`` is ``"Disabled"`` or the
|
||||||
|
format is unsupported.
|
||||||
|
"""
|
||||||
|
if subtitle_mode == "Disabled":
|
||||||
|
return None
|
||||||
|
|
||||||
|
extension, alignment = resolve_subtitle_format(subtitle_format, subtitle_mode)
|
||||||
|
try:
|
||||||
|
return create_subtitle_writer(
|
||||||
|
audio_path.with_suffix(f".{extension}"),
|
||||||
|
extension,
|
||||||
|
subtitle_mode,
|
||||||
|
alignment=alignment,
|
||||||
|
max_words=max_words,
|
||||||
|
)
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"SubtitleFormat",
|
"SubtitleFormat",
|
||||||
"SubtitleMode",
|
"SubtitleMode",
|
||||||
@@ -300,4 +365,6 @@ __all__ = [
|
|||||||
"VttWriter",
|
"VttWriter",
|
||||||
"AssWriter",
|
"AssWriter",
|
||||||
"create_subtitle_writer",
|
"create_subtitle_writer",
|
||||||
|
"resolve_subtitle_format",
|
||||||
|
"make_subtitle_writer",
|
||||||
]
|
]
|
||||||
|
|||||||
+16
-30
@@ -21,7 +21,7 @@ from abogen.constants import (
|
|||||||
SUPPORTED_SOUND_FORMATS,
|
SUPPORTED_SOUND_FORMATS,
|
||||||
SUPPORTED_SUBTITLE_FORMATS,
|
SUPPORTED_SUBTITLE_FORMATS,
|
||||||
)
|
)
|
||||||
from abogen.infrastructure.subtitle_writer import create_subtitle_writer
|
from abogen.infrastructure.subtitle_writer import make_subtitle_writer, resolve_subtitle_format
|
||||||
from abogen.domain.split_pattern import get_split_pattern
|
from abogen.domain.split_pattern import get_split_pattern
|
||||||
from abogen.domain.subtitle_processor import (
|
from abogen.domain.subtitle_processor import (
|
||||||
parse_subtitle_file,
|
parse_subtitle_file,
|
||||||
@@ -65,17 +65,6 @@ import threading # for efficient waiting
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def _subtitle_alignment_from_format(subtitle_format: str) -> str:
|
|
||||||
"""Map PyQt subtitle format string to SubtitleAlignment value."""
|
|
||||||
if subtitle_format in ("ass_centered_wide", "ass_centered_narrow"):
|
|
||||||
if subtitle_format == "ass_centered_narrow":
|
|
||||||
return "center_narrow"
|
|
||||||
return "center"
|
|
||||||
if subtitle_format in ("ass_narrow",):
|
|
||||||
return "narrow"
|
|
||||||
return "left"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Configuration constants
|
# Configuration constants
|
||||||
_USER_RESPONSE_TIMEOUT = (
|
_USER_RESPONSE_TIMEOUT = (
|
||||||
@@ -743,18 +732,17 @@ class ConversionThread(QThread):
|
|||||||
merged_subtitle_path = None
|
merged_subtitle_path = None
|
||||||
if self.subtitle_mode != "Disabled":
|
if self.subtitle_mode != "Disabled":
|
||||||
subtitle_format = getattr(self, "subtitle_format", "srt")
|
subtitle_format = getattr(self, "subtitle_format", "srt")
|
||||||
file_extension = "ass" if "ass" in subtitle_format else "srt"
|
extension, _ = resolve_subtitle_format(subtitle_format, self.subtitle_mode)
|
||||||
merged_subtitle_path = (
|
merged_subtitle_path = (
|
||||||
os.path.splitext(merged_out_path)[0] + f".{file_extension}"
|
os.path.splitext(merged_out_path)[0] + f".{extension}"
|
||||||
)
|
)
|
||||||
alignment = _subtitle_alignment_from_format(subtitle_format)
|
merged_subtitle_writer = make_subtitle_writer(
|
||||||
merged_subtitle_writer = create_subtitle_writer(
|
|
||||||
Path(merged_subtitle_path),
|
Path(merged_subtitle_path),
|
||||||
file_extension,
|
subtitle_format,
|
||||||
self.subtitle_mode,
|
self.subtitle_mode,
|
||||||
alignment=alignment,
|
|
||||||
max_words=self.max_subtitle_words,
|
max_words=self.max_subtitle_words,
|
||||||
)
|
)
|
||||||
|
if merged_subtitle_writer:
|
||||||
merged_subtitle_writer.open()
|
merged_subtitle_writer.open()
|
||||||
else:
|
else:
|
||||||
merged_subtitle_path = None
|
merged_subtitle_path = None
|
||||||
@@ -817,18 +805,17 @@ class ConversionThread(QThread):
|
|||||||
continue
|
continue
|
||||||
if self.subtitle_mode != "Disabled":
|
if self.subtitle_mode != "Disabled":
|
||||||
subtitle_format = getattr(self, "subtitle_format", "srt")
|
subtitle_format = getattr(self, "subtitle_format", "srt")
|
||||||
file_extension = "ass" if "ass" in subtitle_format else "srt"
|
extension, _ = resolve_subtitle_format(subtitle_format, self.subtitle_mode)
|
||||||
chapter_subtitle_path = os.path.join(
|
chapter_subtitle_path = os.path.join(
|
||||||
chapters_out_dir, f"{chapter_filename}.{file_extension}"
|
chapters_out_dir, f"{chapter_filename}.{extension}"
|
||||||
)
|
)
|
||||||
alignment = _subtitle_alignment_from_format(subtitle_format)
|
chapter_subtitle_writer = make_subtitle_writer(
|
||||||
chapter_subtitle_writer = create_subtitle_writer(
|
|
||||||
Path(chapter_subtitle_path),
|
Path(chapter_subtitle_path),
|
||||||
file_extension,
|
subtitle_format,
|
||||||
self.subtitle_mode,
|
self.subtitle_mode,
|
||||||
alignment=alignment,
|
|
||||||
max_words=self.max_subtitle_words,
|
max_words=self.max_subtitle_words,
|
||||||
)
|
)
|
||||||
|
if chapter_subtitle_writer:
|
||||||
chapter_subtitle_writer.open()
|
chapter_subtitle_writer.open()
|
||||||
else:
|
else:
|
||||||
chapter_subtitle_writer = None
|
chapter_subtitle_writer = None
|
||||||
@@ -1189,16 +1176,15 @@ class ConversionThread(QThread):
|
|||||||
subtitle_writer = None
|
subtitle_writer = None
|
||||||
subtitle_path = None
|
subtitle_path = None
|
||||||
subtitle_format = getattr(self, "subtitle_format", "srt")
|
subtitle_format = getattr(self, "subtitle_format", "srt")
|
||||||
file_extension = "ass" if "ass" in subtitle_format else "srt"
|
extension, _ = resolve_subtitle_format(subtitle_format, self.subtitle_mode)
|
||||||
subtitle_path = f"{base_filepath_no_ext}.{file_extension}"
|
subtitle_path = f"{base_filepath_no_ext}.{extension}"
|
||||||
alignment_str = _subtitle_alignment_from_format(subtitle_format)
|
subtitle_writer = make_subtitle_writer(
|
||||||
subtitle_writer = create_subtitle_writer(
|
|
||||||
Path(subtitle_path),
|
Path(subtitle_path),
|
||||||
file_extension,
|
subtitle_format,
|
||||||
self.subtitle_mode,
|
self.subtitle_mode,
|
||||||
alignment=alignment_str,
|
|
||||||
max_words=self.max_subtitle_words,
|
max_words=self.max_subtitle_words,
|
||||||
)
|
)
|
||||||
|
if subtitle_writer:
|
||||||
subtitle_writer.open()
|
subtitle_writer.open()
|
||||||
|
|
||||||
# Load voice
|
# Load voice
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from abogen.utils import (
|
|||||||
)
|
)
|
||||||
from abogen.voice_profiles import load_profiles, normalize_profile_entry
|
from abogen.voice_profiles import load_profiles, normalize_profile_entry
|
||||||
from abogen.llm_client import LLMClientError
|
from abogen.llm_client import LLMClientError
|
||||||
from abogen.infrastructure.subtitle_writer import create_subtitle_writer
|
from abogen.infrastructure.subtitle_writer import make_subtitle_writer
|
||||||
from abogen.domain.chapter_titles import (
|
from abogen.domain.chapter_titles import (
|
||||||
simplify_heading_text as _simplify_heading_text,
|
simplify_heading_text as _simplify_heading_text,
|
||||||
headings_equivalent as _headings_equivalent,
|
headings_equivalent as _headings_equivalent,
|
||||||
@@ -338,7 +338,18 @@ def run_conversion_job(job: Job) -> None:
|
|||||||
cancel_check=lambda: job.cancel_requested,
|
cancel_check=lambda: job.cancel_requested,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
subtitle_writer = _create_subtitle_writer(job, audio_path)
|
subtitle_writer = make_subtitle_writer(
|
||||||
|
audio_path,
|
||||||
|
job.subtitle_format,
|
||||||
|
job.subtitle_mode or "Line",
|
||||||
|
max_words=job.max_subtitle_words,
|
||||||
|
)
|
||||||
|
if subtitle_writer is None and job.subtitle_mode != "Disabled":
|
||||||
|
fmt = (job.subtitle_format or "srt").lower()
|
||||||
|
if job.subtitle_mode == "Sentence + Highlighting" and fmt == "srt":
|
||||||
|
job.add_log("Highlighting requires ASS subtitles. Switching format.", level="warning")
|
||||||
|
else:
|
||||||
|
job.add_log(f"Unsupported subtitle format '{job.subtitle_format}'. Skipping.", level="warning")
|
||||||
job.result.audio_path = audio_path
|
job.result.audio_path = audio_path
|
||||||
if subtitle_writer:
|
if subtitle_writer:
|
||||||
job.result.subtitle_paths.append(subtitle_writer.path)
|
job.result.subtitle_paths.append(subtitle_writer.path)
|
||||||
@@ -1063,25 +1074,6 @@ def _resolve_voice(pipeline, voice_spec: str, use_gpu: bool):
|
|||||||
return voice_spec
|
return voice_spec
|
||||||
|
|
||||||
|
|
||||||
def _create_subtitle_writer(job: Job, audio_path: Path):
|
|
||||||
if job.subtitle_mode == "Disabled":
|
|
||||||
return None
|
|
||||||
|
|
||||||
fmt = (job.subtitle_format or "srt").lower()
|
|
||||||
if job.subtitle_mode == "Sentence + Highlighting" and fmt == "srt":
|
|
||||||
job.add_log("Highlighting requires ASS subtitles. Switching format.", level="warning")
|
|
||||||
fmt = "ass"
|
|
||||||
|
|
||||||
try:
|
|
||||||
return create_subtitle_writer(
|
|
||||||
audio_path.with_suffix(f".{fmt}"),
|
|
||||||
fmt,
|
|
||||||
job.subtitle_mode or "Line",
|
|
||||||
)
|
|
||||||
except (ValueError, KeyError):
|
|
||||||
job.add_log(f"Unsupported subtitle format '{job.subtitle_format}'. Skipping.", level="warning")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _make_canceller(job: Job) -> Callable[[], None]:
|
def _make_canceller(job: Job) -> Callable[[], None]:
|
||||||
def _cancel() -> None:
|
def _cancel() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user