Seedance-Free / app.py
ginipick's picture
Create app.py
239e0d9 verified
raw
history blame
9.71 kB
import os
import streamlit as st
import replicate
from PIL import Image
import io
import base64
import tempfile
# νŽ˜μ΄μ§€ μ„€μ •
st.set_page_config(
page_title="AI Video Generator",
page_icon="🎬",
layout="wide"
)
# μŠ€νƒ€μΌ 적용
st.markdown("""
<style>
.main {
padding-top: 2rem;
}
.stButton>button {
width: 100%;
background-color: #4CAF50;
color: white;
font-weight: bold;
padding: 0.5rem;
border-radius: 0.5rem;
}
.stButton>button:hover {
background-color: #45a049;
}
</style>
""", unsafe_allow_html=True)
# 타이틀
st.title("🎬 AI Video Generator")
st.markdown("**Replicate API**λ₯Ό μ‚¬μš©ν•˜μ—¬ ν…μŠ€νŠΈλ‚˜ μ΄λ―Έμ§€λ‘œλΆ€ν„° λΉ„λ””μ˜€λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.")
# API 토큰 μ„€μ •
api_token = os.getenv("RAPI_TOKEN")
# μ‚¬μ΄λ“œλ°” μ„€μ •
with st.sidebar:
st.header("βš™οΈ μ„€μ •")
# API 토큰 μž…λ ₯ (ν™˜κ²½λ³€μˆ˜κ°€ μ—†λŠ” 경우)
if not api_token:
api_token_input = st.text_input(
"Replicate API Token",
type="password",
help="ν™˜κ²½λ³€μˆ˜ RAPI_TOKEN이 μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. API 토큰을 μž…λ ₯ν•˜μ„Έμš”."
)
if api_token_input:
api_token = api_token_input
os.environ["REPLICATE_API_TOKEN"] = api_token
else:
st.success("βœ… API 토큰이 ν™˜κ²½λ³€μˆ˜μ—μ„œ λ‘œλ“œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
os.environ["REPLICATE_API_TOKEN"] = api_token
st.divider()
# ν™”λ©΄ λΉ„μœ¨ μ„€μ •
st.subheader("πŸ“ ν™”λ©΄ λΉ„μœ¨")
aspect_ratios = {
"16:9": "16:9 (YouTube, 일반 λ™μ˜μƒ)",
"4:3": "4:3 (전톡적인 TV ν˜•μ‹)",
"1:1": "1:1 (Instagram ν”Όλ“œ)",
"3:4": "3:4 (Instagram 포트레이트)",
"9:16": "9:16 (Instagram 릴슀, TikTok)",
"21:9": "21:9 (μ‹œλ„€λ§ˆν‹± μ™€μ΄λ“œ)",
"9:21": "9:21 (울트라 μ„Έλ‘œν˜•)"
}
selected_ratio = st.selectbox(
"λΉ„μœ¨ 선택",
options=list(aspect_ratios.keys()),
format_func=lambda x: aspect_ratios[x],
index=0
)
st.divider()
# Seed μ„€μ •
st.subheader("🎲 랜덀 μ‹œλ“œ")
seed = st.number_input(
"Seed κ°’",
min_value=0,
max_value=999999,
value=42,
help="λ™μΌν•œ μ‹œλ“œκ°’μœΌλ‘œ λ™μΌν•œ κ²°κ³Όλ₯Ό μž¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€."
)
st.divider()
# κ³ μ • μ„€μ • ν‘œμ‹œ
st.subheader("πŸ“‹ κ³ μ • μ„€μ •")
st.info("""
- **μž¬μƒ μ‹œκ°„**: 5초
- **해상도**: 480p
""")
# 메인 컨텐츠
col1, col2 = st.columns([1, 1])
with col1:
st.header("🎯 생성 λͺ¨λ“œ 선택")
mode = st.radio(
"λͺ¨λ“œλ₯Ό μ„ νƒν•˜μ„Έμš”:",
["ν…μŠ€νŠΈ to λΉ„λ””μ˜€", "이미지 to λΉ„λ””μ˜€"],
help="ν…μŠ€νŠΈ μ„€λͺ…μ΄λ‚˜ 이미지λ₯Ό 기반으둜 λΉ„λ””μ˜€λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€."
)
# 이미지 μ—…λ‘œλ“œ (이미지 to λΉ„λ””μ˜€ λͺ¨λ“œ)
uploaded_image = None
image_base64 = None
if mode == "이미지 to λΉ„λ””μ˜€":
st.subheader("πŸ“· 이미지 μ—…λ‘œλ“œ")
uploaded_file = st.file_uploader(
"이미지λ₯Ό μ„ νƒν•˜μ„Έμš”",
type=['png', 'jpg', 'jpeg', 'webp'],
help="μ—…λ‘œλ“œν•œ 이미지λ₯Ό 기반으둜 λΉ„λ””μ˜€κ°€ μƒμ„±λ©λ‹ˆλ‹€."
)
if uploaded_file is not None:
# 이미지 ν‘œμ‹œ
uploaded_image = Image.open(uploaded_file)
st.image(uploaded_image, caption="μ—…λ‘œλ“œλœ 이미지", use_column_width=True)
# 이미지λ₯Ό base64둜 λ³€ν™˜
buffered = io.BytesIO()
uploaded_image.save(buffered, format="PNG")
image_base64 = base64.b64encode(buffered.getvalue()).decode()
with col2:
st.header("✍️ ν”„λ‘¬ν”„νŠΈ μž…λ ₯")
if mode == "ν…μŠ€νŠΈ to λΉ„λ””μ˜€":
prompt_placeholder = "생성할 λΉ„λ””μ˜€λ₯Ό μ„€λͺ…ν•΄μ£Όμ„Έμš”.\n예: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn."
else:
prompt_placeholder = "이미지λ₯Ό μ–΄λ–»κ²Œ μ›€μ§μ΄κ²Œ ν• μ§€ μ„€λͺ…ν•΄μ£Όμ„Έμš”.\n예: Camera slowly zooms in while clouds move across the sky. The subject's hair gently moves in the wind."
prompt = st.text_area(
"ν”„λ‘¬ν”„νŠΈ",
height=150,
placeholder=prompt_placeholder,
help="μžμ„Έν•˜κ³  ꡬ체적인 μ„€λͺ…μΌμˆ˜λ‘ 더 쒋은 κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€."
)
# 생성 λ²„νŠΌ
st.divider()
if st.button("🎬 λΉ„λ””μ˜€ 생성", type="primary", use_container_width=True):
# μž…λ ₯ 검증
if not api_token:
st.error("❌ API 토큰이 ν•„μš”ν•©λ‹ˆλ‹€. μ‚¬μ΄λ“œλ°”μ—μ„œ μ„€μ •ν•΄μ£Όμ„Έμš”.")
elif not prompt:
st.error("❌ ν”„λ‘¬ν”„νŠΈλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.")
elif mode == "이미지 to λΉ„λ””μ˜€" and uploaded_image is None:
st.error("❌ 이미지λ₯Ό μ—…λ‘œλ“œν•΄μ£Όμ„Έμš”.")
else:
try:
# ν”„λ‘œκ·Έλ ˆμŠ€ λ°”
progress_text = "λΉ„λ””μ˜€ 생성 쀑... μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”."
progress_bar = st.progress(0, text=progress_text)
# μž…λ ₯ νŒŒλΌλ―Έν„° μ„€μ •
input_params = {
"prompt": prompt,
"duration": 5,
"resolution": "480p",
"aspect_ratio": selected_ratio,
"seed": seed
}
# 이미지 to λΉ„λ””μ˜€ λͺ¨λ“œμΈ 경우
if mode == "이미지 to λΉ„λ””μ˜€" and image_base64:
input_params["image"] = f"data:image/png;base64,{image_base64}"
# μ§„ν–‰λ₯  μ—…λ°μ΄νŠΈ
progress_bar.progress(25, text="Replicate API 호좜 쀑...")
# Replicate μ‹€ν–‰
output = replicate.run(
"bytedance/seedance-1-lite",
input=input_params
)
# μ§„ν–‰λ₯  μ—…λ°μ΄νŠΈ
progress_bar.progress(75, text="λΉ„λ””μ˜€ λ‹€μš΄λ‘œλ“œ 쀑...")
# λΉ„λ””μ˜€ μ €μž₯
if hasattr(output, 'read'):
video_data = output.read()
else:
# URL인 경우 λ‹€μš΄λ‘œλ“œ
import requests
response = requests.get(output)
video_data = response.content
# μž„μ‹œ 파일둜 μ €μž₯
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
tmp_file.write(video_data)
tmp_filename = tmp_file.name
# 둜컬 νŒŒμΌλ‘œλ„ μ €μž₯
output_filename = "output.mp4"
with open(output_filename, "wb") as file:
file.write(video_data)
# μ§„ν–‰λ₯  μ™„λ£Œ
progress_bar.progress(100, text="μ™„λ£Œ!")
# 성곡 λ©”μ‹œμ§€
st.success(f"βœ… λΉ„λ””μ˜€κ°€ μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€! ({output_filename})")
# λΉ„λ””μ˜€ ν‘œμ‹œ
st.subheader("πŸ“Ή μƒμ„±λœ λΉ„λ””μ˜€")
st.video(tmp_filename)
# λ‹€μš΄λ‘œλ“œ λ²„νŠΌ
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
st.download_button(
label="⬇️ λΉ„λ””μ˜€ λ‹€μš΄λ‘œλ“œ",
data=video_data,
file_name="generated_video.mp4",
mime="video/mp4",
use_container_width=True
)
# 생성 정보 ν‘œμ‹œ
with st.expander("πŸ“Š 생성 정보"):
st.json({
"mode": mode,
"aspect_ratio": selected_ratio,
"seed": seed,
"duration": "5초",
"resolution": "480p",
"prompt": prompt[:100] + "..." if len(prompt) > 100 else prompt
})
except Exception as e:
st.error(f"❌ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}")
st.info("πŸ’‘ API 토큰이 μ˜¬λ°”λ₯Έμ§€, λͺ¨λΈμ΄ μ‚¬μš© κ°€λŠ₯ν•œμ§€ ν™•μΈν•΄μ£Όμ„Έμš”.")
# μ‚¬μš© 방법
with st.expander("πŸ“– μ‚¬μš© 방법"):
st.markdown("""
### μ„€μΉ˜ 및 μ‹€ν–‰
1. **ν•„μš”ν•œ νŒ¨ν‚€μ§€ μ„€μΉ˜**:
```bash
pip install streamlit replicate pillow requests
```
2. **ν™˜κ²½λ³€μˆ˜ μ„€μ •** (선택사항):
```bash
export RAPI_TOKEN="your-replicate-api-token"
```
3. **μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰**:
```bash
streamlit run video_generator.py
```
### κΈ°λŠ₯ μ„€λͺ…
- **ν…μŠ€νŠΈ to λΉ„λ””μ˜€**: ν…μŠ€νŠΈ μ„€λͺ…λ§ŒμœΌλ‘œ λΉ„λ””μ˜€λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
- **이미지 to λΉ„λ””μ˜€**: μ—…λ‘œλ“œν•œ 이미지λ₯Ό μ›€μ§μ΄λŠ” λΉ„λ””μ˜€λ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.
- **ν™”λ©΄ λΉ„μœ¨**: λ‹€μ–‘ν•œ SNS ν”Œλž«νΌμ— μ΅œμ ν™”λœ λΉ„μœ¨μ„ 선택할 수 μžˆμŠ΅λ‹ˆλ‹€.
- **Seed κ°’**: λ™μΌν•œ μ‹œλ“œκ°’μœΌλ‘œ λ™μΌν•œ κ²°κ³Όλ₯Ό μž¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
### 팁
- ꡬ체적이고 μƒμ„Έν•œ ν”„λ‘¬ν”„νŠΈμΌμˆ˜λ‘ 더 쒋은 κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.
- 카메라 μ›€μ§μž„, μ‘°λͺ…, λΆ„μœ„κΈ° 등을 μ„€λͺ…에 ν¬ν•¨μ‹œμΌœλ³΄μ„Έμš”.
- 이미지 to λΉ„λ””μ˜€ λͺ¨λ“œμ—μ„œλŠ” μ΄λ―Έμ§€μ˜ μ–΄λ–€ 뢀뢄을 μ–΄λ–»κ²Œ 움직일지 μ„€λͺ…ν•˜μ„Έμš”.
""")
# ν‘Έν„°
st.divider()
st.markdown(
"""
<div style='text-align: center; color: gray;'>
<p>Powered by Replicate AI and bytedance/seedance-1-lite model</p>
</div>
""",
unsafe_allow_html=True
)