magic-book / book_reader_app.py
snowim's picture
Upload book_reader_app.py
31d14fb verified
Raw History Blame Contribute Delete
5.19 kB
import streamlit as st
from google import genai
from gtts import gTTS
from PIL import Image
import io
st.set_page_config(page_title="๋งˆ๋ฒ•์˜ ์ฑ… ์ฝ๊ธฐ ์นด๋ฉ”๋ผ", layout="centered", page_icon="๐Ÿ“–")
# Custom CSS for Kids UI
st.markdown("""
<style>
.main { background-color: #FFF8F0; }
h1 { color: #FF6B6B; font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif; text-align: center; }
h3 { color: #4ECDC4; text-align: center; }
.stCameraInput { border-radius: 15px; border: 4px dashed #FFD93D; padding: 10px; }
.stAudio { margin-top: 20px; }
.story-box {
background-color: white;
padding: 20px;
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border-left: 8px solid #FFD93D;
font-size: 20px;
line-height: 1.6;
color: #333;
}
</style>
""", unsafe_allow_html=True)
st.title("๐Ÿ“– ๋งˆ๋ฒ•์˜ ์ฑ… ์ฝ๊ธฐ ์นด๋ฉ”๋ผ ๐Ÿ“ท")
st.markdown("### ์ฑ…์„ ์ฐฐ์นต! ์ฐ์–ด์ฃผ๋ฉด ๋งˆ๋ฒ•์‚ฌ ์š”์ •์ด ๊ธ€์„ ์ฝ์–ด์ค˜์š” โœจ")
import os
# Sidebar for configuration
with st.sidebar:
st.header("โš™๏ธ ๋ถ€๋ชจ๋‹˜ ์„ค์ • ๋ฉ”๋‰ด")
st.info("API ํ‚ค๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์ž…๋ ฅํ•˜๊ณ  'ํ‚ค ์ €์žฅํ•˜๊ธฐ'๋ฅผ ๋ˆŒ๋Ÿฌ์ฃผ์„ธ์š”!")
# Check if key is in environment variables (for Hugging Face Secrets)
env_key = os.environ.get("GEMINI_API_KEY", "")
with st.form("api_key_form"):
gemini_api_key = st.text_input("Gemini API Key", type="password", placeholder="AIzaSy...", value=env_key)
submitted = st.form_submit_button("ํ‚ค ์ €์žฅํ•˜๊ธฐ (์—”ํ„ฐ ์—ญํ• )")
if submitted:
st.success("ํ‚ค๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ์ด์ œ ์‚ฌ์ง„์„ ์ฐ์–ด๋ณด์„ธ์š”.")
# Initialize session state for storing the current generated text and audio
if "story_text" not in st.session_state:
st.session_state.story_text = ""
if "audio_bytes" not in st.session_state:
st.session_state.audio_bytes = None
# Camera or File Upload Input
tab1, tab2 = st.tabs(["๐Ÿ“ธ ์นด๋ฉ”๋ผ ์ดฌ์˜", "๐Ÿ“ ์‚ฌ์ง„ ์—…๋กœ๋“œ (๋ชจ๋ฐ”์ผ ์ถ”์ฒœ)"])
with tab1:
camera_image = st.camera_input("์นด๋ฉ”๋ผ๋กœ ์ฑ…์„ ์ฐ์–ด๋ณด์„ธ์š”!")
with tab2:
uploaded_image = st.file_uploader("์Šค๋งˆํŠธํฐ ๊ธฐ๋ณธ ์นด๋ฉ”๋ผ๋กœ ์ฐ์€ ์‚ฌ์ง„์„ ์˜ฌ๋ ค์ฃผ์„ธ์š”", type=['jpg', 'jpeg', 'png'])
final_image = camera_image if camera_image else uploaded_image
if final_image is not None:
if not gemini_api_key:
st.warning("โš ๏ธ ์™ผ์ชฝ ๋ฉ”๋‰ด์—์„œ Gemini API ํ‚ค๋ฅผ ๋จผ์ € ์ž…๋ ฅํ•˜๊ณ  ์ €์žฅํ•ด์ฃผ์„ธ์š”!")
else:
st.success("์‚ฌ์ง„ ์ฐฐ์นต! ์š”์ •์—๊ฒŒ ์‚ฌ์ง„์„ ์ „๋‹ฌํ–ˆ์–ด์š”. ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š” ๐Ÿงšโ€โ™€๏ธ")
# This prevents re-running the API call if the image hasn't changed
if "last_image" not in st.session_state or st.session_state.last_image != final_image.getvalue():
st.session_state.last_image = final_image.getvalue()
with st.spinner("๋งˆ๋ฒ•์‚ฌ ์š”์ •์ด ์ฑ…์„ ์ฝ๊ณ  ์žˆ์–ด์š”... ๋พฐ๋กœ๋กฑ! โœจ"):
try:
# 1. OCR + Contextualization with Gemini 2.5 Flash
client = genai.Client(api_key=gemini_api_key)
image = Image.open(final_image)
# ๋ชจ๋ฐ”์ผ ๊ณ ํ•ด์ƒ๋„ ์‚ฌ์ง„ ์šฉ๋Ÿ‰ ์ค„์ด๊ธฐ (ํฌ๋ž˜์‹œ ๋ฐฉ์ง€)
image.thumbnail((1024, 1024))
prompt = """
์ด ์ด๋ฏธ์ง€์— ์žˆ๋Š” ์ฑ…์˜ ๋‚ด์šฉ์„ ์ •ํ™•ํ•˜๊ฒŒ ์ฝ์–ด์ค˜.
์•„์ด์—๊ฒŒ ๋™ํ™”์ฑ…์„ ์ฝ์–ด์ฃผ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์นœ์ ˆํ•˜๊ณ  ๋‹ค์ •ํ•œ ๋งํˆฌ๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐ”๊ฟ”์„œ ๋“ค๋ ค์ค˜.
๋‚ด์šฉ์— ์–ด์šธ๋ฆฌ๋Š” ๊ท€์—ฌ์šด ์ด๋ชจ์ง€๋„ ์ค‘๊ฐ„์ค‘๊ฐ„ ๋„ฃ์–ด์ค˜.
์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๊ฐ€ ์—†๋‹ค๋ฉด "์•—, ๊ธ€์ž๊ฐ€ ์•ˆ ๋ณด์—ฌ์š”! ๋‹ค์‹œ ํ•œ ๋ฒˆ ์ฐ์–ด์ค„๋ž˜?" ๋ผ๊ณ  ๋งํ•ด์ค˜.
"""
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[prompt, image]
)
st.session_state.story_text = response.text
# 2. Text-to-Speech with gTTS
tts = gTTS(text=st.session_state.story_text, lang='ko', slow=False)
audio_fp = io.BytesIO()
tts.write_to_fp(audio_fp)
st.session_state.audio_bytes = audio_fp.getvalue()
except Exception as e:
st.error(f"์•—! ๋งˆ๋ฒ•์‚ฌ ์š”์ •์ด ์‹ค์ˆ˜ํ–ˆ์–ด์š”: {e}")
st.session_state.story_text = ""
st.session_state.audio_bytes = None
# Display Output
if st.session_state.story_text and st.session_state.audio_bytes:
st.markdown("### ๐ŸŽ™๏ธ ๋งˆ๋ฒ•์‚ฌ ์š”์ •์˜ ๋ชฉ์†Œ๋ฆฌ๋ฅผ ๋“ค์–ด๋ณด์„ธ์š”!")
st.audio(st.session_state.audio_bytes, format='audio/mp3', autoplay=True)
st.markdown("### ๐Ÿ“ ์š”์ •์ด ์ฝ์–ด์ค€ ๋‚ด์šฉ")
st.markdown(f'<div class="story-box">{st.session_state.story_text}</div>', unsafe_allow_html=True)