summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/story/artist.go11
-rw-r--r--internal/story/runner.go54
-rw-r--r--internal/version.go2
3 files changed, 66 insertions, 1 deletions
diff --git a/internal/story/artist.go b/internal/story/artist.go
index fa62511..6e69435 100644
--- a/internal/story/artist.go
+++ b/internal/story/artist.go
@@ -57,6 +57,13 @@ const (
// helperRetryPause waits before retrying when the model returns an empty
// response — typically caused by free-tier RPM exhaustion between rapid calls.
helperRetryPause = 15 * time.Second
+
+ // renderingRequirement is appended to every image prompt (cover, story pages,
+ // back cover, gallery) to push the model toward photorealistic output even
+ // within a comic grid layout. Centralised here so it is easy to tune.
+ renderingRequirement = "RENDERING REQUIREMENT: every panel and illustration must look " +
+ "like a real photograph — photorealistic skin texture, fabric detail, lighting, " +
+ "and environment. NOT a drawing, painting, or illustration. Real-world photo quality.\n"
)
// comicStyles is the pool from which the page style is drawn each run.
@@ -400,6 +407,7 @@ func buildCoverPrompt(storyText, style, bible string) string {
"All text on the cover (cover lines, banners, labels) MUST be in Bulgarian "+
"Cyrillic script. The masthead title must also be rendered in a striking comic-book font.\n\n"+
"Art style: %s.%s\n"+
+ renderingRequirement+
"TRADITIONAL COMIC BOOK FRONT COVER — portrait orientation, single full-bleed illustration.\n"+
"NO panel grid. NO speech bubbles.\n"+
"MANDATORY MASTHEAD — the most important visual element on this cover:\n"+
@@ -466,6 +474,7 @@ func buildStoryPagePrompt(section string, pageNum, totalPages int, style, bible
"Each panel is separated by a thin black gutter line. "+
"All 4 panels must be clearly distinct scenes — NOT one continuous image. "+
"The full image area must be covered by the 4 panels with no empty space.\n"+
+ renderingRequirement+
"STRICT CONSISTENCY RULES — apply to every single panel:\n"+
" • Human characters: identical face, AGE APPEARANCE, hair colour/style, and clothing "+
"to the reference — a child must never look older or younger than defined.\n"+
@@ -531,6 +540,7 @@ func buildBackCoverPrompt(storyText, style, bible, blurb string) string {
"All visible text (blurb box, labels, banners) MUST be in Bulgarian Cyrillic script. "+
"English text anywhere on the back cover is STRICTLY FORBIDDEN.\n\n"+
"Art style: %s.%s\n"+
+ renderingRequirement+
"TRADITIONAL COMIC BOOK BACK COVER — portrait orientation, single full-bleed illustration.\n"+
"NO panel grid. NO speech bubbles.\n"+
"Layout rules (must follow exactly):\n"+
@@ -566,6 +576,7 @@ func buildGalleryPagePrompt(style, bible string, galleryNum int) string {
bibleBlock := bibleSection(bible, fmt.Sprintf("gallery page %d", galleryNum))
return fmt.Sprintf(
"Art style: %s.%s\n"+
+ renderingRequirement+
"FULL-BLEED CHARACTER ART PAGE — portrait orientation, single illustration.\n"+
"NO text of any kind. NO title. NO labels. NO speech bubbles. NO panel borders. NO UI elements.\n"+
"This is a text-free variant cover / gallery page. Pure art only.\n\n"+
diff --git a/internal/story/runner.go b/internal/story/runner.go
index f69b2f4..107e584 100644
--- a/internal/story/runner.go
+++ b/internal/story/runner.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "strings"
"codeberg.org/snonux/totalrecall/internal/batch"
)
@@ -143,6 +144,16 @@ func (r *Runner) Run(batchFile string) error {
return err
}
+ if err := r.saveVocabularyFile(result.StoryText, entries, slug, comicsDir); err != nil {
+ // Non-fatal: vocabulary file is a learning aid, not required for the comic.
+ fmt.Fprintf(os.Stderr, "Warning: could not write vocabulary file: %v\n", err)
+ }
+
+ if err := r.saveThemeFile(slug, comicsDir); err != nil {
+ // Non-fatal: theme file is a convenience record for reproduction.
+ fmt.Fprintf(os.Stderr, "Warning: could not write theme file: %v\n", err)
+ }
+
r.drawComicPages(result.StoryText, result.Bible, slug, entries)
return r.handleNarration(result.StoryText, slug, comicsDir)
@@ -203,6 +214,49 @@ func (r *Runner) saveStoryText(text, titleSlug, dir string) error {
return nil
}
+// saveVocabularyFile writes <slug>_comic_vocabulary.txt — a learning aid that
+// lists the vocabulary words with translations followed by the full story text,
+// so the reader can study the words in context.
+func (r *Runner) saveVocabularyFile(storyText string, entries []batch.WordEntry, titleSlug, dir string) error {
+ path := filepath.Join(dir, titleSlug+"_comic_vocabulary.txt")
+ var sb strings.Builder
+
+ sb.WriteString("# Vocabulary Words\n\n")
+ for _, e := range entries {
+ if e.Translation != "" {
+ sb.WriteString(fmt.Sprintf(" %s — %s\n", e.Bulgarian, e.Translation))
+ } else {
+ sb.WriteString(fmt.Sprintf(" %s\n", e.Bulgarian))
+ }
+ }
+
+ sb.WriteString("\n# Story Text\n\n")
+ sb.WriteString(strings.TrimSpace(storyText))
+ sb.WriteString("\n")
+
+ if err := os.WriteFile(path, []byte(sb.String()), 0644); err != nil {
+ return fmt.Errorf("failed to write vocabulary file: %w", err)
+ }
+ fmt.Printf("Vocabulary saved: %s\n", path)
+ return nil
+}
+
+// saveThemeFile writes <slug>_theme.txt containing the --story-theme value used
+// for this run, so the comic can be reproduced by passing the same theme again.
+func (r *Runner) saveThemeFile(titleSlug, dir string) error {
+ theme := ""
+ if r.config != nil {
+ theme = r.config.Theme
+ }
+ path := filepath.Join(dir, titleSlug+"_theme.txt")
+ content := theme + "\n"
+ if err := os.WriteFile(path, []byte(content), 0644); err != nil {
+ return fmt.Errorf("failed to write theme file: %w", err)
+ }
+ fmt.Printf("Theme saved: %s\n", path)
+ return nil
+}
+
// saveTTSPlaceholder writes <slug>_tts_todo.txt as a fallback when narration
// is unavailable or fails.
func (r *Runner) saveTTSPlaceholder(titleSlug, dir string) error {
diff --git a/internal/version.go b/internal/version.go
index 39b6037..e65f021 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -1,3 +1,3 @@
package internal
-const Version = "0.11.0"
+const Version = "0.12.0"