From a7c08b27cd9e6dc1ad611fbcf9fce82b57629bda Mon Sep 17 00:00:00 2001 From: Sebastian Annies Date: Sat, 28 Feb 2026 09:45:19 +0200 Subject: [PATCH] get multiline right! --- pycaption/subtitler_image_based.py | 20 +++++++++++++++++--- tests/test_subtitler_image_based.py | 15 ++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pycaption/subtitler_image_based.py b/pycaption/subtitler_image_based.py index e317c5c7..d0f84143 100644 --- a/pycaption/subtitler_image_based.py +++ b/pycaption/subtitler_image_based.py @@ -227,9 +227,22 @@ def write_images( def printLine(self, draw: ImageDraw, caption_list: Caption, fnt: ImageFont, position: str = 'bottom', align: str = 'left'): ascender, descender = fnt.getmetrics() - line_spacing = (ascender + abs(descender)) * 0.75 # Basic line height without extra padding + line_spacing = (ascender + abs(descender)) * 0.80 # Basic line height without extra padding + + # Split captions containing \n into separate single-line captions + flat_captions = [] + for caption in caption_list: + text = caption.get_text() + if '\n' in text: + for line in text.split('\n'): + flat_captions.append(Caption(caption.start, caption.end, + [CaptionNode.create_text(line)], + layout_info=caption.layout_info)) + else: + flat_captions.append(caption) + lines_written = 0 - for caption in caption_list[::-1]: + for caption in flat_captions[::-1]: text = caption.get_text() l, t, r, b = draw.textbbox((0, 0), text, font=fnt, align=align) @@ -266,7 +279,8 @@ def printLine(self, draw: ImageDraw, caption_list: Caption, fnt: ImageFont, posi if position != 'source': x = self.video_width / 2 - r / 2 if position == 'bottom': - # Place baseline at 5% from the bottom; descender runs below + # Place baseline at 8% from the bottom; descender runs below + # Additional lines grow upward from the same anchor y = self.video_height * 0.92 - ascender - lines_written * line_spacing elif position == 'top': y = 10 + lines_written * line_spacing diff --git a/tests/test_subtitler_image_based.py b/tests/test_subtitler_image_based.py index 702cf605..da81acc9 100644 --- a/tests/test_subtitler_image_based.py +++ b/tests/test_subtitler_image_based.py @@ -1,11 +1,14 @@ import os +from unittest import skip import pytest from PIL import Image, ImageDraw, ImageFont +from pycaption import SRTReader from pycaption.base import Caption, CaptionNode from pycaption.exceptions import CaptionRendererError +from pycaption.filtergraph import FiltergraphWriter from pycaption.geometry import Layout, Point, Size, UnitEnum from pycaption.subtitler_image_based import SubtitleImageBasedWriter @@ -112,12 +115,20 @@ class TestBaselineAlignment: NO_DESCENDER = "AHLEN" # no descenders WITH_DESCENDER = "gypsy" # descenders: g, y, p + WITH_DESCENDER_TOP = "gypsy\nAHLEN" # descenders: g, y, p + WITH_DESCENDER_BOTTOM = "AHLEN\ngypsy" # descenders: g, y, p + COMBOS = [ ("no_desc_x2", [NO_DESCENDER, NO_DESCENDER]), ("desc_x2", [WITH_DESCENDER, WITH_DESCENDER]), ("top_no_bottom_yes", [NO_DESCENDER, WITH_DESCENDER]), ("top_yes_bottom_no", [WITH_DESCENDER, NO_DESCENDER]), + ("one_line-no", [NO_DESCENDER]), + ("one_line-yes", [WITH_DESCENDER]), + ("one_line-yes", [WITH_DESCENDER]), + ("two-in-one-a", [WITH_DESCENDER_TOP]), + ("tow-in-one-b", [WITH_DESCENDER_BOTTOM]), ] @pytest.fixture(params=COMBOS, ids=[c[0] for c in COMBOS]) @@ -140,5 +151,7 @@ def test_baseline_visual(self, combo, tmp_path): guide.line([(0, baseline_y), (width, baseline_y)], fill=(255, 0, 0, 200), width=1) out = tmp_path / f"baseline_{name}.png" + out = f"tests/baseline_samples/baseline_{name}.png" img.save(str(out)) - print(f"\nSaved: {out}") \ No newline at end of file + print(f"\nSaved: {out}") +