diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-20 08:01:01 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-20 08:01:01 +0300 |
| commit | af2aa5e5efa82d3bc7cf5530e0a1eee72ba99244 (patch) | |
| tree | 609634b874bd4a82e5df7d9b56d8505cac14fdd6 | |
| parent | a35ca6f40de73e93372ade6f2d74c231389799e2 (diff) | |
Fix task 25 follow-up: stop PNG prompt leaks and keep comics consistent
| -rw-r--r-- | internal/comic/artist.go | 28 | ||||
| -rw-r--r-- | internal/comic/comic_test.go | 96 | ||||
| -rw-r--r-- | internal/comic/image_validation.go | 68 | ||||
| -rw-r--r-- | internal/comic/localization_test.go | 26 | ||||
| -rw-r--r-- | internal/comic/types.go | 1 | ||||
| -rw-r--r-- | prompts/back_cover_prompt.md | 20 | ||||
| -rw-r--r-- | prompts/cover_prompt.md | 28 | ||||
| -rw-r--r-- | prompts/gallery_page_prompt.md | 17 | ||||
| -rw-r--r-- | prompts/rendering_requirement.md | 12 | ||||
| -rw-r--r-- | prompts/rendering_requirement_end.md | 2 | ||||
| -rw-r--r-- | prompts/story_page_prompt.md | 34 |
11 files changed, 261 insertions, 71 deletions
diff --git a/internal/comic/artist.go b/internal/comic/artist.go index 54f28cf..53f43f8 100644 --- a/internal/comic/artist.go +++ b/internal/comic/artist.go @@ -173,6 +173,12 @@ func (a *Artist) generateWithRetry(ctx context.Context, prompt, outputFile, labe for attempt := 1; attempt <= attempts; attempt++ { callCtx, cancel := withTimeout(ctx, helperTimeout) err := a.imageProvider.GenerateImage(callCtx, prompt, outputFile) + if err == nil { + if leakErr := validateImagePromptLeakageFn(callCtx, outputFile, label); leakErr != nil { + _ = os.Remove(outputFile) + err = leakErr + } + } cancel() if err == nil { return nil @@ -311,10 +317,10 @@ func buildPanelLayout(section string, pagePanels []string) string { labels := [4]string{"TOP-LEFT", "TOP-RIGHT", "BOTTOM-LEFT", "BOTTOM-RIGHT"} if len(pagePanels) == 4 && pagePanels[0] != "" && pagePanels[1] != "" && pagePanels[2] != "" && pagePanels[3] != "" { var sb strings.Builder - sb.WriteString("MANDATORY PANEL LAYOUT — divide the image into exactly 4 panels in a 2×2 grid.\n") - sb.WriteString("Draw each panel EXACTLY as described below — these are the precise scenes to illustrate:\n") + sb.WriteString("ЗАДЪЛЖИТЕЛНО ОФОРМЛЕНИЕ НА ПАНЕЛИТЕ — раздели изображението точно на 4 панела в решетка 2×2.\n") + sb.WriteString("Оформи всеки панел ТОЧНО както е описано по-долу:\n") for i, label := range labels { - sb.WriteString(fmt.Sprintf(" • %s panel: %s\n", label, pagePanels[i])) + sb.WriteString(fmt.Sprintf(" • %s панел: %s\n", label, pagePanels[i])) } return sb.String() } @@ -327,19 +333,19 @@ func buildPanelLayout(section string, pagePanels []string) string { } excerpt += "…" } - return "MANDATORY PANEL LAYOUT — divide the image into exactly 4 panels in a 2×2 grid:\n" + - " • TOP-LEFT panel: scene 1 from the excerpt\n" + - " • TOP-RIGHT panel: scene 2 from the excerpt\n" + - " • BOTTOM-LEFT panel: scene 3 from the excerpt\n" + - " • BOTTOM-RIGHT panel: scene 4 from the excerpt\n" + - "Story excerpt (ALL panels must illustrate THIS excerpt only):\n\n" + excerpt + "\n" + return "ЗАДЪЛЖИТЕЛНО ОФОРМЛЕНИЕ НА ПАНЕЛИТЕ — раздели изображението точно на 4 панела в решетка 2×2:\n" + + " • TOP-LEFT панел: сцена 1 от откъса\n" + + " • TOP-RIGHT панел: сцена 2 от откъса\n" + + " • BOTTOM-LEFT панел: сцена 3 от откъса\n" + + " • BOTTOM-RIGHT панел: сцена 4 от откъса\n" + + "Откъс от историята (всички панели трябва да илюстрират САМО този откъс):\n\n" + excerpt + "\n" } func blurbBoxInstruction(blurb string) string { if strings.TrimSpace(blurb) == "" { - return "a rectangular text box (white or cream background, thin black border) near the bottom, styled like a classic back-cover synopsis box" + return "правоъгълно текстово поле (бял или кремав фон, тънка черна рамка) близо до дъното, в стил на класически синопсис на задната корица" } - return fmt.Sprintf("a rectangular text box (white or cream background, thin black border) near the bottom displaying this blurb text in italic type:\n %q", blurb) + return fmt.Sprintf("правоъгълно текстово поле (бял или кремав фон, тънка черна рамка) близо до дъното, което показва този текст в курсив:\n %q", blurb) } func errorsJoin(errs ...error) error { diff --git a/internal/comic/comic_test.go b/internal/comic/comic_test.go index 3506303..3b89871 100644 --- a/internal/comic/comic_test.go +++ b/internal/comic/comic_test.go @@ -47,7 +47,7 @@ func TestBuildPanelLayoutUsesFallbackExcerpt(t *testing.T) { t.Parallel() got := buildPanelLayout("one two three four five", nil) - if !strings.Contains(got, "Story excerpt") { + if !strings.Contains(got, "Откъс от историята") { t.Fatalf("buildPanelLayout() = %q", got) } } @@ -142,6 +142,12 @@ func TestDrawComicPagesReturnsErrorWhenRenderFails(t *testing.T) { func TestArtistAndRunnerEndToEndWithFakes(t *testing.T) { t.Parallel() + originalLeakValidation := validateImagePromptLeakageFn + validateImagePromptLeakageFn = func(context.Context, string, string) error { return nil } + t.Cleanup(func() { + validateImagePromptLeakageFn = originalLeakValidation + }) + tmpDir := t.TempDir() img := fakeImageProvider{t: t} genText := &scriptedTextProvider{responses: []string{ @@ -194,8 +200,11 @@ func TestArtistAndRunnerEndToEndWithFakes(t *testing.T) { func TestRunnerPropagatesRenderFailures(t *testing.T) { originalSleep := sleep sleep = func(time.Duration) {} + originalLeakValidation := validateImagePromptLeakageFn + validateImagePromptLeakageFn = func(context.Context, string, string) error { return nil } t.Cleanup(func() { sleep = originalSleep + validateImagePromptLeakageFn = originalLeakValidation }) runner := NewRunner(&RunnerConfig{ @@ -226,6 +235,49 @@ func TestRunnerPropagatesRenderFailures(t *testing.T) { } } +func TestDrawComicPagesUsesOneStyleAcrossTheWholePDF(t *testing.T) { + originalLeakValidation := validateImagePromptLeakageFn + validateImagePromptLeakageFn = func(context.Context, string, string) error { return nil } + t.Cleanup(func() { + validateImagePromptLeakageFn = originalLeakValidation + }) + + renderer := &recordingPromptRenderer{} + artist := NewArtist(&ArtistConfig{ + ImageProvider: fakeImageProvider{t: t}, + Prompts: renderer, + OutputDir: t.TempDir(), + UltraRealistic: false, + }) + + if _, err := artist.DrawComicPages(context.Background(), "история", "библия", "slug", []WordEntry{{Word: "ябълка"}}, nil); err != nil { + t.Fatalf("DrawComicPages() error = %v", err) + } + + var style string + var promptCount int + for _, call := range renderer.calls { + if !isImagePromptTemplate(call.name) { + continue + } + promptCount++ + gotStyle, ok := call.data["Style"].(string) + if !ok || gotStyle == "" { + t.Fatalf("image prompt %q missing style: %#v", call.name, call.data) + } + if style == "" { + style = gotStyle + continue + } + if gotStyle != style { + t.Fatalf("image prompt styles diverged: first=%q later=%q in %q", style, gotStyle, call.name) + } + } + if promptCount == 0 { + t.Fatal("no image prompts recorded") + } +} + func TestConvertToStereoFallsBackToCopyWhenFFmpegMissing(t *testing.T) { originalLookPath := lookPath lookPath = func(string) (string, error) { @@ -274,6 +326,48 @@ func (fakePromptRenderer) RenderPrompt(name string, data any) (string, error) { } } +type recordingPromptRenderer struct { + calls []recordedPromptCall +} + +type recordedPromptCall struct { + name string + data map[string]any +} + +func (r *recordingPromptRenderer) RenderPrompt(name string, data any) (string, error) { + if m, ok := data.(map[string]any); ok { + copyData := make(map[string]any, len(m)) + for k, v := range m { + copyData[k] = v + } + r.calls = append(r.calls, recordedPromptCall{name: name, data: copyData}) + } else { + r.calls = append(r.calls, recordedPromptCall{name: name}) + } + switch name { + case storySystemPromptTemplate: + return "system prompt", nil + case storyPromptTemplate, storyFullPromptTemplate: + return "prompt", nil + case coverPromptTemplate, storyPagePromptTemplate, galleryPagePromptTemplate, backCoverPromptTemplate: + return "image prompt", nil + case blurbSystemTemplate, introSystemTemplate, conclusionSystemTemplate: + return "teaser prompt", nil + default: + return "", errors.New("unexpected template") + } +} + +func isImagePromptTemplate(name string) bool { + switch name { + case coverPromptTemplate, storyPagePromptTemplate, galleryPagePromptTemplate, backCoverPromptTemplate: + return true + default: + return false + } +} + type fakeTextProvider struct{ text string } func (f fakeTextProvider) Name() string { return "fake-text" } diff --git a/internal/comic/image_validation.go b/internal/comic/image_validation.go new file mode 100644 index 0000000..475cb96 --- /dev/null +++ b/internal/comic/image_validation.go @@ -0,0 +1,68 @@ +package comic + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "strings" +) + +var imageLeakMarkers = []string{ + "mandatory language rule", + "this is a bulgarian comic book", + "bulgarian comic book", + "mandatory panel layout", + "mandatory speech bubbles", + "ultra-realistic rendering", + "final lock", + "photorealism", + "character & setting reference", + "this is a text-free character gallery page", + "story page", + "gallery page", + "back cover", + "cover lines", + "story teaser", + "story ending hint", + "art style:", + "no panel grid", + "no speech bubbles", + "no text of any kind", +} + +var validateImagePromptLeakageFn = validateImagePromptLeakage + +func validateImagePromptLeakage(ctx context.Context, outputFile, label string) error { + if ctx == nil { + ctx = context.Background() + } + + tesseractPath, err := exec.LookPath("tesseract") + if err != nil { + return nil + } + + cmd := exec.CommandContext(ctx, tesseractPath, outputFile, "stdout", "-l", "eng", "--psm", "11") + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &bytes.Buffer{} + if err := cmd.Run(); err != nil { + return fmt.Errorf("%s OCR failed: %w", label, err) + } + + ocr := strings.ToLower(out.String()) + if marker, ok := findImageLeakMarker(ocr); ok { + return fmt.Errorf("%s contains prompt leakage marker %q", label, marker) + } + return nil +} + +func findImageLeakMarker(text string) (string, bool) { + for _, marker := range imageLeakMarkers { + if strings.Contains(text, marker) { + return marker, true + } + } + return "", false +} diff --git a/internal/comic/localization_test.go b/internal/comic/localization_test.go index a1609f3..8855732 100644 --- a/internal/comic/localization_test.go +++ b/internal/comic/localization_test.go @@ -1,6 +1,9 @@ package comic -import "testing" +import ( + "strings" + "testing" +) func TestLocalizedBrandName(t *testing.T) { t.Parallel() @@ -40,3 +43,24 @@ func TestValidateNoPromptLeakage(t *testing.T) { t.Fatal("validateNoPromptLeakage() error = nil, want prompt leakage rejection") } } + +func TestFindImageLeakMarker(t *testing.T) { + t.Parallel() + + if marker, ok := findImageLeakMarker("calm text"); ok || marker != "" { + t.Fatalf("findImageLeakMarker() = %q, %v, want no match", marker, ok) + } + if marker, ok := findImageLeakMarker("mandatory language rule"); !ok || marker != "mandatory language rule" { + t.Fatalf("findImageLeakMarker() = %q, %v, want mandatory language rule match", marker, ok) + } +} + +func TestComicStylesStayComic(t *testing.T) { + t.Parallel() + + for _, style := range comicStyles { + if strings.Contains(strings.ToLower(style), "ultra realistic") || strings.Contains(strings.ToLower(style), "photograph") { + t.Fatalf("comicStyles contains hybrid or photorealistic style: %q", style) + } + } +} diff --git a/internal/comic/types.go b/internal/comic/types.go index 6689095..ba9a83d 100644 --- a/internal/comic/types.go +++ b/internal/comic/types.go @@ -82,7 +82,6 @@ var ( } comicStyles = []string{ - "ultra realistic comic strip with photographic detail and dramatic lighting", "classic American comic book with bold ink outlines, halftone dots, and primary colors", "Japanese manga with clean linework, expressive eyes, and speed lines", "retro 1960s pop art in the style of Roy Lichtenstein with thick outlines and Ben-Day dots", diff --git a/prompts/back_cover_prompt.md b/prompts/back_cover_prompt.md index 2150599..3a9d852 100644 --- a/prompts/back_cover_prompt.md +++ b/prompts/back_cover_prompt.md @@ -1,15 +1,15 @@ -{{if .Language}}MANDATORY LANGUAGE RULE: This is a {{.Language}} comic book. All visible text (blurb box, labels, banners) MUST be in {{.Script}} script. English text anywhere on the back cover is STRICTLY FORBIDDEN. +{{if .Language}}Български комикс. Всички видими надписи, ленти и етикети на задната корица са на {{.Script}}. Без латиница на задната корица. -{{end}}Art style: {{.Style}}.{{.RenderingRequirement}} -TRADITIONAL COMIC BOOK BACK COVER — {{if .RenderingRequirement}}single full-bleed image (photoreal — like a physical back-cover photo shoot){{else}}single full-bleed illustration{{end}}, landscape 16:9 format. -NO panel grid. NO speech bubbles. -{{if .Bible}}CHARACTER & SETTING REFERENCE (back cover — follow exactly, do NOT change clothing): +{{end}}Стил: {{.Style}}.{{.RenderingRequirement}} +ТРАДИЦИОННА ЗАДНА КО РИЦА НА КОМИКС — {{if .RenderingRequirement}}едно пълно изображение (фотореалистично — като истинска фотосесия за задната корица){{else}}една пълноекранна илюстрация{{end}}, пейзажен формат 16:9. +Без панелна решетка. Без балони за реплики. +{{if .Bible}}РЕФЕРЕНЦИЯ ЗА ПЕРСОНАЖИТЕ И СРЕДАТА (задна корица — следвай точно, без промяна на дрехите): {{.Bible}} -{{end}}Layout rules (must follow exactly): - • MAIN ART: a calm, warm, resolved scene filling the upper 60% of the cover — EXACTLY the named characters from the story (as described in the reference above) in a peaceful or triumphant ending moment, with the full story setting behind them. The scene must look PHOTOGRAPHED (live-action), not drawn. Do NOT invent new characters or use generic stand-ins. - • BLURB BOX: {{.BlurbBox}} - • BOTTOM STRIP: barcode box bottom-left (black-and-white barcode graphic), series title '{{.SeriesTitle}}' bottom-right — classic comic book back-cover production design. -IMPORTANT: only the characters named in the reference may appear on this back cover. Same age, same face, same clothing, same animals as in the interior pages. LANGUAGE REMINDER: all text in {{.Script}} script — see rule at top. Story ending hint: +{{end}}Правила за композицията: + • ОСНОВНА СЦЕНА: спокойно, топло и завършено изображение в горните 60% на корицата — ТОЧНО описаните герои от историята в мирен или триумфален финален момент, с цялата среда зад тях. Сцената трябва да изглежда ФОТОГРАФСКА, не рисувана. + • ТЕКСТОВО ПОЛЕ: {{.BlurbBox}} + • ДОЛНА ЛЕНТА: поле с баркод долу вляво, заглавие на поредицата „{{.SeriesTitle}}“ долу вдясно — класическо комиксово оформление. +ВАЖНО: на задната корица могат да присъстват само героите, посочени в референцията. Същата възраст, лице и дрехи като в страниците. Всички надписи са на {{.Script}}. Подсказка за финала на историята: {{.StoryText}}{{if .RenderingRequirement}}{{.RenderingRequirementEnd}}{{end}} diff --git a/prompts/cover_prompt.md b/prompts/cover_prompt.md index 7303a9e..f824c6a 100644 --- a/prompts/cover_prompt.md +++ b/prompts/cover_prompt.md @@ -1,19 +1,19 @@ -{{if .Language}}MANDATORY LANGUAGE RULE: This is a {{.Language}} comic book. All text on the cover (cover lines, banners, labels) MUST be in {{.Script}} script. The masthead title must also be rendered in a striking comic-book font. +{{if .Language}}Български комикс. Всички надписи на корицата са на {{.Script}}, включително основното заглавие. -{{end}}Art style: {{.Style}}.{{.RenderingRequirement}} -TRADITIONAL COMIC BOOK FRONT COVER — {{if .RenderingRequirement}}single full-bleed image (photoreal — like a physical comic book cover photo shoot){{else}}single full-bleed illustration{{end}}, landscape 16:9 format. -NO panel grid. NO speech bubbles. -{{if .Bible}}CHARACTER & SETTING REFERENCE (cover — follow exactly, do NOT change clothing): +{{end}}Стил: {{.Style}}.{{.RenderingRequirement}} +ТРАДИЦИОННА ПРЕДНА КО РИКА НА КОМИКС — {{if .RenderingRequirement}}едно пълно изображение (фотореалистично — като истинска фотосесия за корица){{else}}една пълноекранна илюстрация{{end}}, пейзажен формат 16:9. +Без панелна решетка. Без балони за реплики. +{{if .Bible}}РЕФЕРЕНЦИЯ ЗА ПЕРСОНАЖИТЕ И СРЕДАТА (корица — следвай точно, без промяна на дрехите): {{.Bible}} -{{end}}MANDATORY MASTHEAD — the most important visual element on this cover: - • Invent a DRAMATIC, STORY-SPECIFIC comic book title that fits the characters and theme of the story teaser below (e.g. for a space story: 'ГАЛАКТИЧЕСКИ ГЕРОИ', for a mystery: 'ТАЙНАТА НА ГОРАТА'). The title must be in HUGE, dominant lettering across the very top of the cover — bold comic-book masthead font, thick outlines, bright contrasting colours (yellow, red, or white on dark), taking up the top 20% of the image. This title MUST be legible and unmissable. - • Directly below the main title, add a smaller subtitle banner: '{{.Subtitle}}' in a contrasting accent colour. - • Add a bold comic-book LOGO BUG (small circular or star-shaped badge) in the top-left corner — e.g. a planet, rocket, magnifying glass, sword — matching the story theme. The logo should feel like a real publisher imprint. -Remaining layout rules: - • MAIN ART: below the masthead, a dramatic {{if .RenderingRequirement}}photographed cinematic scene{{else}}illustration{{end}} of EXACTLY the named characters from the story (as described in the reference above) — same faces, same ages, same clothing, same animals. Do NOT invent new characters or use generic stand-ins. - • COVER LINES: 2–3 short {{.Language}} teaser phrases in bold display type (e.g. 'A GREAT ADVENTURE!' or 'THE MYSTERY UNFOLDS!') - • BOTTOM STRIP: price box bottom-left, issue number bottom-right — classic Silver-Age / Bronze-Age comic production design. -IMPORTANT: only the characters named in the reference may appear on this cover. Same age, same face, same clothing as in the interior pages. LANGUAGE REMINDER: all cover text in {{.Script}} script — see rule at top. Story teaser: +{{end}}ОСНОВНО ЗАГЛАВИЕ — най-важният визуален елемент на корицата: + • Измисли ДРАМАТИЧНО, СПЕЦИФИЧНО ЗА ИСТОРИЯТА комиксово заглавие, което подхожда на героите и темата на откъса по-долу (например за космическа история: „ГАЛАКТИЧЕСКИ ГЕРОИ“, за мистерия: „ТАЙНАТА НА ГОРАТА“). Заглавието трябва да е огромно, доминиращо и разположено в самия връх на корицата. + • Под основното заглавие добави по-малка лента с подзаглавие „{{.Subtitle}}“ в контрастен цвят. + • Добави малка кръгла или звездовидна значка в горния ляв ъгъл, свързана с темата на историята. +Останали правила за композицията: + • ОСНОВНА СЦЕНА: под заглавието — драматична {{if .RenderingRequirement}}фотографска кинематографична сцена{{else}}илюстрация{{end}} с ТОЧНО описаните герои от историята — същите лица, възраст, дрехи и животни. Не измисляй нови персонажи и не използвай заместители. + • КОРОВИ ФРАЗИ: 2–3 кратки фрази на {{.Language}} в удебелен дисплей шрифт. + • ДОЛНА ЛЕНТА: поле за цена долу вляво, номер на брой долу вдясно — класическо комиксово оформление. +ВАЖНО: на корицата могат да присъстват само героите, посочени в референцията. Същата възраст, лице и дрехи като в страниците. Всички надписи са на {{.Script}}. Откъс от историята: {{.StoryText}}{{if .RenderingRequirement}}{{.RenderingRequirementEnd}}{{end}} diff --git a/prompts/gallery_page_prompt.md b/prompts/gallery_page_prompt.md index 8bea1b9..b5dfca6 100644 --- a/prompts/gallery_page_prompt.md +++ b/prompts/gallery_page_prompt.md @@ -1,14 +1,13 @@ -Art style: {{.Style}}.{{.RenderingRequirement}} -FULL-BLEED SINGLE {{if .RenderingRequirement}}PHOTOGRAPH — must look 100% like a real camera shot (no illustration style){{else}}ILLUSTRATION{{end}} — landscape 16:9 format, ONE image only, NO grid, NO panels. -DO NOT split the image into multiple panels or sections. The ENTIRE canvas is ONE single scene. -NO text of any kind. NO title. NO labels. NO speech bubbles. NO panel borders. NO UI elements. -This is a text-free character gallery page. If ultra-realistic mode: pure cinematic photography only — not painted or comic art. -{{if .Bible}}CHARACTER & SETTING REFERENCE (gallery page — follow exactly, do NOT change clothing): +Стил: {{.Style}}.{{.RenderingRequirement}} +ПЪЛЕН КАДЪР {{if .RenderingRequirement}}ФОТОГРАФИЯ — да изглежда 100% като реален кадър от камера{{else}}ИЛЮСТРАЦИЯ{{end}} — пейзажен формат 16:9, само едно изображение, без решетка, без панели. +Не разделяй изображението на няколко панела или секции. Цялото поле трябва да е една единствена сцена. +Без текст от какъвто и да е вид. Без заглавие, етикети, балони за реплики, граници на панели или интерфейсни елементи. +{{if .Bible}}РЕФЕРЕНЦИЯ ЗА ПЕРСОНАЖИТЕ И СРЕДАТА (галериен кадър — следвай точно, без да сменяш дрехите): {{.Bible}} {{end}} -Composition: {{.Pose}} +Композиция: {{.Pose}} -The subject MUST be EXACTLY the main character(s) described in the reference above — same faces, same genders, same ages, same clothing. Include the companion animal if naturally present. Do NOT invent new characters or change any character's gender. Do NOT add any text overlays. -Background: the story's setting rendered with full cinematic atmosphere and colour mood.{{if .RenderingRequirement}}{{.RenderingRequirementEnd}}{{end}} +Героите трябва да са ТОЧНО описаните персонажи — същите лица, пол, възраст и дрехи. Ако има естествен спътник-животно, включи го. Не измисляй нови персонажи и не променяй пола им. Не добавяй текстови наслагвания. +Фон: средата на историята, предадена с пълна кинематографична атмосфера и цветово настроение.{{if .RenderingRequirement}}{{.RenderingRequirementEnd}}{{end}} diff --git a/prompts/rendering_requirement.md b/prompts/rendering_requirement.md index e60b1d7..8b6f312 100644 --- a/prompts/rendering_requirement.md +++ b/prompts/rendering_requirement.md @@ -1,6 +1,6 @@ -ULTRA-REALISTIC RENDERING (mandatory for this entire image): - • The output must look like a REAL PHOTOGRAPH or a high-budget live-action film still — shot on a real set or location with real actors, costumes, and props. - • Skin, hair, fabric, metal, and environments must show real-world texture, lens blur, and natural light — NOT ink, NOT cel shading, NOT painterly brushwork. - • FORBIDDEN overall styles: cartoon, anime, manga, comic-book line art, halftone dots, Ben-Day, visible outlines, storybook illustration, watercolor/oil-paint look, or any obviously drawn or stylized artwork. - • Speech bubbles, masthead lettering, and UI-like overlays (where the layout requires them) may look like graphic design ON TOP of the photo — the underlying scene must stay photographic. - • Gallery pages (no bubbles): the whole frame must be 100% photographic — no exception. +ФОТОРЕАЛИСТИЧЕН РЕЖИМ за това изображение: + • Картината трябва да изглежда като истинска фотография или кадър от високобюджетен жив филм. + • Кожата, косата, платът, металът и средата трябва да изглеждат естествени и реални, без рисуван или живописен ефект. + • Избягвай карикатурен, аниме, манга, комиксов или очевидно илюстративен вид. + • Текстови елементи, ако са нужни по композиция, трябва да изглеждат като част от фотографска корица или плакат. + • При галерийни кадри цялото изображение трябва да е изцяло фотографско. diff --git a/prompts/rendering_requirement_end.md b/prompts/rendering_requirement_end.md index c90af9f..36aa8cd 100644 --- a/prompts/rendering_requirement_end.md +++ b/prompts/rendering_requirement_end.md @@ -1 +1 @@ -FINAL LOCK — PHOTOREALISM: Entire image = camera-captured realism. If anything looks illustrated rather than photographed, the output is wrong. Do not drift toward comic art between panels or on gallery pages. +КРАЕН ЗАКЛЮЧИТЕЛЕН РЕЖИМ — ФОТОРЕАЛИЗЪМ: цялото изображение трябва да изглежда като заснето с камера. Ако нещо изглежда рисувано, резултатът е грешен. Не допускай комиксов вид между панелите или в галерийните кадри. diff --git a/prompts/story_page_prompt.md b/prompts/story_page_prompt.md index 5e8606a..81df066 100644 --- a/prompts/story_page_prompt.md +++ b/prompts/story_page_prompt.md @@ -1,22 +1,22 @@ -{{if .Language}}MANDATORY LANGUAGE RULE: This is a {{.Language}} comic book. Every word of text inside speech bubbles, thought bubbles, caption boxes, and panel labels MUST be written in {{.Script}} script (for example: "Hello! What are you doing? Hurry up!"). English text anywhere in the panels is STRICTLY FORBIDDEN — use ONLY {{.Language}}. +{{if .Language}}Български комикс: всички реплики, мисли, надписи и етикети в панелите трябва да са изцяло на {{.Script}}. Без латиница в самите панели. {{end}}{{.Words}} -Art style: {{.Style}}.{{.RenderingRequirement}} -Comic book story page {{.PageNum}} of {{.TotalPages}}. -{{if .Bible}}CHARACTER & SETTING REFERENCE (story page {{.PageNum}} of {{.TotalPages}} — follow exactly, do NOT change clothing): +Стил: {{.Style}}.{{.RenderingRequirement}} +Комиксова страница {{.PageNum}} от {{.TotalPages}}. +{{if .Bible}}РЕФЕРЕНЦИЯ ЗА ПЕРСОНАЖИТЕ И СРЕДАТА (страница {{.PageNum}} от {{.TotalPages}} — следвай точно, без промяна на дрехите): {{.Bible}} -{{end}}{{.PanelLayout}}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. -MANDATORY SPEECH BUBBLES — this is a comic book; characters MUST speak: - • At least 3 of the 4 panels MUST contain a speech bubble or thought bubble. - • If the panel description includes quoted dialogue, render it EXACTLY inside a speech bubble. - • Speech bubbles have a white background, black outline, and a tail pointing to the speaker. - • Thought bubbles use a cloud shape with small circles leading to the thinker. - • ALL bubble text is in {{.Script}} script — never Roman letters. -VARIETY MANDATE — every panel MUST differ from the others in at least 3 of these dimensions: camera angle (close-up, medium shot, wide shot, bird's-eye, low angle), character pose or action, location or background detail, lighting or time-of-day, and foreground objects. Repeating the same angle or composition across panels is FORBIDDEN. -{{.RenderingRequirement}}STRICT CONSISTENCY RULES — apply to every single panel: - • Human characters: identical face, AGE APPEARANCE, hair colour/style, and clothing to the reference — a child must never look older or younger as defined. - • Animal characters: identical breed, fur colour/pattern, markings, and eye colour — NEVER substitute a different animal or a generic version of the species. - • Clothing changes only if this page's description explicitly describes a change. - • LANGUAGE: all speech, thought, and caption text — {{.Script}} ONLY. +{{end}}{{.PanelLayout}}Всеки панел е разделен с тънка черна линия. И 4-те панела трябва да са ясно различни сцени, а не едно непрекъснато изображение. Цялата площ трябва да бъде запълнена, без празни полета. +ЗАДЪЛЖИТЕЛНИ БАЛОНИ С РЕПЛИКИ — това е комикс; героите трябва да говорят: + • Най-малко 3 от 4-те панела на всяка страница трябва да имат балон с реплика или мисъл. + • Ако описанието на панела включва цитирана реплика, изобрази я точно в балона. + • Балоните за реплики са бели с черен контур и опашка към говорещия. + • Балоните за мисли са облакоподобни с малки кръгчета към мислещия. + • Всички текстове в балоните са само на {{.Script}}. +ВАРИАТИВНОСТ — всеки панел трябва да се различава от останалите поне по 3 от следните измерения: ъгъл на камерата (крупен, среден, общ, от птичи поглед, нисък), поза или действие на героя, място или детайл от фона, осветление или време на деня, и предмети на преден план. Повтарянето на една и съща композиция е забранено. +{{.RenderingRequirement}}СТРОГИ ПРАВИЛА ЗА КОНСИСТЕНТНОСТ — за всеки панел: + • Човешките герои: идентични лице, възрастово внушение, прическа и облекло спрямо референцията. + • Животните: идентична порода, козина/шарка и цвят на очите — без замяна с друго животно. + • Смяна на дрехи само ако изрично е описана в сцената. + • Всички реплики, мисли и надписи са само на {{.Script}}. {{if .RenderingRequirement}}{{.RenderingRequirementEnd}}{{end}} |
