mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
refactor: extract title/outro builders into domain/title_builder.py
- Extract build_title_intro_text and build_outro_text into domain/title_builder.py - Uses metadata_helpers for metadata processing - Remove _build_title_intro_text and _build_outro_text from conversion_runner.py - Add tests/test_title_builder.py with 12 tests - All tests match old behavior
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Mapping, Optional
|
||||||
|
|
||||||
|
from .metadata_helpers import (
|
||||||
|
ensure_sentence,
|
||||||
|
extract_series_metadata,
|
||||||
|
format_author_sentence,
|
||||||
|
format_series_sentence,
|
||||||
|
normalize_metadata_map,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_title_intro_text(
|
||||||
|
metadata: Optional[Mapping[str, Any]],
|
||||||
|
fallback_basename: str,
|
||||||
|
) -> str:
|
||||||
|
"""Build the title introduction text from metadata."""
|
||||||
|
normalized = normalize_metadata_map(metadata)
|
||||||
|
fallback_title = Path(fallback_basename).stem if fallback_basename else ""
|
||||||
|
title = (
|
||||||
|
normalized.get("title")
|
||||||
|
or normalized.get("book_title")
|
||||||
|
or normalized.get("album")
|
||||||
|
or fallback_title
|
||||||
|
)
|
||||||
|
if not title:
|
||||||
|
title = fallback_title
|
||||||
|
subtitle = normalized.get("subtitle") or normalized.get("sub_title")
|
||||||
|
if subtitle and title and subtitle.casefold() == title.casefold():
|
||||||
|
subtitle = ""
|
||||||
|
|
||||||
|
author_value = ""
|
||||||
|
for candidate in ("artist", "album_artist", "author", "authors", "writer", "composer"):
|
||||||
|
value = normalized.get(candidate)
|
||||||
|
if value:
|
||||||
|
author_value = value
|
||||||
|
break
|
||||||
|
|
||||||
|
series_name, series_number = extract_series_metadata(normalized)
|
||||||
|
series_sentence = format_series_sentence(series_name, series_number)
|
||||||
|
|
||||||
|
sentences: List[str] = []
|
||||||
|
if series_sentence:
|
||||||
|
sentences.append(ensure_sentence(series_sentence))
|
||||||
|
if title:
|
||||||
|
sentences.append(ensure_sentence(title))
|
||||||
|
if subtitle:
|
||||||
|
sentences.append(ensure_sentence(subtitle))
|
||||||
|
author_sentence = format_author_sentence(author_value)
|
||||||
|
if author_sentence:
|
||||||
|
sentences.append(ensure_sentence(author_sentence))
|
||||||
|
return " ".join(sentences).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def build_outro_text(
|
||||||
|
metadata: Optional[Mapping[str, Any]],
|
||||||
|
fallback_basename: str,
|
||||||
|
) -> str:
|
||||||
|
"""Build the outro/closing text from metadata."""
|
||||||
|
normalized = normalize_metadata_map(metadata)
|
||||||
|
fallback_title = Path(fallback_basename).stem if fallback_basename else ""
|
||||||
|
title = (
|
||||||
|
normalized.get("title")
|
||||||
|
or normalized.get("book_title")
|
||||||
|
or normalized.get("album")
|
||||||
|
or fallback_title
|
||||||
|
)
|
||||||
|
author_value = ""
|
||||||
|
for candidate in ("authors", "author", "album_artist", "artist", "writer", "composer"):
|
||||||
|
value = normalized.get(candidate)
|
||||||
|
if value:
|
||||||
|
author_value = value
|
||||||
|
break
|
||||||
|
author_sentence = format_author_sentence(author_value)
|
||||||
|
authors_fragment = (
|
||||||
|
author_sentence[3:].strip() if author_sentence.lower().startswith("by ") else author_sentence.strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
if title and authors_fragment:
|
||||||
|
closing_line = f"The end of {title} from {authors_fragment}"
|
||||||
|
elif title:
|
||||||
|
closing_line = f"The end of {title}"
|
||||||
|
elif authors_fragment:
|
||||||
|
closing_line = f"The end from {authors_fragment}"
|
||||||
|
else:
|
||||||
|
closing_line = "The end"
|
||||||
|
|
||||||
|
series_name, series_number = extract_series_metadata(normalized)
|
||||||
|
series_sentence = format_series_sentence(series_name, series_number)
|
||||||
|
|
||||||
|
sentences: List[str] = [ensure_sentence(closing_line)]
|
||||||
|
if series_sentence:
|
||||||
|
sentences.append(ensure_sentence(series_sentence))
|
||||||
|
|
||||||
|
return " ".join(sentence for sentence in sentences if sentence).strip()
|
||||||
@@ -62,6 +62,10 @@ from abogen.domain.metadata_helpers import (
|
|||||||
extract_series_metadata as _extract_series_metadata,
|
extract_series_metadata as _extract_series_metadata,
|
||||||
format_series_sentence as _format_series_sentence,
|
format_series_sentence as _format_series_sentence,
|
||||||
)
|
)
|
||||||
|
from abogen.domain.title_builder import (
|
||||||
|
build_title_intro_text as _build_title_intro_text,
|
||||||
|
build_outro_text as _build_outro_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from .service import Job, JobStatus
|
from .service import Job, JobStatus
|
||||||
@@ -167,81 +171,6 @@ def _coerce_truthy(value: Any, default: bool = True) -> bool:
|
|||||||
_OUTPUT_SANITIZE_RE = re.compile(r"[^\w\-_.]+")
|
_OUTPUT_SANITIZE_RE = re.compile(r"[^\w\-_.]+")
|
||||||
|
|
||||||
|
|
||||||
def _build_title_intro_text(
|
|
||||||
metadata: Optional[Mapping[str, Any]],
|
|
||||||
fallback_basename: str,
|
|
||||||
) -> str:
|
|
||||||
normalized = _normalize_metadata_map(metadata)
|
|
||||||
fallback_title = Path(fallback_basename).stem if fallback_basename else ""
|
|
||||||
title = normalized.get("title") or normalized.get("book_title") or normalized.get("album") or fallback_title
|
|
||||||
if not title:
|
|
||||||
title = fallback_title
|
|
||||||
subtitle = normalized.get("subtitle") or normalized.get("sub_title")
|
|
||||||
if subtitle and title and subtitle.casefold() == title.casefold():
|
|
||||||
subtitle = ""
|
|
||||||
author_value = ""
|
|
||||||
for candidate in ("artist", "album_artist", "author", "authors", "writer", "composer"):
|
|
||||||
value = normalized.get(candidate)
|
|
||||||
if value:
|
|
||||||
author_value = value
|
|
||||||
break
|
|
||||||
|
|
||||||
series_name, series_number = _extract_series_metadata(normalized)
|
|
||||||
series_sentence = _format_series_sentence(series_name, series_number)
|
|
||||||
|
|
||||||
sentences: List[str] = []
|
|
||||||
if series_sentence:
|
|
||||||
sentences.append(_ensure_sentence(series_sentence))
|
|
||||||
if title:
|
|
||||||
sentences.append(_ensure_sentence(title))
|
|
||||||
if subtitle:
|
|
||||||
sentences.append(_ensure_sentence(subtitle))
|
|
||||||
author_sentence = _format_author_sentence(author_value)
|
|
||||||
if author_sentence:
|
|
||||||
sentences.append(_ensure_sentence(author_sentence))
|
|
||||||
return " ".join(sentences).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _build_outro_text(
|
|
||||||
metadata: Optional[Mapping[str, Any]],
|
|
||||||
fallback_basename: str,
|
|
||||||
) -> str:
|
|
||||||
normalized = _normalize_metadata_map(metadata)
|
|
||||||
fallback_title = Path(fallback_basename).stem if fallback_basename else ""
|
|
||||||
title = (
|
|
||||||
normalized.get("title")
|
|
||||||
or normalized.get("book_title")
|
|
||||||
or normalized.get("album")
|
|
||||||
or fallback_title
|
|
||||||
)
|
|
||||||
author_value = ""
|
|
||||||
for candidate in ("authors", "author", "album_artist", "artist", "writer", "composer"):
|
|
||||||
value = normalized.get(candidate)
|
|
||||||
if value:
|
|
||||||
author_value = value
|
|
||||||
break
|
|
||||||
author_sentence = _format_author_sentence(author_value)
|
|
||||||
authors_fragment = author_sentence[3:].strip() if author_sentence.lower().startswith("by ") else author_sentence.strip()
|
|
||||||
|
|
||||||
if title and authors_fragment:
|
|
||||||
closing_line = f"The end of {title} from {authors_fragment}"
|
|
||||||
elif title:
|
|
||||||
closing_line = f"The end of {title}"
|
|
||||||
elif authors_fragment:
|
|
||||||
closing_line = f"The end from {authors_fragment}"
|
|
||||||
else:
|
|
||||||
closing_line = "The end"
|
|
||||||
|
|
||||||
series_name, series_number = _extract_series_metadata(normalized)
|
|
||||||
series_sentence = _format_series_sentence(series_name, series_number)
|
|
||||||
|
|
||||||
sentences: List[str] = [_ensure_sentence(closing_line)]
|
|
||||||
if series_sentence:
|
|
||||||
sentences.append(_ensure_sentence(series_sentence))
|
|
||||||
|
|
||||||
return " ".join(sentence for sentence in sentences if sentence).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _spec_to_voice_ids(spec: Any) -> Set[str]:
|
def _spec_to_voice_ids(spec: Any) -> Set[str]:
|
||||||
text = str(spec or "").strip()
|
text = str(spec or "").strip()
|
||||||
if not text:
|
if not text:
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
"""Tests for domain/title_builder.py."""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
|
||||||
|
from abogen.domain.title_builder import build_title_intro_text, build_outro_text
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuildTitleIntroText:
|
||||||
|
def test_empty_metadata(self):
|
||||||
|
result = build_title_intro_text({}, "book.epub")
|
||||||
|
assert result == "book."
|
||||||
|
|
||||||
|
def test_title_from_metadata(self):
|
||||||
|
result = build_title_intro_text({"title": "My Book"}, "book.epub")
|
||||||
|
assert result == "My Book."
|
||||||
|
|
||||||
|
def test_title_fallback_basename(self):
|
||||||
|
result = build_title_intro_text({}, "my_book.epub")
|
||||||
|
assert result == "my_book."
|
||||||
|
|
||||||
|
def test_with_author(self):
|
||||||
|
result = build_title_intro_text({"title": "My Book", "author": "John Doe"}, "book.epub")
|
||||||
|
assert result == "My Book. By John Doe."
|
||||||
|
|
||||||
|
def test_with_subtitle(self):
|
||||||
|
result = build_title_intro_text({"title": "My Book", "subtitle": "A Tale"}, "book.epub")
|
||||||
|
assert result == "My Book. A Tale."
|
||||||
|
|
||||||
|
def test_duplicate_title_subtitle(self):
|
||||||
|
result = build_title_intro_text({"title": "My Book", "subtitle": "My Book"}, "book.epub")
|
||||||
|
assert result == "My Book."
|
||||||
|
|
||||||
|
def test_with_series(self):
|
||||||
|
result = build_title_intro_text({"title": "My Book", "series": "Series", "series_index": "3"}, "book.epub")
|
||||||
|
assert result == "Book 3 of the Series. My Book."
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuildOutroText:
|
||||||
|
def test_empty(self):
|
||||||
|
result = build_outro_text({}, "book.epub")
|
||||||
|
assert result == "The end of book."
|
||||||
|
|
||||||
|
def test_title_only(self):
|
||||||
|
result = build_outro_text({"title": "My Book"}, "book.epub")
|
||||||
|
assert result == "The end of My Book."
|
||||||
|
|
||||||
|
def test_author_only(self):
|
||||||
|
result = build_outro_text({"author": "John Doe"}, "book.epub")
|
||||||
|
assert result == "The end of book from John Doe."
|
||||||
|
|
||||||
|
def test_title_and_author(self):
|
||||||
|
result = build_outro_text({"title": "My Book", "author": "John Doe"}, "book.epub")
|
||||||
|
assert result == "The end of My Book from John Doe."
|
||||||
|
|
||||||
|
def test_with_series(self):
|
||||||
|
result = build_outro_text({"title": "My Book", "series": "Series", "series_index": "3"}, "book.epub")
|
||||||
|
assert "The end of My Book." in result
|
||||||
|
assert "Book 3 of the Series." in result
|
||||||
Reference in New Issue
Block a user