From 42513af1a5e81dae05699fb4acdb79cad67a258c Mon Sep 17 00:00:00 2001 From: kevin9327 Date: Wed, 21 Oct 2026 20:30:35 +0900 Subject: [PATCH] Read the stage size from and accept single-quoted viewBox --- skills/motion2code/scripts/_m2c.py | 12 +++++-- skills/motion2code/scripts/render_anim.py | 2 +- tests/test_stage_size.py | 44 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tests/test_stage_size.py diff --git a/skills/motion2code/scripts/_m2c.py b/skills/motion2code/scripts/_m2c.py index 64d638e..a33291c 100644 --- a/skills/motion2code/scripts/_m2c.py +++ b/skills/motion2code/scripts/_m2c.py @@ -126,10 +126,16 @@ def launch(p): def stage_size(html: str | Path) -> tuple[int, int]: - """Stage size from the first (falls back to 1920x1080).""" + """Stage size from the viewBox of , else the first (falls back to 1920x1080).""" txt = Path(html).read_text(encoding="utf-8", errors="ignore") - m = re.search(r']*viewBox="\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)', txt) - return (int(float(m.group(1))), int(float(m.group(2)))) if m else (1920, 1080) + vb = r"""\bviewBox=["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)""" + svgs = re.findall(r"]*>", txt, flags=re.I) + stage = [t for t in svgs if re.search(r"""\bid=["']stage["']""", t)] + for tag in stage + svgs: + m = re.search(vb, tag) + if m: + return int(float(m.group(1))), int(float(m.group(2))) + return 1920, 1080 def parse_size(s: str | None, html: str | Path) -> tuple[int, int]: diff --git a/skills/motion2code/scripts/render_anim.py b/skills/motion2code/scripts/render_anim.py index aac04d5..3c7e668 100644 --- a/skills/motion2code/scripts/render_anim.py +++ b/skills/motion2code/scripts/render_anim.py @@ -53,7 +53,7 @@ def main(): ap.add_argument("--fps", type=float, default=24) ap.add_argument("--duration", type=float, help="seconds to render (default: window.anim.duration)") ap.add_argument("--start", type=float, default=0.0) - ap.add_argument("--size", help="viewport WxH (default: the first , else 1920x1080)") + ap.add_argument("--size", help="viewport WxH (default: the viewBox of , else the first , else 1920x1080)") ap.add_argument("--width", type=int, help="scale the output video to this width") ap.add_argument("--crf", type=int, default=16) ap.add_argument("--root", help="folder to serve (default: the page's folder)") diff --git a/tests/test_stage_size.py b/tests/test_stage_size.py new file mode 100644 index 0000000..28220dd --- /dev/null +++ b/tests/test_stage_size.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Unit tests for _m2c.stage_size (stdlib only). + + python3 tests/test_stage_size.py +""" +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "skills" / "motion2code" / "scripts")) +from _m2c import stage_size # noqa: E402 + + +def size_of(html: str): + with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False) as f: + f.write(html) + try: + return stage_size(f.name) + finally: + Path(f.name).unlink() + + +class StageSize(unittest.TestCase): + def test_plain_stage(self): + self.assertEqual(size_of(''), (1080, 1920)) + + def test_stage_after_icon_sprite(self): + html = ('' + '') + self.assertEqual(size_of(html), (1080, 1920)) + + def test_single_quoted_viewbox(self): + self.assertEqual(size_of(""), (1080, 1920)) + + def test_no_stage_id_uses_first_svg(self): + self.assertEqual(size_of(''), (800, 600)) + + def test_fallback(self): + self.assertEqual(size_of("
"), (1920, 1080)) + + +if __name__ == "__main__": + unittest.main()