| Age | Commit message (Collapse) | Author |
|
- Increase galleryPageCount from 3 to 5 (12 total pages per comic)
- Add 2 new gallery poses: profile close-up and power stance full-body
- Fix gallery prompt: explicitly state single 16:9 landscape image, no grid/panels
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
README
- Random 50/50 rendering mode per run (ultra-realistic or standard comic style)
- --no-ultra-realistic flag forces standard comic style when set
- renderingRequirement const centralised and applied to all image prompts via DRY renderReq() method
- Log rendering mode chosen at generation time
- Update README: correct page count (10), output filenames, all --story flags documented
- Bump version to 0.13.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
vocabulary/theme files
- Add renderingRequirement const applied to all prompts (cover, story pages, back cover, gallery)
- Write <slug>_comic_vocabulary.txt with vocabulary words and story text for learning
- Write <slug>_theme.txt recording the --story-theme used for easy reproduction
- Enforce adult (18+) characters in generator to avoid content policy blocks on revealing clothing
- Add cover/back cover retry logic (same pageMaxRetries as story pages)
- Bump version to 0.12.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
TTS chunks
- Increase story pages from 3 to 5 (10 total with cover/gallery/back)
- Add 3 text-free gallery pages with close-up heroine poses before back cover
- Add retry logic to cover and back cover generation (same as story pages)
- Enforce adult (18+) characters in generator prompt to avoid content policy blocks
- Reduce TTS chunk size from 200 to 100 words to stay under 1-minute quality threshold
- Increase TTS timeout from 2 to 3 minutes to avoid spurious deadline exceeded errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
subfolder, 16:9
- Iterative image chaining: each page receives cover + prev page as pixel-level
reference (multimodal) so the model copies appearance directly, not from text alone
- --story-theme flag overrides random genre pick per run
- Gemini now generates a short comic title (---COMIC TITLE--- separator) used as
the output slug; all files named <slug>_cover.png, <slug>_page_1.png, etc.
- All comic output stored under comics/<slug>/ subdirectory
- Aspect ratio changed to 16:9 (ThinkPad X1 Gen 9 screen fit; 16:10 not supported)
- Bulgarian language rule moved to top of every prompt for stronger compliance
- Dynamic story-specific cover masthead + publisher logo badge matching story genre
- --story-theme "a thrilling space..." forces genre without touching random pool
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- buildConclusion() calls gemini-2.5-flash (same SystemInstruction pattern)
to write a 3-4 sentence Bulgarian cinematic epilogue (~40-60 words, ~15-30s)
that reflects the story's emotion rather than recapping the plot
- narrateConclusion() narrates it as a separate TTS chunk and appends it to
the chunk list before final concatenation; failure is non-fatal
- Narrator struct now stores apiKey for the text-generation call
- Single-chunk path simplified: Rename instead of unnecessary concatenation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Gemini TTS voice quality degrades noticeably over long single-call texts.
Split the story into ~200-word paragraph-aligned chunks, narrate each
separately, then concatenate with ffmpeg's concat demuxer (copy codec,
no re-encoding). Short stories (single chunk) still use one call.
Temp files are written to a OS temp dir and cleaned up automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Bible prompt now requires EXACT age for each character (e.g. "8-year-old girl")
and explicitly states age must not change across any page, cover, or back cover
- Story page consistency rules now call out AGE APPEARANCE alongside face and clothing
- Cover prompt: "Do NOT invent new characters or use generic stand-ins" — only
story characters with matching age/face/clothing may appear
- Back cover prompt: same restriction, plus "same age, same face, same clothing,
same animals as in the interior pages"
- Both covers updated with: "a child must never look older or younger than defined"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Cover/back improvements:
- Front cover: MANDATORY TITLE instruction makes 'BULGARIAN VOCABULARY ADVENTURE'
dominate the top 20% of the cover in bold masthead lettering
- Back cover: Gemini generates a 2-3 sentence English marketing blurb that is
embedded verbatim in the blurb-box instruction (italic display type)
- Both covers now specify Silver-Age / Bronze-Age comic production layout
(price box, barcode strip, cover lines)
Animal consistency:
- Character bible prompt now treats animals as named characters — specifies exact
breed, fur colour/pattern, markings, and eye colour; instructs model never to
substitute a generic animal or change markings between pages
- Story page prompts add explicit animal consistency rule alongside human rules
Bible/blurb reliability:
- Switch helper calls to same SystemInstruction + user-content pattern that the
story generator uses successfully with gemini-2.5-flash
- Increase MaxOutputTokens to 8192 and timeout to 90s to match story generator
- Retry pause extended to 15s for free-tier RPM recovery between rapid calls
- Removed deprecated gemini-2.0-flash model references
PDF generation (new internal/story/pdf.go):
- AssembleComicPDF() combines all 5 images into comic.pdf via ImageMagick convert
- Pages are in reading order: cover → story pages → back cover
- Non-fatal: warns if ImageMagick is not installed, never blocks the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add comic_cover.png and comic_back.png flanking the 3 story pages
- Switch story pages to 3:4 portrait aspect ratio for standard comic proportions
- Request 9-panel (3×3) grid layout in every story page prompt
- Generate a Gemini character bible before image generation to lock characters,
clothing, and setting across all 5 pages — clothing only changes if the story
explicitly directs it
- Fix nil-writer panic in cover warning path
- Fix undefined comicPageCount reference in runner.go
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Generate a Gemini character bible before panel image generation
- Bible describes each character (name, age, hair, eyes, clothing) and
setting; it is prepended to every panel prompt to lock visual consistency
- Revert to 3 separate comic_page_N.png files (4:3 aspect ratio)
- bibleMaxTokens=2048 to avoid Gemini 2.5 Flash thinking-token starvation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
narration
- Replace 3 separate comic pages with one 9:16 tall comic_strip.png
- Prompt explicitly requests 3 vertically stacked panels with consistent characters
- Add AspectRatio field to SearchOptions; NanoBanana uses it when set
- Make cinematic narration instruction more dramatic (movie trailer narrator style)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Generates a ~500-word Bulgarian vocabulary story from a batch file
- Produces 3 comic pages via NanoBanana (90% ultra-realistic style)
- Art style chosen randomly per run; override with --story-style
- Cinematic Gemini TTS narration saved as story_narration.mp3
- Random voice from curated pool (Charon, Fenrir, Enceladus, Algieba, Aoede, Schedar)
- Override narrator voice with --narrator-voice
- Falls back to story_tts_todo.txt if narration fails
- No new API key required — reuses existing GOOGLE_API_KEY
- Bump version to 0.9.4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Gemini 2.5 Flash runs internal reasoning by default. Those thinking tokens
count against MaxOutputTokens, leaving almost no budget for the actual IPA
output — hence the truncated [ˈɡlɛdɐm with no closing bracket.
Set ThinkingBudget=0 in ThinkingConfig so all 200 output tokens are reserved
for the IPA transcription itself. A simple dictionary-style lookup needs no
chain-of-thought reasoning.
Also included: audio_player layout fix (NewBorder center + TextWrapWord) so
long IPA strings are not clipped in the GUI bar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Two issues prevented the IPA from being fully displayed:
1. audio_player.go: switched the bottom bar from NewHBox to NewBorder so the
phonetic label fills the centre column instead of being squeezed to its
minimum width between the stop button and the status label. Also enabled
TextWrapWord so very long IPA strings wrap rather than being clipped.
2. phonetic/fetcher.go: raised MaxOutputTokens from 50 to 200 so Gemini 2.5
Flash has enough budget to emit the full IPA bracket pair without truncation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Gemini 2.5 Flash emits internal reasoning tokens before the IPA bracket pair.
With the 50-token cap the output was silently truncated to just [ˈ, losing the
rest of the transcription. 200 tokens is ample headroom for any Bulgarian word
or short phrase while keeping the response focused.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- audio/fallbacks.go: lowercase error string per Go convention
- gui/app.go: remove empty else branch in keyboard shortcut handler
- audio/provider_test.go: remove unused mockProvider type
- update test assertions in voices_test.go and processor_test.go to match
the corrected lowercase error string
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- startFileCheckTicker: add wg.Add(1)/wg.Done() so the app shutdown handler
waits for the ticker goroutine to exit (it already uses ctx.Done() to stop).
- post-delete cleanup goroutine: add wg.Add(1)/wg.Done() and a ctx.Done()
case so a shutdown during the 5-second polling window terminates cleanly
instead of leaking the goroutine.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Introduce AttributionProvider interface with GetAttribution() separate from
ImageSearcher so callers only needing search/download don't carry attribution.
ImageClient composes both. Downloader.searcher and NewDownloader now take
ImageClient since attribution is needed when saving downloaded files.
Compile-time assertions updated to ImageClient.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
config, extract shared card-dir logic
task 00d: remove package-level var test seams from processor and gui packages;
factory functions (newAudioProvider, newOpenAIImageClient, newNanoBananaImageClient)
are now struct fields on Processor and Application, initialized with production
defaults in constructors and replaced in tests without global mutation.
task 006: add viperConfig struct captured once in NewProcessor; no method body
calls viper.GetString/IsSet/GetFloat64 directly any more — all config-file values
are accessed via p.viperCfg fields.
task 007: extract FindCardDirectory and FindOrCreateCardDirectory into
internal/utils.go; both Processor.findCardDirectory and
Application.findCardDirectory now delegate to the shared implementation,
which also handles the legacy _word.txt backward-compat fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Extend audio.Provider with Voices() []string and BuildAttribution() string
so all provider-specific behaviour is encapsulated in the implementation
rather than scattered as switch-cases across callers.
Add package-level VoicesFor(name) and BuildAttributionFor(name, params)
for callers (processor, GUI) that need these before constructing a
Provider instance. Add AttributionParamsFrom(config, word, ...) so
callers can build AttributionParams from the flat Config without a manual
provider switch. Implement both new interface methods in OpenAIProvider
and GeminiProvider. Update all Provider mock/fake types in tests.
Migrate audioVoicesForProvider() and saveAudioAttribution() in both
processor.go and gui/generator.go to use the new package-level helpers,
replacing the 10+ duplicated switch blocks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
audio_player.go: use time.AfterFunc instead of goroutine+sleep for the
100ms auto-play delay — no goroutine is blocked waiting.
app.go (setupUI, setupTooltips): replace goroutine+sleep with time.AfterFunc
for both 500ms tooltip initialization delays.
navigation.go: replace fixed 500ms sleep in the delete-cleanup goroutine with
a ticker+select loop that polls hasActiveOperations(). The goroutine now
proceeds as soon as all in-flight operations for the deleted word finish
(or after a 5-second safety timeout), rather than waiting a fixed interval
that may be too short or too long.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replace Processor.RunGUIMode() with a standalone runGUIMode() function in
the composition root (cmd/main.go) that calls proc.GUIConfig() to get the
GUI settings and then owns the gui.New()/Run() lifecycle. The processor
package still imports gui for the gui.Config return type; complete removal
of that import is deferred to task 000 (god-object decomposition) where
the Processor itself will be split up.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Introduce OpenAIAudioConfig and GeminiAudioConfig sub-structs so each
provider implementation only receives the fields it needs. NewOpenAIProvider
and NewGeminiProvider now accept their respective sub-configs; NewProvider
extracts the appropriate sub-config from the flat Config via two helper
functions. The flat Config struct is preserved unchanged for all external
callers (gui, processor, tests) so no consumer needs to be updated.
InstructionForProvider in sidecar.go uses the helpers to extract sub-configs
before calling provider-specific helpers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Add PhoneticFetcher and Translator fields to gui.Config so callers can
inject ready-to-use instances. gui.New() uses the injected values when
non-nil and falls back to constructing from provider/key fields otherwise.
The processor composition root now builds both dependencies and sets them
on gui.Config, keeping construction logic out of gui.New().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
provider normalization
- task 009: remove translateBulgarianToEnglish from OpenAIClient and
NanoBananaClient. Image clients no longer make internal translation API calls;
callers must supply SearchOptions.Translation (processor and GUI already do).
When translation is absent, opts.Query (the Bulgarian word) is used as
fallback subject so image generation still proceeds. Update tests to pass
translation via SearchOptions rather than relying on the removed fallback.
- task 00i: consolidate duplicate ProviderGemini/ProviderOpenAI constants and
normalizeProvider() functions from internal/translation and internal/phonetic
into a shared config.NormalizeProvider(string) string in
internal/config/provider.go. Both packages now delegate to the shared
function so the normalization rule (lowercase, trim, default "gemini") has
one authoritative home.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Enable -race flag on the default 'task test' command so race conditions
are caught by default, not only when running 'task test-race'
- Add t.Parallel() to all tests in internal/image/prompt_test.go (pure
function tests with no shared state)
- Add t.Parallel() to TestReadBatchFile and its subtests in batch package
(file I/O with t.TempDir(), no global state)
- Add t.Parallel() to TestValidateBulgarianText and its subtests in audio
package (pure string validation, no shared state)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
batch timeout
- Thread ctx context.Context through generateAudio, generateAudioBgBg,
generateAudioWithVoice, generateAudioWithVoiceAndFilename,
generateAudioWithVoiceAndFilenameInDir, and downloadImagesWithTranslation.
Also add ctx to ProcessWordWithTranslationAndType so callers can control
the deadline for the full word-processing pipeline.
- In ProcessBatch, create a context.WithTimeout(5 * time.Minute) per word
so a single hung TTS or image API call cannot block the whole batch run.
Previously both functions created context.Background() internally, making
cancellation impossible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
shared prompt
- task 00g: fix gofmt violations (trailing whitespace, missing newlines,
indentation) in 8 files; all pass gofmt -l now
- task 00k: remove unused ProviderWithFallback and its tests (YAGNI — no
production caller existed; voice-level fallback via RunWithVoiceFallbacks
already covers the real use case)
- task 00h: replace private splitLines/trimSpace/isSpace helpers in
internal/batch/processor.go with strings.Split+ReplaceAll and
strings.TrimSpace from the stdlib; remove the now-redundant tests
- task 008: extract buildEducationalPrompt into internal/image/prompt.go so
the prompt-assembly policy (scene truncation cascade, char limit) lives in
one place; both OpenAIClient and NanoBananaClient delegate to it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Remove dead DownloadImage stub from image/search.go (always returned nil,
real implementation lives in Downloader.DownloadImage in download.go)
- Add compile-time interface assertions for OpenAIProvider, ProviderWithFallback,
and OpenAIClient to catch interface drift at compile time
- Replace http.DefaultClient (no timeout) with a shared imageHTTPClient
(60s timeout) in both OpenAIClient and NanoBananaClient Download methods;
prevents goroutine hangs on slow/unresponsive image servers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replace per-rune += string(r) heap allocations with strings.Builder in
SanitizeFilename (internal/utils.go) and splitLines (internal/batch/processor.go).
Both now call Grow/Reset appropriately to pre-allocate capacity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|