Spaces:
Sleeping
Sleeping
Upload app.py with huggingface_hub
Browse files
app.py
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
DoodleDreams β ZeroGPU orchestrator
|
| 3 |
+
Draw + voice β illustrated bedtime storybook narrated in your cloned voice.
|
| 4 |
+
"""
|
| 5 |
+
import os, sys, json, time, tempfile, logging, threading
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
import spaces
|
| 12 |
+
except ModuleNotFoundError:
|
| 13 |
+
class _Shim:
|
| 14 |
+
@staticmethod
|
| 15 |
+
def GPU(*a, **k):
|
| 16 |
+
return a[0] if a and callable(a[0]) else (lambda fn: fn)
|
| 17 |
+
spaces = _Shim()
|
| 18 |
+
|
| 19 |
+
import gradio as gr
|
| 20 |
+
from config import (
|
| 21 |
+
FLUX_MODEL, STORY_MODEL, TTS_MODEL, TRANSLATION_MODEL,
|
| 22 |
+
KANNADA_TTS_MODEL, BASE_SEED, FLUX_STEPS, FLUX_GUIDANCE, FLUX_SIZE,
|
| 23 |
+
COLOR_ART_STYLE, COLOR_PAGE_SUFFIX, STORY_LENGTHS, GENRES, MOODS,
|
| 24 |
+
)
|
| 25 |
+
from book_builder import build_book_html, export_pdf, magic_loader_html
|
| 26 |
+
from ui.layout import create_layout
|
| 27 |
+
|
| 28 |
+
logging.basicConfig(level=logging.INFO)
|
| 29 |
+
logger = logging.getLogger(__name__)
|
| 30 |
+
|
| 31 |
+
ON_ZEROGPU = bool(os.environ.get("SPACES_ZERO_GPU"))
|
| 32 |
+
|
| 33 |
+
_FLUX = None
|
| 34 |
+
_STORY_M = None; _STORY_TOK = None
|
| 35 |
+
_TTS_M = None
|
| 36 |
+
_TRANS_M = None; _TRANS_TOK = None
|
| 37 |
+
_KAN_TTS_M = None
|
| 38 |
+
_LOAD_ERRORS = {}
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _load_flux():
|
| 42 |
+
global _FLUX
|
| 43 |
+
if _FLUX is None:
|
| 44 |
+
from diffusers import Flux2KleinPipeline
|
| 45 |
+
_FLUX = Flux2KleinPipeline.from_pretrained(
|
| 46 |
+
FLUX_MODEL.hub_id, torch_dtype=torch.bfloat16).to("cuda")
|
| 47 |
+
return _FLUX
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _load_story():
|
| 51 |
+
global _STORY_M, _STORY_TOK
|
| 52 |
+
if _STORY_M is None:
|
| 53 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 54 |
+
_STORY_TOK = AutoTokenizer.from_pretrained(
|
| 55 |
+
STORY_MODEL.hub_id, trust_remote_code=True)
|
| 56 |
+
_STORY_M = AutoModelForCausalLM.from_pretrained(
|
| 57 |
+
STORY_MODEL.hub_id, torch_dtype=torch.float16, trust_remote_code=True,
|
| 58 |
+
).to("cuda").eval()
|
| 59 |
+
return _STORY_M, _STORY_TOK
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _load_tts():
|
| 63 |
+
global _TTS_M
|
| 64 |
+
if _TTS_M is None:
|
| 65 |
+
from voxcpm import VoxCPM
|
| 66 |
+
_TTS_M = VoxCPM.from_pretrained(TTS_MODEL.hub_id, load_denoiser=False)
|
| 67 |
+
return _TTS_M
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _load_translation():
|
| 71 |
+
global _TRANS_M, _TRANS_TOK
|
| 72 |
+
if _TRANS_M is None:
|
| 73 |
+
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
| 74 |
+
_TRANS_TOK = AutoTokenizer.from_pretrained(
|
| 75 |
+
TRANSLATION_MODEL.hub_id, trust_remote_code=True)
|
| 76 |
+
_TRANS_M = AutoModelForSeq2SeqLM.from_pretrained(
|
| 77 |
+
TRANSLATION_MODEL.hub_id, trust_remote_code=True,
|
| 78 |
+
).to("cuda").eval()
|
| 79 |
+
return _TRANS_M, _TRANS_TOK
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _load_kannada_tts():
|
| 83 |
+
global _KAN_TTS_M
|
| 84 |
+
if _KAN_TTS_M is None:
|
| 85 |
+
from indic_tts import _get_model
|
| 86 |
+
_KAN_TTS_M = _get_model()
|
| 87 |
+
return _KAN_TTS_M
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
if ON_ZEROGPU:
|
| 91 |
+
for _n, _fn in [("flux", _load_flux), ("story", _load_story),
|
| 92 |
+
("tts", _load_tts), ("translation", _load_translation),
|
| 93 |
+
("kannada_tts", _load_kannada_tts)]:
|
| 94 |
+
try:
|
| 95 |
+
_fn()
|
| 96 |
+
except Exception as e:
|
| 97 |
+
_LOAD_ERRORS[_n] = repr(e)
|
| 98 |
+
logger.exception(f"Module-level load failed: {_n}")
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# ββ Genre/mood β deterministic story templates (fallback) ββββββββββββββ
|
| 102 |
+
|
| 103 |
+
_TEMPLATES = {
|
| 104 |
+
"Animals": [
|
| 105 |
+
("{hero} loved exploring the meadow every evening.", "{hero} walking through a golden meadow at dusk"),
|
| 106 |
+
("One night, {hero} heard a tiny sound in the tall grass.", "{hero} listening carefully near the rustling grass"),
|
| 107 |
+
("A little firefly needed help finding its family.", "{hero} meeting a tiny glowing firefly"),
|
| 108 |
+
("{hero} gently carried the firefly through the dark forest.", "{hero} walking carefully through a moonlit forest"),
|
| 109 |
+
("Together they found the firefly's home, glowing warm and bright.", "{hero} and the firefly reunited with the glowing firefly family"),
|
| 110 |
+
("Tired and happy, {hero} curled up under the stars.", "{hero} sleeping peacefully under a starry sky"),
|
| 111 |
+
],
|
| 112 |
+
"Kingdom": [
|
| 113 |
+
("In a cozy kingdom, {hero} was the kindest helper of all.", "{hero} standing cheerfully in a small fairy-tale kingdom"),
|
| 114 |
+
("One sleepy evening, the king's golden crown went missing.", "{hero} seeing the worried king without his crown"),
|
| 115 |
+
("{hero} searched the royal garden by moonlight.", "{hero} searching carefully through a moonlit garden"),
|
| 116 |
+
("A sleepy mouse had borrowed it for a bed!", "{hero} discovering a tiny mouse asleep inside the crown"),
|
| 117 |
+
("{hero} found the mouse a proper bed made of petals.", "{hero} tucking the tiny mouse into a flower-petal bed"),
|
| 118 |
+
("The king smiled, and the whole kingdom slept in peace.", "{hero} and the king smiling together under the night sky"),
|
| 119 |
+
],
|
| 120 |
+
"Space": [
|
| 121 |
+
("{hero} loved watching the stars from the garden.", "{hero} lying in the grass gazing at the starry sky"),
|
| 122 |
+
("One night, a small star blinked and fell from the sky.", "{hero} seeing a little star tumbling down"),
|
| 123 |
+
("{hero} caught the star in a jar of moonlight.", "{hero} gently catching a glowing star in a jar"),
|
| 124 |
+
("The star was lost and didn't know how to get home.", "{hero} listening to the sad little star"),
|
| 125 |
+
("{hero} climbed the tallest hill and let the star go free.", "{hero} releasing the star from the hilltop into the sky"),
|
| 126 |
+
("The star zoomed home, and {hero} fell fast asleep smiling.", "{hero} smiling and drifting off to sleep under the stars"),
|
| 127 |
+
],
|
| 128 |
+
"Dragons": [
|
| 129 |
+
("{hero} lived near a mountain where a shy dragon slept.", "{hero} looking up at a misty mountain at night"),
|
| 130 |
+
("One evening, the dragon sneezed and lost its flame.", "{hero} watching the dragon sneeze sadly"),
|
| 131 |
+
("{hero} brought warm soup and a soft blanket to the dragon.", "{hero} carrying a steaming bowl of soup to the dragon"),
|
| 132 |
+
("The dragon felt better and puffed a tiny grateful flame.", "{hero} and the dragon sharing a warm cozy moment"),
|
| 133 |
+
("Together they lit the lanterns along the sleepy village path.", "{hero} and the dragon lighting lanterns in the quiet village"),
|
| 134 |
+
("The dragon curled up, and {hero} tucked it in with a smile.", "{hero} tucking the dragon in for the night"),
|
| 135 |
+
],
|
| 136 |
+
"Ocean": [
|
| 137 |
+
("{hero} sat by the shore watching the moonlight on the waves.", "{hero} sitting peacefully by the ocean at night"),
|
| 138 |
+
("A little fish splashed up and looked worried.", "{hero} seeing a small worried fish near the surface"),
|
| 139 |
+
("The fish had lost its shell-home in a big wave.", "{hero} listening to the little fish explain its problem"),
|
| 140 |
+
("{hero} dove gently and found the shell on the sandy floor.", "{hero} swimming carefully along the moonlit ocean floor"),
|
| 141 |
+
("The fish swam home, and the sea became calm and quiet.", "{hero} watching the happy fish return to its shell"),
|
| 142 |
+
("{hero} fell asleep to the soft sound of the waves.", "{hero} sleeping peacefully beside the calm, moonlit sea"),
|
| 143 |
+
],
|
| 144 |
+
"Forest": [
|
| 145 |
+
("{hero} walked into the whispering forest as the moon rose.", "{hero} stepping into a moonlit forest path"),
|
| 146 |
+
("The trees were worried β an owl had lost its song.", "{hero} hearing the trees whisper about the silent owl"),
|
| 147 |
+
("{hero} climbed a mossy rock and hummed a gentle tune.", "{hero} humming softly on a mossy rock under the moon"),
|
| 148 |
+
("The owl listened and slowly remembered its melody.", "{hero} watching the owl open its eyes and begin to sing"),
|
| 149 |
+
("The whole forest filled with soft nighttime music.", "{hero} smiling as the forest glows with peaceful sound"),
|
| 150 |
+
("{hero} yawned and drifted off to sleep among the roots.", "{hero} sleeping curled up peacefully at the base of a great tree"),
|
| 151 |
+
],
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
FEW_SHOT = """
|
| 155 |
+
Write a 6-page children's bedtime story for age 5 about Luna the cat. Genre: Animals. Mood: Calming.
|
| 156 |
+
|
| 157 |
+
Return ONLY valid JSON:
|
| 158 |
+
{
|
| 159 |
+
"title": "Luna and the Sleepy Firefly",
|
| 160 |
+
"character_description": "A small grey cat named Luna with soft fur, big green eyes, and a white tip on her tail",
|
| 161 |
+
"pages": [
|
| 162 |
+
{"page": 1, "text": "Luna loved sitting in the garden when the moon came out.", "scene": "Luna sitting in a moonlit garden"},
|
| 163 |
+
{"page": 2, "text": "One night, she heard a tiny buzzing sound in the flowers.", "scene": "Luna listening near a flower patch"},
|
| 164 |
+
{"page": 3, "text": "A little firefly was lost and couldn't find its family.", "scene": "Luna meeting a tiny glowing firefly"},
|
| 165 |
+
{"page": 4, "text": "Luna walked gently through the dark, lighting the way.", "scene": "Luna walking with the firefly glowing beside her"},
|
| 166 |
+
{"page": 5, "text": "They found the firefly's home, glowing warm and bright.", "scene": "Luna and firefly arriving at a cluster of glowing lights"},
|
| 167 |
+
{"page": 6, "text": "Luna purred softly and curled up under the stars.", "scene": "Luna sleeping peacefully under a starry sky"}
|
| 168 |
+
]
|
| 169 |
+
}
|
| 170 |
+
"""
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _build_story_locally(hero_name: str, genre: str) -> dict:
|
| 174 |
+
hero = (hero_name or "Little Hero").strip() or "Little Hero"
|
| 175 |
+
beats = _TEMPLATES.get(genre, _TEMPLATES["Animals"])
|
| 176 |
+
pages = [
|
| 177 |
+
{"page": i+1, "text": t.format(hero=hero), "scene": s.format(hero=hero)}
|
| 178 |
+
for i, (t, s) in enumerate(beats)
|
| 179 |
+
]
|
| 180 |
+
return {
|
| 181 |
+
"title": f"{hero}'s Bedtime Dream",
|
| 182 |
+
"character_description": (
|
| 183 |
+
f"{hero}, a friendly children's storybook hero with bright colors, "
|
| 184 |
+
"bold outlines, and a cheerful expressive face"
|
| 185 |
+
),
|
| 186 |
+
"pages": pages,
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def _parse_story_json(raw: str) -> dict | None:
|
| 191 |
+
import re
|
| 192 |
+
m = re.search(r'\{[\s\S]*\}', raw or "")
|
| 193 |
+
if not m:
|
| 194 |
+
return None
|
| 195 |
+
try:
|
| 196 |
+
d = json.loads(m.group(0))
|
| 197 |
+
if "pages" in d and "title" in d:
|
| 198 |
+
return d
|
| 199 |
+
except Exception:
|
| 200 |
+
pass
|
| 201 |
+
return None
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
# ββ ZeroGPU inference ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 205 |
+
|
| 206 |
+
@spaces.GPU(duration=60)
|
| 207 |
+
def _gen_story_gpu(hero_name: str, genre: str, mood: str) -> dict:
|
| 208 |
+
try:
|
| 209 |
+
model, tok = _load_story()
|
| 210 |
+
prompt = (
|
| 211 |
+
f"{FEW_SHOT}\n\n"
|
| 212 |
+
f"Write a 6-page children's bedtime story for age 5 about {hero_name}. "
|
| 213 |
+
f"Genre: {genre}. Mood: {mood}. Keep it gentle and sleepy.\n\n"
|
| 214 |
+
f"Return ONLY valid JSON:\n"
|
| 215 |
+
)
|
| 216 |
+
inputs = tok.apply_chat_template(
|
| 217 |
+
[{"role": "user", "content": prompt}],
|
| 218 |
+
add_generation_prompt=True, enable_thinking=False,
|
| 219 |
+
return_dict=True, return_tensors="pt",
|
| 220 |
+
).to("cuda")
|
| 221 |
+
with torch.no_grad():
|
| 222 |
+
out = model.generate(**inputs, max_new_tokens=800, do_sample=False)
|
| 223 |
+
response = tok.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
|
| 224 |
+
parsed = _parse_story_json(response)
|
| 225 |
+
if parsed:
|
| 226 |
+
return parsed
|
| 227 |
+
except Exception as e:
|
| 228 |
+
logger.warning(f"Story GPU failed: {e}")
|
| 229 |
+
return _build_story_locally(hero_name, genre)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
@spaces.GPU(duration=150)
|
| 233 |
+
def _gen_images_gpu(char_desc: str, scenes: list,
|
| 234 |
+
doodle_bytes: bytes | None, seed: int) -> list:
|
| 235 |
+
import io
|
| 236 |
+
from PIL import Image
|
| 237 |
+
pipe = _load_flux()
|
| 238 |
+
canonical = None
|
| 239 |
+
if doodle_bytes:
|
| 240 |
+
try:
|
| 241 |
+
ref = Image.open(io.BytesIO(doodle_bytes)).convert("RGB")
|
| 242 |
+
canonical = pipe(
|
| 243 |
+
prompt=(
|
| 244 |
+
"Turn this child's drawing into a clean, full-body cartoon character "
|
| 245 |
+
"for a children's storybook. Keep the EXACT same creature. "
|
| 246 |
+
f"{COLOR_ART_STYLE}, plain white background, full character visible, centered."
|
| 247 |
+
),
|
| 248 |
+
image=ref, height=FLUX_SIZE, width=FLUX_SIZE,
|
| 249 |
+
guidance_scale=FLUX_GUIDANCE, num_inference_steps=FLUX_STEPS,
|
| 250 |
+
generator=torch.Generator("cuda").manual_seed(seed),
|
| 251 |
+
).images[0]
|
| 252 |
+
except Exception as e:
|
| 253 |
+
logger.warning(f"Canonical pass failed ({e}); text2img fallback")
|
| 254 |
+
images = []
|
| 255 |
+
for i, scene in enumerate(scenes):
|
| 256 |
+
if canonical is not None:
|
| 257 |
+
kw = dict(image=canonical,
|
| 258 |
+
prompt=f"The same character. {scene}. {COLOR_ART_STYLE}, {COLOR_PAGE_SUFFIX}")
|
| 259 |
+
else:
|
| 260 |
+
kw = dict(prompt=f"{char_desc}. Scene: {scene}. {COLOR_ART_STYLE}, centered.")
|
| 261 |
+
kw.update(height=FLUX_SIZE, width=FLUX_SIZE, guidance_scale=FLUX_GUIDANCE,
|
| 262 |
+
num_inference_steps=FLUX_STEPS,
|
| 263 |
+
generator=torch.Generator("cuda").manual_seed(seed + i + 1))
|
| 264 |
+
images.append(pipe(**kw).images[0])
|
| 265 |
+
logger.info(f"Page {i+1}/{len(scenes)} illustrated")
|
| 266 |
+
return images
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
@spaces.GPU(duration=120)
|
| 270 |
+
def _gen_tts_gpu(text: str, ref_wav: str | None,
|
| 271 |
+
mood: str, energy: float, language: str) -> str:
|
| 272 |
+
if language == "Kannada":
|
| 273 |
+
from indic_text import translate_to_kannada
|
| 274 |
+
from indic_tts import narrate_kannada
|
| 275 |
+
kannada_text = translate_to_kannada(text)
|
| 276 |
+
ref_txt = "ΰ²ΰ²¦ΰ³ ನನΰ³ΰ²¨ ΰ²§ΰ³ΰ²΅ΰ²¨ΰ²Ώ"
|
| 277 |
+
return narrate_kannada(ref_wav or "", ref_txt, kannada_text, mood, energy)
|
| 278 |
+
else:
|
| 279 |
+
from tts import clone_and_speak
|
| 280 |
+
return clone_and_speak(
|
| 281 |
+
ref_wav=ref_wav,
|
| 282 |
+
text=text,
|
| 283 |
+
speed=0.9,
|
| 284 |
+
mood=mood.lower(),
|
| 285 |
+
energy=energy,
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
# ββ heartbeat helper βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 290 |
+
|
| 291 |
+
def _with_heartbeat(blocking_fn, frame_fn, poll=4.0):
|
| 292 |
+
box = {}
|
| 293 |
+
def _run():
|
| 294 |
+
try: box["val"] = blocking_fn()
|
| 295 |
+
except BaseException as e: box["err"] = e
|
| 296 |
+
th = threading.Thread(target=_run, daemon=True)
|
| 297 |
+
th.start()
|
| 298 |
+
t0 = time.time()
|
| 299 |
+
while th.is_alive():
|
| 300 |
+
th.join(timeout=poll)
|
| 301 |
+
if th.is_alive():
|
| 302 |
+
yield ("hb", frame_fn(int(time.time() - t0)))
|
| 303 |
+
if "err" in box:
|
| 304 |
+
raise box["err"]
|
| 305 |
+
yield ("done", box["val"])
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
# ββ main generator βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 309 |
+
|
| 310 |
+
def create_book(doodle_image, ref_audio, hero_name, genre, mood, language, length_label):
|
| 311 |
+
t0 = time.perf_counter()
|
| 312 |
+
hero_name = (hero_name or "").strip() or "Little Hero"
|
| 313 |
+
energy = 0.45
|
| 314 |
+
|
| 315 |
+
trace = {
|
| 316 |
+
"backend": "zerogpu", "hero": hero_name,
|
| 317 |
+
"genre": genre, "mood": mood, "language": language,
|
| 318 |
+
"seed": BASE_SEED, "ts": time.strftime("%Y-%m-%d %H:%M:%S"),
|
| 319 |
+
}
|
| 320 |
+
if _LOAD_ERRORS:
|
| 321 |
+
trace["load_errors"] = _LOAD_ERRORS
|
| 322 |
+
|
| 323 |
+
_no = gr.update(visible=False)
|
| 324 |
+
_keep = gr.update()
|
| 325 |
+
|
| 326 |
+
yield (magic_loader_html("story", hero_name),
|
| 327 |
+
"Writing the bedtime storyβ¦", None, _no, {}, "")
|
| 328 |
+
|
| 329 |
+
try:
|
| 330 |
+
story = _gen_story_gpu(hero_name, genre, mood)
|
| 331 |
+
except Exception as e:
|
| 332 |
+
yield (f"<div class='page-loading'>Error: {e}</div>",
|
| 333 |
+
f"Error: {e}", None, _no, {}, "")
|
| 334 |
+
return
|
| 335 |
+
|
| 336 |
+
title = story.get("title", "A Bedtime Story")
|
| 337 |
+
pages = story.get("pages", [])
|
| 338 |
+
char_desc = story.get("character_description", "")
|
| 339 |
+
scenes = [p.get("scene", "") for p in pages]
|
| 340 |
+
page_texts = [p.get("text", "") for p in pages]
|
| 341 |
+
full_text = f"{title}. {' '.join(page_texts)}"
|
| 342 |
+
trace.update(title=title, char_desc=char_desc)
|
| 343 |
+
|
| 344 |
+
yield (magic_loader_html("images", hero_name),
|
| 345 |
+
f"{title} β illustratingβ¦", None, _no, story, json.dumps(trace, indent=2))
|
| 346 |
+
|
| 347 |
+
doodle_bytes = None
|
| 348 |
+
if doodle_image is not None:
|
| 349 |
+
import io
|
| 350 |
+
from PIL import Image
|
| 351 |
+
buf = io.BytesIO()
|
| 352 |
+
Image.fromarray(doodle_image).save(buf, format="PNG")
|
| 353 |
+
doodle_bytes = buf.getvalue()
|
| 354 |
+
|
| 355 |
+
# narration starts in parallel with illustration
|
| 356 |
+
voice_box = {}
|
| 357 |
+
def _do_voice():
|
| 358 |
+
try:
|
| 359 |
+
voice_box["path"] = _gen_tts_gpu(full_text, ref_audio, mood, energy, language)
|
| 360 |
+
except Exception as e:
|
| 361 |
+
voice_box["err"] = e
|
| 362 |
+
|
| 363 |
+
voice_th = threading.Thread(target=_do_voice, daemon=True)
|
| 364 |
+
voice_th.start()
|
| 365 |
+
|
| 366 |
+
def _audio_now():
|
| 367 |
+
return voice_box.get("path")
|
| 368 |
+
|
| 369 |
+
img_bytes, engine = None, "sketch"
|
| 370 |
+
try:
|
| 371 |
+
for kind, payload in _with_heartbeat(
|
| 372 |
+
lambda: _gen_images_gpu(char_desc, scenes, doodle_bytes, BASE_SEED),
|
| 373 |
+
lambda s: (
|
| 374 |
+
magic_loader_html("images", hero_name),
|
| 375 |
+
f"{title} β illustratingβ¦ {s}s"
|
| 376 |
+
+ (" Β· narration ready βΆ" if _audio_now() else " Β· recordingβ¦"),
|
| 377 |
+
_audio_now(), _no, story, json.dumps(trace, indent=2),
|
| 378 |
+
),
|
| 379 |
+
):
|
| 380 |
+
if kind == "hb":
|
| 381 |
+
yield payload
|
| 382 |
+
else:
|
| 383 |
+
import io
|
| 384 |
+
img_bytes = []
|
| 385 |
+
for img in payload:
|
| 386 |
+
buf = io.BytesIO(); img.save(buf, format="PNG")
|
| 387 |
+
img_bytes.append(buf.getvalue())
|
| 388 |
+
engine = "flux"
|
| 389 |
+
except Exception as e:
|
| 390 |
+
logger.exception("Image generation failed")
|
| 391 |
+
trace["image_error"] = repr(e)
|
| 392 |
+
from services.images import generate_placeholder_images
|
| 393 |
+
img_bytes = generate_placeholder_images(char_desc, scenes, doodle_bytes)
|
| 394 |
+
|
| 395 |
+
book_html = build_book_html(img_bytes, page_texts, title, engine)
|
| 396 |
+
|
| 397 |
+
while voice_th.is_alive():
|
| 398 |
+
voice_th.join(timeout=4)
|
| 399 |
+
if voice_th.is_alive():
|
| 400 |
+
yield (book_html, f"{title} β finishing narrationβ¦",
|
| 401 |
+
_audio_now(), _no, story, json.dumps(trace, indent=2))
|
| 402 |
+
audio_path = _audio_now()
|
| 403 |
+
if voice_box.get("err"):
|
| 404 |
+
trace["tts_error"] = repr(voice_box["err"])
|
| 405 |
+
|
| 406 |
+
pdf_path = None
|
| 407 |
+
try:
|
| 408 |
+
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as f:
|
| 409 |
+
pdf_path = export_pdf(img_bytes, page_texts, title, f.name)
|
| 410 |
+
except Exception as e:
|
| 411 |
+
logger.warning(f"PDF failed: {e}")
|
| 412 |
+
|
| 413 |
+
trace["total_sec"] = round(time.perf_counter() - t0, 2)
|
| 414 |
+
trace["engine"] = engine
|
| 415 |
+
|
| 416 |
+
pdf_update = gr.update(value=pdf_path, visible=True) if pdf_path else _keep
|
| 417 |
+
|
| 418 |
+
yield (
|
| 419 |
+
book_html,
|
| 420 |
+
f"Done: {title} Β· {len(img_bytes)} pages Β· {language} Β· {trace['total_sec']}s",
|
| 421 |
+
audio_path, pdf_update, story, json.dumps(trace, indent=2),
|
| 422 |
+
)
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
if __name__ == "__main__":
|
| 426 |
+
demo = create_layout(create_book_fn=create_book)
|
| 427 |
+
demo.queue(default_concurrency_limit=2, max_size=8)
|
| 428 |
+
demo.launch(share=False, allowed_paths=[tempfile.gettempdir()],
|
| 429 |
+
**demo.design_kwargs)
|