# -*- coding: utf-8 -*-
"""
:File: EuljiroWorship/core/generator/utils/slide_exporter.py
:Author: Benjamin Jaedon Choi - https://github.com/saintbenjamin
:Affiliated Church: The Eulji-ro Presbyterian Church [대한예수교장로회(통합) 을지로교회]
:Address: The Eulji-ro Presbyterian Church, 24-10, Eulji-ro 20-gil, Jung-gu, Seoul 04549, South Korea
:Telephone: +82-2-2266-3070
:E-mail: euljirochurch [at] G.M.A.I.L. (replace [at] with @ and G.M.A.I.L as you understood.)
:License: MIT License with Attribution Requirement (see LICENSE file for details); Copyright (c) 2025 The Eulji-ro Presbyterian Church.
Slide export pipeline for converting generator slide data into
overlay-ready slide blocks.
This module defines the :class:`core.generator.utils.slide_exporter.SlideExporter` class, which takes structured
slide dictionaries produced by the generator UI and transforms them
into a flattened sequence of slides suitable for real-time overlay
display. The exporter applies style-specific rules such as line
segmentation, verse splitting, and style normalization.
"""
from core.config import constants
from core.generator.utils.text_splitter import split_by_length
from core.generator.utils.segment_utils import segment_lyrics_for_export
[docs]
class SlideExporter:
"""
Convert generator-format slides into overlay-ready slide blocks.
This exporter applies style-specific transformation rules, including automatic
line splitting, slide segmentation, and style normalization.
Supported styles and behaviors:
- ``lyrics`` / ``hymn`` / ``anthem``
- Lyrics are split into two-line slides using blank-line boundaries.
- Hymn slides are exported as ``lyrics``.
- Anthem slides preserve caption and optional choir name.
- ``verse``
- Each (reference, verse) pair is split by character length.
- ``respo``
- Each response line is exported as an individual ``verse``-style slide.
- ``intro`` / ``blank``
- Passed through with minimal transformation.
- Other styles
- Passed through unchanged.
Args:
settings (dict | None):
Optional exporter settings.
Supported keys:
- ``max_chars`` (int):
Maximum characters per text chunk.
Attributes:
max_chars (int):
Maximum characters per exported text chunk.
"""
[docs]
def __init__(self, settings=None):
"""
Initialize the slide exporter.
See also :py:data:`core.config.constants.MAX_CHARS`.
Args:
settings (dict | None):
Optional configuration dictionary.
If provided, ``max_chars`` controls the maximum number of
characters allowed per exported text block.
"""
self.max_chars = (settings or {}).get("max_chars", constants.MAX_CHARS)
[docs]
def export(self, raw_slides: list[dict]) -> list[dict]:
"""
Transform generator slides into a flat list of overlay slides.
The exporter iterates over generator-format slide dictionaries
and applies style-dependent rules to produce a sequence of
overlay-ready slides.
Args:
raw_slides (list[dict]):
List of slide dictionaries produced by the generator.
Each dictionary is expected to include at least:
- "style"
- "caption"
- "headline"
Returns:
list[dict]:
A flattened list of slide dictionaries suitable for
overlay display.
"""
output = []
for slide in raw_slides:
style = slide.get("style", "lyrics")
caption = slide.get("caption", "").strip()
headline = slide.get("headline", "").strip()
image_path = slide.get("image_path", "")
# Lyrics, Hymn, Anthem
if style in {"lyrics", "anthem", "hymn"}:
if style == "anthem":
# Extract choir name if not separately stored
caption_main = slide.get("caption", "").strip()
caption_choir = slide.get("caption_choir", "").strip()
if not caption_choir:
parts = caption_main.split()
if len(parts) == 2:
caption_main, caption_choir = parts[0], parts[1]
else:
caption_choir = ""
slide["caption"] = caption_main
slide["caption_choir"] = caption_choir
split_slides = segment_lyrics_for_export(slide)
for s in split_slides:
export_style = "lyrics" if style == "hymn" else style
s["style"] = export_style
if style == "anthem":
s["caption_choir"] = slide.get("caption_choir", "")
output.append(s)
continue
# Bible verses
elif style == "verse":
lines = [line.strip() for line in headline.splitlines() if line.strip()]
i = 0
while i < len(lines) - 1:
ref = lines[i]
verse_text = lines[i + 1]
chunks = self._split_text(verse_text)
for chunk in chunks:
output.append({
"style": "verse",
"caption": ref,
"headline": chunk
})
i += 2
if i < len(lines):
output.append({
"style": "verse",
"caption": lines[i],
"headline": ""
})
continue
# Responsive reading (each line = 1 slide)
elif style == "respo":
for line in headline.splitlines():
if line.strip():
output.append({
"style": "verse",
"caption": caption,
"headline": line.strip()
})
continue
# Intro or blank
elif style == "intro":
output.append({
"style": "intro",
"caption": caption,
"headline": headline
})
continue
elif style == "blank":
output.append({
"style": "blank",
"caption": "",
"headline": ""
})
continue
# Passthrough for other styles
else:
output.append({
"style": style,
"caption": caption,
"headline": headline
})
return output
[docs]
def _split_text(self, text: str) -> list[str]:
"""
Split a text string into smaller chunks by character count.
This helper delegates to :func:`core.generator.utils.text_splitter.split_by_length`, using the configured
maximum character length for exported slides.
Args:
text (str):
Input text to be split.
Returns:
list[str]:
List of text chunks, each within the configured length.
"""
return split_by_length(text, max_chars=self.max_chars)