Spaces:
Runtime error
Runtime error
Download book_reader_app.py from snowim/magic-book: direct link, hf CLI and curl.
- Browser
- Download file 5.19 kB
-
https://huggingface.co/spaces/snowim/magic-book/resolve/main/book_reader_app.py
- Command line
-
hf download hf://spaces/snowim/magic-book/book_reader_app.py
-
curl -L -o book_reader_app.py https://huggingface.co/spaces/snowim/magic-book/resolve/main/book_reader_app.py
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) | |