aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-05 13:24:18 +0200
committerDanilo M. <danix@danix.xyz>2026-10-05 13:24:18 +0200
commit50ccd6bad7e791179a7000811fcff139619238fb (patch)
tree069d1c4dde2ee30e59b2515100dd9c0220603632
parent20e515b79796789a835a76aaf5656f5370764968 (diff)
downloadfanfictioner-50ccd6bad7e791179a7000811fcff139619238fb.tar.gz
fanfictioner-50ccd6bad7e791179a7000811fcff139619238fb.zip
Fit the montage to the window and put characters before the scene text
The candidate montage filled the whole terminal, hiding the key prompt. Show a copy resized to the kitty window minus four rows. Character descriptions came after the scene text, so a scene detail such as a change of dress was overridden by the character sheet. List the characters first and let the scene text follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rwxr-xr-xfanfictioner24
1 files changed, 19 insertions, 5 deletions
diff --git a/fanfictioner b/fanfictioner
index 010b521..7d53bce 100755
--- a/fanfictioner
+++ b/fanfictioner
@@ -309,8 +309,9 @@ def build_json(plan):
pages = []
for p in plan["pages"]:
poses = parse_poses(p["pose"])
- parts = [style, sentence(p["text"])]
- parts += [f"{sentence(plan['characters'][n])} {sentence(poses[n])}" for n in p["characters"]]
+ # Characters before the scene text, so a scene detail (a change of dress) overrides the sheet.
+ parts = [style] + [f"{sentence(plan['characters'][n])} {sentence(poses[n])}" for n in p["characters"]]
+ parts.append(sentence(p["text"]))
parts.append(sentence(p["framing"]))
if p["lettering"]:
parts.append(f'Title lettering at the top of the image reading exactly "{p["lettering"]}". '
@@ -404,6 +405,19 @@ def generate(d, profile, prompt, size, refs=()):
return files, seed, err
+def fit(m):
+ """A copy of the montage short enough to leave the key prompt visible below it."""
+ try:
+ w, h = map(int, subprocess.run(["kitty", "+kitten", "icat", "--print-window-size"],
+ capture_output=True, text=True).stdout.strip().split("x"))
+ rows = shutil.get_terminal_size().lines
+ v = m.with_name(f"{m.stem}_view.png")
+ subprocess.run(["magick", str(m), "-resize", f"{w}x{h * (rows - 4) // rows}>", str(v)], check=True)
+ return v
+ except (OSError, ValueError, subprocess.CalledProcessError):
+ return m
+
+
def show(files, seed, d):
m = d / f"{seed}_montage.png"
labelled = [x for i, f in enumerate(files) for x in ("-label", f"{i + 1} seed {seed + i}", str(f))]
@@ -417,7 +431,7 @@ def show(files, seed, d):
return
print(m)
try:
- subprocess.run(["kitty", "+kitten", "icat", str(m)])
+ subprocess.run(["kitty", "+kitten", "icat", str(fit(m))])
except OSError as e:
print(f"cannot show image: {e}")
@@ -682,10 +696,10 @@ def selftest():
assert (j1["n"], j1["orientation"], j1["model"]) == (1, "landscape", "")
assert (j2["n"], j2["orientation"], j2["model"], j2["edits"]) == (2, "portrait", "krea2", [])
assert j1["base_prompt"] == (
- "Soft watercolor, muted colors. They reach the roof at dusk. "
+ "Soft watercolor, muted colors. "
"Young woman, long red hair, green eyes, grey coat. Left, facing right. "
"Tall man, short beard, blue jacket. Center, facing camera. "
- "Small girl, black bob, yellow raincoat. Right, sitting. "
+ "Small girl, black bob, yellow raincoat. Right, sitting. They reach the roof at dusk. "
"Wide shot, eye level, landscape. No text, no speech bubbles."), j1["base_prompt"]
assert j2["base_prompt"] == ("Soft watercolor, muted colors. Nobody is there any more. "
"Wide shot, portrait. No text, no speech bubbles."), j2["base_prompt"]