From 43812349e747ccc8b6480152382fa6a5be00570e Mon Sep 17 00:00:00 2001 From: LingMoe404 <54352563+LingMoe404@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:22:04 +0800 Subject: [PATCH 1/3] Add --aspect-ratio for backend-verified framing The tested backend ignores the request's size field when framing an image: a request for 1024x1536 comes back square, and both the native and Responses paths behave the same way. Prompt wording is what actually controls the frame, so expose that as a first-class option rather than leaving users to discover it. --aspect-ratio W:H appends explicit framing guidance to the prompt and then verifies the returned image against the requested ratio through the existing --size-policy. Requests outside 1:3..3:1 are rejected locally because the backend clamps them and would otherwise surface as a confusing mismatch. The size field is left untouched: the ratio is prompt guidance, not a disguised size. The returned image is never resized or cropped to hide a mismatch, matching the project's existing boundary. Covered by 43 offline tests. Live checks returned the requested ratio on every attempt (2:3 -> 1024x1536, 16:9 -> 1672x941, 21:9 -> 1916x821, 1:1 -> 1254x1254, 9:18 -> 887x1774, 3:1 -> 2172x724), including edit mode. --- CHANGELOG.md | 6 ++ README.md | 29 +++++- codex_imagegen_cli/cli.py | 86 ++++++++++++++-- tests/test_aspect_ratio.py | 202 +++++++++++++++++++++++++++++++++++++ 4 files changed, 315 insertions(+), 8 deletions(-) create mode 100644 tests/test_aspect_ratio.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a546855..ed78caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses semantic versioning before its first stable release. +## [Unreleased] + +### Added + +- `--aspect-ratio W:H` for `generate`, `edit`, and `batch`. The tested backend ignores the request's `size` field when framing the image, so the ratio is appended to the prompt as explicit framing guidance and the returned image is verified against it with `--size-policy`. Ratios from `1:3` to `3:1` are accepted; requests beyond that range are clamped by the backend and reported as a mismatch. `--aspect-ratio` and `--size` are mutually exclusive. + ## [0.2.0] - 2026-09-15 ### Changed diff --git a/README.md b/README.md index 8bc4f18..02a42ea 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ Each line can be either a JSON string prompt or an object with: Per-job validation or backend failures are reported and later jobs continue unless `--fail-fast` is set. Any failed job produces exit status 1. +Image options such as `--size` and `--aspect-ratio` are CLI flags and apply to every job in the run. + ## Useful Flags - `--prompt-file prompt.txt`: read the prompt from a file @@ -197,7 +199,8 @@ Per-job validation or backend failures are reported and later jobs continue unle - `--background auto|opaque`: direct `background` parameter. Explicit `transparent` is unsupported on the tested backend and rejected locally. - `--quality auto|low|medium|high`: direct `quality` parameter. - `--size auto|WIDTHxHEIGHT`: requested dimensions; see constraints below. -- `--size-policy warn|error`: on a dimension mismatch, warn and save (default), or fail without writing the output. +- `--aspect-ratio W:H`: requested aspect ratio (for example `16:9`, `2:3`, `21:9`). The backend ignores the request's `size` field, so the ratio is sent as prompt guidance and the returned image is verified against it. Not allowed together with `--size`. See aspect ratios below. +- `--size-policy warn|error`: on a dimension or aspect-ratio mismatch, warn and save (default), or fail without writing the output. - `--style-image PATH`: edit mode only. Use this image as the style reference; `--image` stays the content image and `--prompt` becomes optional extra guidance. - `--output-format auto|png|webp`: output file format. Default: infer from `--out`; `.webp` writes WebP, everything else writes PNG. - `--webp-quality 1..100`: WebP encoder quality. Default: `85`. @@ -271,6 +274,30 @@ The accepted image preference values are: Requested dimensions are not guaranteed. The CLI reports actual dimensions and warns on mismatches by default. `--size-policy error` rejects a mismatched output without saving it, even with `--force`; the generation has already occurred and may have consumed usage. The CLI does not resize or crop output to match the request. +### Aspect ratios + +`--size` does not control the aspect ratio. The tested backend ignores the request's `size` field for framing and returns its own dimensions, so `--size 1024x1536` can come back as a square. What does control the ratio is the prompt: `--aspect-ratio W:H` appends explicit framing guidance to the prompt and then verifies the returned image against that ratio using `--size-policy`. + +```bash +codex-imagegen generate --prompt "A lighthouse at dusk" --out out.png --aspect-ratio 2:3 +codex-imagegen generate --prompt "A wide banner" --out banner.png --aspect-ratio 21:9 +``` + +Ratios are accepted between `1:3` and `3:1`. Live checks on the tested backend returned the requested ratio every time, with the frame size following the ratio at a roughly constant pixel budget: + +| Requested | Returned | Returned ratio | +| --- | --- | --- | +| `1:1` | `1254x1254` | 1.000 | +| `3:4` | `1086x1448` | 0.750 | +| `2:3` | `1024x1536` | 0.667 | +| `9:16` | `941x1672` | 0.563 | +| `1:2` | `887x1774` | 0.500 | +| `16:9` | `1672x941` | 1.777 | +| `21:9` | `1916x821` | 2.334 | +| `3:1` | `2172x724` | 3.000 | + +Requests beyond 3:1 are clamped by the backend to roughly 3:1, so `--aspect-ratio 4:1` will be reported as a mismatch. Because the exact returned size is chosen by the backend, treat `--aspect-ratio` as a way to get the framing right, not as a way to pin exact pixels; crop or pad afterwards if a fixed pixel size is required. The CLI never silently resizes or crops the returned image to hide a mismatch. + `n` is handled by the CLI by running one hosted image request per output path. For edit jobs, repeated outputs may wait for the per-minute input-image quota window before retrying; if the bucket stays full, retries back off progressively. Edit input images are compacted locally to WebP before upload by default. This keeps request bodies smaller and reduces pressure on the input-image quota. diff --git a/codex_imagegen_cli/cli.py b/codex_imagegen_cli/cli.py index 71cfa0a..c0b17ba 100644 --- a/codex_imagegen_cli/cli.py +++ b/codex_imagegen_cli/cli.py @@ -48,6 +48,11 @@ INPUT_IMAGE_RATE_LIMIT_DELAYS = (65.0, 130.0, 260.0, 300.0) DEFAULT_INPUT_MAX_EDGE = 1536 DEFAULT_INPUT_WEBP_QUALITY = 90 +# The backend honors an aspect ratio only as prompt guidance and clamps beyond 1:3..3:1. +ASPECT_RATIO_LIMIT = 3.0 +# Live checks returned the requested ratio within 0.1%; 2% still rejects a real mismatch +# such as 2:3 (0.667) answered with 9:16 (0.563). +ASPECT_RATIO_TOLERANCE = 0.02 class CliError(Exception): @@ -665,7 +670,7 @@ def _native_payload( ) -> dict[str, Any]: payload = { "model": NATIVE_REQUEST_MODEL, - "prompt": prompt, + "prompt": _prompt_with_aspect_ratio(prompt, args), "size": args.size, "quality": args.quality, "background": args.background, @@ -684,7 +689,9 @@ def _responses_payload( mode: str, images: Sequence[Path] | None = None, ) -> dict[str, Any]: - content: list[dict[str, Any]] = [{"type": "input_text", "text": prompt}] + content: list[dict[str, Any]] = [ + {"type": "input_text", "text": _prompt_with_aspect_ratio(prompt, args)} + ] if images: if len(images) > MAX_EDIT_IMAGES: raise CliError(f"Edit supports at most {MAX_EDIT_IMAGES} images.") @@ -732,6 +739,7 @@ def _write_response_image( webp_quality: int, requested_size: str = "auto", size_policy: str = "warn", + requested_aspect_ratio: str | None = None, ) -> Path: _check_output(output_path, force) if len(encoded) > ((MAX_IMAGE_BYTES + 2) // 3) * 4: @@ -750,12 +758,20 @@ def _write_response_image( actual_size = f"{image.width}x{image.height}" except (OSError, ValueError, Image.DecompressionBombError) as exc: raise CliError(f"Image generation returned an invalid image: {exc}") from exc - if requested_size != "auto" and requested_size != actual_size: - message = f"Requested {requested_size}, backend returned {actual_size} for {output_path}." + mismatch = None + if requested_aspect_ratio is not None: + if _aspect_ratio_mismatch(requested_aspect_ratio, actual_size): + mismatch = ( + f"Requested aspect ratio {requested_aspect_ratio}, backend returned " + f"{actual_size} for {output_path}." + ) + elif requested_size != "auto" and requested_size != actual_size: + mismatch = f"Requested {requested_size}, backend returned {actual_size} for {output_path}." + if mismatch is not None: if size_policy == "error": - raise CliError(message + " Output was not written (--size-policy error).") + raise CliError(mismatch + " Output was not written (--size-policy error).") _warn( - message + mismatch + " Saving the original dimensions; use --size-policy error to reject mismatches." ) _write_image_bytes( @@ -960,6 +976,7 @@ def _call_backend( webp_quality=args.webp_quality, requested_size=args.size, size_policy=args.size_policy, + requested_aspect_ratio=args.aspect_ratio, ) ) # Publish each successful path immediately, even if a later image fails. @@ -1195,12 +1212,23 @@ def _add_image_args(parser: argparse.ArgumentParser) -> None: default="auto", help="Direct quality parameter.", ) - parser.add_argument( + ratio_group = parser.add_mutually_exclusive_group() + ratio_group.add_argument( "--size", type=_parse_size, default="auto", help="Requested image dimensions: auto or WIDTHxHEIGHT. Backend may return a different size.", ) + ratio_group.add_argument( + "--aspect-ratio", + type=_parse_aspect_ratio, + default=None, + help=( + "Requested aspect ratio as W:H (for example 16:9, 2:3, 21:9). " + "Sent as prompt guidance because the backend ignores the size field; " + "the result is verified against the requested ratio. Not allowed with --size." + ), + ) parser.add_argument( "--size-policy", choices=["warn", "error"], @@ -1260,6 +1288,50 @@ def _parse_size(value: str) -> str: ) +def _parse_aspect_ratio(value: str) -> str: + match = re.fullmatch(r"([1-9][0-9]{0,2}):([1-9][0-9]{0,2})", value) + if match: + width, height = map(int, match.groups()) + ratio = width / height + if 1 / ASPECT_RATIO_LIMIT <= ratio <= ASPECT_RATIO_LIMIT: + return f"{width}:{height}" + raise argparse.ArgumentTypeError( + "Aspect ratio must be W:H between 1:3 and 3:1, for example 16:9, 2:3, or 21:9." + ) + + +def _aspect_ratio_value(aspect_ratio: str) -> float: + width, height = aspect_ratio.split(":") + return int(width) / int(height) + + +def _prompt_with_aspect_ratio(prompt: str, args: argparse.Namespace) -> str: + ratio = getattr(args, "aspect_ratio", None) + return _aspect_ratio_prompt(prompt, ratio) if ratio else prompt + + +def _aspect_ratio_prompt(prompt: str, aspect_ratio: str) -> str: + """Ask for the ratio in words: the backend ignores the request's size field.""" + value = _aspect_ratio_value(aspect_ratio) + if value == 1: + instruction = "The frame must be a 1:1 square." + elif value > 1: + instruction = ( + f"The frame must be in {aspect_ratio} landscape format, wider than it is tall." + ) + else: + instruction = ( + f"The frame must be in {aspect_ratio} portrait format, taller than it is wide." + ) + return f"{prompt}\n\n{instruction}" + + +def _aspect_ratio_mismatch(aspect_ratio: str, size: str) -> bool: + width, height = (int(part) for part in size.split("x")) + requested = _aspect_ratio_value(aspect_ratio) + return abs(width / height - requested) / requested > ASPECT_RATIO_TOLERANCE + + def _validate_common(args: argparse.Namespace) -> Path: if not math.isfinite(args.timeout) or args.timeout <= 0: raise CliError("--timeout must be a positive finite number.") diff --git a/tests/test_aspect_ratio.py b/tests/test_aspect_ratio.py new file mode 100644 index 0000000..d44f132 --- /dev/null +++ b/tests/test_aspect_ratio.py @@ -0,0 +1,202 @@ +"""Aspect-ratio request coverage; all transport/auth data is synthetic.""" + +import base64 +import json +from io import BytesIO + +import pytest +from PIL import Image +from test_regressions import fake_auth + +from codex_imagegen_cli import cli + + +def image_result(width: int, height: int) -> str: + out = BytesIO() + Image.new("RGB", (width, height), "blue").save(out, "PNG") + return base64.b64encode(out.getvalue()).decode() + + +def ratio_args(tmp_path, ratio): + return cli.build_parser().parse_args( + [ + "generate", + "--backend", + "responses", + "--prompt", + "test", + "--out", + str(tmp_path / "out.png"), + "--model", + "test-model", + "--aspect-ratio", + ratio, + ] + ) + + +@pytest.mark.parametrize( + "value", + ["1:1", "16:9", "9:16", "2:3", "3:2", "21:9", "9:18", "2:1", "1:2", "3:1", "1:3"], +) +def test_supported_aspect_ratios_are_accepted(value): + assert cli._parse_aspect_ratio(value) == value + + +@pytest.mark.parametrize( + "value", ["0:1", "1:0", "16:0", "0:9", "4:1", "1:4", "abc", "16", "16x9", ""] +) +def test_invalid_aspect_ratios_are_rejected(value): + with pytest.raises(cli.argparse.ArgumentTypeError): + cli._parse_aspect_ratio(value) + + +def test_aspect_ratio_is_mutually_exclusive_with_size(tmp_path, capsys): + with pytest.raises(SystemExit): + cli.build_parser().parse_args( + [ + "generate", + "--prompt", + "test", + "--out", + str(tmp_path / "out.png"), + "--size", + "1024x1024", + "--aspect-ratio", + "16:9", + ] + ) + assert "not allowed with argument --size" in capsys.readouterr().err + + +@pytest.mark.parametrize( + ("ratio", "expected"), + [ + ("16:9", "16:9 landscape format, wider than it is tall"), + ("2:3", "2:3 portrait format, taller than it is wide"), + ("1:1", "1:1 square"), + ], +) +def test_prompt_gains_ratio_orientation_guidance(ratio, expected): + prompt = cli._aspect_ratio_prompt("A lighthouse.", ratio) + assert prompt.startswith("A lighthouse.") + assert expected in prompt + + +def test_prompt_ratio_guidance_survives_an_empty_prompt(): + assert cli._aspect_ratio_prompt("", "16:9").strip() != "" + + +def test_send_prompt_carries_ratio_guidance(tmp_path, monkeypatch): + calls = [] + + def post(url, **kw): + calls.append(kw["payload"]) + return {"data": [{"b64_json": image_result(1024, 1536)}]} + + monkeypatch.setattr(cli, "_post_json", post) + monkeypatch.setattr(cli, "_load_ready_auth", lambda a: (fake_auth(), tmp_path / "auth.json")) + monkeypatch.setattr(cli, "_auth_headers", lambda a: {}) + args = ratio_args(tmp_path, "2:3") + args.backend = "native" + cli._call_native_backend( + args=args, mode="generate", prompt="A lighthouse.", output_path=tmp_path / "out.png" + ) + assert "2:3 portrait format" in calls[0]["prompt"] + + +@pytest.mark.parametrize( + ("ratio", "size", "mismatch"), + [ + ("2:3", "1024x1536", False), + ("2:3", "1254x1254", True), + ("2:3", "941x1672", True), + ("16:9", "1672x941", False), + ("16:9", "1254x1254", True), + ("1:1", "1254x1254", False), + ("21:9", "1916x821", False), + ("21:9", "2141x734", True), + ("3:2", "1536x1024", False), + ], +) +def test_aspect_ratio_mismatch_detection(ratio, size, mismatch): + assert cli._aspect_ratio_mismatch(ratio, size) is mismatch + + +def _write(encoded, out, ratio, policy, force=False): + return cli._write_response_image( + encoded, + out, + force=force, + output_format="png", + webp_quality=85, + size_policy=policy, + requested_aspect_ratio=ratio, + ) + + +def test_matching_ratio_does_not_warn(tmp_path, capsys): + _write(image_result(1024, 1536), tmp_path / "out.png", "2:3", "error") + assert "Warning" not in capsys.readouterr().err + + +def test_ratio_mismatch_warns_and_keeps_original_dimensions(tmp_path, capsys): + out = tmp_path / "out.png" + _write(image_result(1254, 1254), out, "2:3", "warn") + assert "Requested aspect ratio 2:3, backend returned 1254x1254" in capsys.readouterr().err + with Image.open(out) as image: + assert image.size == (1254, 1254) + + +def test_ratio_mismatch_error_does_not_write_output(tmp_path): + out = tmp_path / "out.png" + with pytest.raises(cli.CliError, match="Requested aspect ratio 2:3"): + _write(image_result(1254, 1254), out, "2:3", "error") + assert not out.exists() + + +def test_ratio_mismatch_error_preserves_existing_output(tmp_path): + out = tmp_path / "out.png" + out.write_bytes(b"old") + with pytest.raises(cli.CliError, match="Requested aspect ratio 2:3"): + _write(image_result(1254, 1254), out, "2:3", "error", force=True) + assert out.read_bytes() == b"old" + + +def test_ratio_request_keeps_size_in_payload_untouched(tmp_path): + """The backend ignores size; the ratio must not be smuggled into it.""" + args = ratio_args(tmp_path, "16:9") + payload = cli._responses_payload(prompt="x", args=args, mode="generate") + assert payload["tools"][0]["size"] == "auto" + + +def test_batch_applies_the_cli_ratio_to_every_job(tmp_path, monkeypatch): + """Batch image options come from CLI flags, matching --size and --quality.""" + jobs = tmp_path / "jobs.jsonl" + jobs.write_text( + json.dumps({"prompt": "a", "out": "a.png"}) + + "\n" + + json.dumps({"prompt": "b", "out": "b.png"}) + + "\n", + encoding="utf-8", + ) + seen = [] + + def run_one(*, args, mode, prompt, output_path, image_paths=None, log_prefix=""): + seen.append((args.aspect_ratio, mode, prompt)) + return True + + monkeypatch.setattr(cli, "_run_one", run_one) + code = cli.main( + [ + "batch", + "--input", + str(jobs), + "--out-dir", + str(tmp_path / "out"), + "--aspect-ratio", + "16:9", + ] + ) + assert code == 0 + assert seen == [("16:9", "generate", "a"), ("16:9", "generate", "b")] From cf22c0d3e8453388581ff4a2cd4ea932ed99fc6d Mon Sep 17 00:00:00 2001 From: LingMoe404 <54352563+LingMoe404@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:26:33 +0800 Subject: [PATCH 2/3] Correct the out-of-range ratio documentation The previous wording said a request beyond 3:1 "will be reported as a mismatch", but --aspect-ratio rejects anything outside 1:3..3:1 during argument parsing, so a 4:1 request never reaches the backend. Describe what the CLI actually does, and keep the live observation that motivated the local check. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02a42ea..8e0ff0d 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ Ratios are accepted between `1:3` and `3:1`. Live checks on the tested backend r | `21:9` | `1916x821` | 2.334 | | `3:1` | `2172x724` | 3.000 | -Requests beyond 3:1 are clamped by the backend to roughly 3:1, so `--aspect-ratio 4:1` will be reported as a mismatch. Because the exact returned size is chosen by the backend, treat `--aspect-ratio` as a way to get the framing right, not as a way to pin exact pixels; crop or pad afterwards if a fixed pixel size is required. The CLI never silently resizes or crops the returned image to hide a mismatch. +Requests beyond 3:1 are rejected locally, because the backend clamps them to roughly 3:1 (a live `4:1` request came back at `2172x724`) and would otherwise surface as a mismatch only after spending usage. Because the exact returned size is chosen by the backend, treat `--aspect-ratio` as a way to get the framing right, not as a way to pin exact pixels; crop or pad afterwards if a fixed pixel size is required. The CLI never silently resizes or crops the returned image to hide a mismatch. `n` is handled by the CLI by running one hosted image request per output path. For edit jobs, repeated outputs may wait for the per-minute input-image quota window before retrying; if the bucket stays full, retries back off progressively. From f5c69240f6e75118c586c2492db36ccf3719ce64 Mon Sep 17 00:00:00 2001 From: LingMoe404 <54352563+LingMoe404@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:58:33 +0800 Subject: [PATCH 3/3] Lead the prompt with the aspect-ratio instruction Framing now precedes the scene description: The frame must be in 2:3 portrait format, taller than it is wide. A lighthouse at dusk The published GPT-Image2 style-library templates put the ratio constraint before the scene ("must be written first, otherwise the model defaults to a phone 9:16"), and the ratio appears near the start of the prompt in most of that library's 544 cases. A/B checks showed no behavioural difference between the two orders on the tested backend, including the conflict case of a portrait phone-screenshot prompt asked to render at 21:9, so order follows the documented convention rather than a measured advantage. Live re-checks after the change still returned the requested ratio on every attempt (2:3 -> 1024x1536, 21:9 -> 1916x821, 16:9 -> 1672x941, 1:1 -> 1254x1254, and 2:3 on an edit). --- README.md | 10 +++++++++- codex_imagegen_cli/cli.py | 8 ++++++-- tests/test_aspect_ratio.py | 9 ++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e0ff0d..bdb32f4 100644 --- a/README.md +++ b/README.md @@ -276,13 +276,21 @@ Requested dimensions are not guaranteed. The CLI reports actual dimensions and w ### Aspect ratios -`--size` does not control the aspect ratio. The tested backend ignores the request's `size` field for framing and returns its own dimensions, so `--size 1024x1536` can come back as a square. What does control the ratio is the prompt: `--aspect-ratio W:H` appends explicit framing guidance to the prompt and then verifies the returned image against that ratio using `--size-policy`. +`--size` does not control the aspect ratio. The tested backend ignores the request's `size` field for framing and returns its own dimensions, so `--size 1024x1536` can come back as a square. What does control the ratio is the prompt: `--aspect-ratio W:H` prepends explicit framing guidance to the prompt and then verifies the returned image against that ratio using `--size-policy`. ```bash codex-imagegen generate --prompt "A lighthouse at dusk" --out out.png --aspect-ratio 2:3 codex-imagegen generate --prompt "A wide banner" --out banner.png --aspect-ratio 21:9 ``` +The framing instruction leads the prompt, so the ratio is set before the scene is described: + +```text +The frame must be in 2:3 portrait format, taller than it is wide. + +A lighthouse at dusk +``` + Ratios are accepted between `1:3` and `3:1`. Live checks on the tested backend returned the requested ratio every time, with the frame size following the ratio at a roughly constant pixel budget: | Requested | Returned | Returned ratio | diff --git a/codex_imagegen_cli/cli.py b/codex_imagegen_cli/cli.py index c0b17ba..d2e0955 100644 --- a/codex_imagegen_cli/cli.py +++ b/codex_imagegen_cli/cli.py @@ -1311,7 +1311,11 @@ def _prompt_with_aspect_ratio(prompt: str, args: argparse.Namespace) -> str: def _aspect_ratio_prompt(prompt: str, aspect_ratio: str) -> str: - """Ask for the ratio in words: the backend ignores the request's size field.""" + """Ask for the ratio in words: the backend ignores the request's size field. + + The instruction leads the prompt so the framing is set before the scene is + described, matching how the style-library templates order ratio constraints. + """ value = _aspect_ratio_value(aspect_ratio) if value == 1: instruction = "The frame must be a 1:1 square." @@ -1323,7 +1327,7 @@ def _aspect_ratio_prompt(prompt: str, aspect_ratio: str) -> str: instruction = ( f"The frame must be in {aspect_ratio} portrait format, taller than it is wide." ) - return f"{prompt}\n\n{instruction}" + return f"{instruction}\n\n{prompt}" def _aspect_ratio_mismatch(aspect_ratio: str, size: str) -> bool: diff --git a/tests/test_aspect_ratio.py b/tests/test_aspect_ratio.py index d44f132..a695eae 100644 --- a/tests/test_aspect_ratio.py +++ b/tests/test_aspect_ratio.py @@ -79,8 +79,15 @@ def test_aspect_ratio_is_mutually_exclusive_with_size(tmp_path, capsys): ) def test_prompt_gains_ratio_orientation_guidance(ratio, expected): prompt = cli._aspect_ratio_prompt("A lighthouse.", ratio) - assert prompt.startswith("A lighthouse.") assert expected in prompt + assert "A lighthouse." in prompt + + +def test_ratio_instruction_leads_the_prompt(): + """Framing leads the scene, matching the style-library template ordering.""" + prompt = cli._aspect_ratio_prompt("A lighthouse.", "16:9") + assert prompt.startswith("The frame must be") + assert prompt.index("The frame must be") < prompt.index("A lighthouse.") def test_prompt_ratio_guidance_survives_an_empty_prompt():