Spaces:
Running
Running
#!/usr/bin/env python3 | |
""" | |
AI Video Generator with Gradio | |
Single file application - app.py | |
""" | |
import os | |
import gradio as gr | |
import replicate | |
import base64 | |
from PIL import Image | |
import io | |
import requests | |
from datetime import datetime | |
import tempfile | |
# API ํ ํฐ ์ค์ | |
api_token = os.getenv("RAPI_TOKEN") | |
if api_token: | |
os.environ["REPLICATE_API_TOKEN"] = api_token | |
# ํ๋ฉด ๋น์จ ์ต์ | |
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 (์ธํธ๋ผ ์ธ๋กํ)" | |
} | |
def update_prompt_placeholder(mode): | |
"""๋ชจ๋์ ๋ฐ๋ผ ํ๋กฌํํธ ํ๋ ์ด์คํ๋ ์ ๋ฐ์ดํธ""" | |
if mode == "ํ ์คํธ to ๋น๋์ค": | |
return gr.update(placeholder="์์ฑํ ๋น๋์ค๋ฅผ ์ค๋ช ํด์ฃผ์ธ์.\n์: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn.") | |
else: | |
return gr.update(placeholder="์ด๋ฏธ์ง๋ฅผ ์ด๋ป๊ฒ ์์ง์ด๊ฒ ํ ์ง ์ค๋ช ํด์ฃผ์ธ์.\n์: Camera slowly zooms in while clouds move across the sky. The subject's hair gently moves in the wind.") | |
def update_image_input(mode): | |
"""๋ชจ๋์ ๋ฐ๋ผ ์ด๋ฏธ์ง ์ ๋ ฅ ํ์/์จ๊น""" | |
if mode == "์ด๋ฏธ์ง to ๋น๋์ค": | |
return gr.update(visible=True) | |
else: | |
return gr.update(visible=False) | |
def generate_video(mode, prompt, image, aspect_ratio, seed, api_key_input, progress=gr.Progress()): | |
"""๋น๋์ค ์์ฑ ๋ฉ์ธ ํจ์""" | |
# API ํ ํฐ ํ์ธ | |
token = api_key_input or api_token | |
if not token: | |
return None, "โ API ํ ํฐ์ด ํ์ํฉ๋๋ค. ํ๊ฒฝ๋ณ์ RAPI_TOKEN์ ์ค์ ํ๊ฑฐ๋ API ํค๋ฅผ ์ ๋ ฅํ์ธ์." | |
os.environ["REPLICATE_API_TOKEN"] = token | |
# ์ ๋ ฅ ๊ฒ์ฆ | |
if not prompt: | |
return None, "โ ํ๋กฌํํธ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." | |
if mode == "์ด๋ฏธ์ง to ๋น๋์ค" and image is None: | |
return None, "โ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํด์ฃผ์ธ์." | |
try: | |
progress(0, desc="๋น๋์ค ์์ฑ ์ค๋น ์ค...") | |
# ์ ๋ ฅ ํ๋ผ๋ฏธํฐ ์ค์ | |
input_params = { | |
"prompt": prompt, | |
"duration": 5, | |
"resolution": "480p", | |
"aspect_ratio": aspect_ratio, | |
"seed": seed | |
} | |
# ์ด๋ฏธ์ง to ๋น๋์ค ๋ชจ๋ | |
if mode == "์ด๋ฏธ์ง to ๋น๋์ค" and image is not None: | |
progress(0.1, desc="์ด๋ฏธ์ง ์ฒ๋ฆฌ ์ค...") | |
# PIL Image๋ฅผ base64๋ก ๋ณํ | |
if isinstance(image, str): # ํ์ผ ๊ฒฝ๋ก์ธ ๊ฒฝ์ฐ | |
with Image.open(image) as img: | |
buffered = io.BytesIO() | |
img.save(buffered, format="PNG") | |
image_base64 = base64.b64encode(buffered.getvalue()).decode() | |
else: # PIL Image ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ | |
buffered = io.BytesIO() | |
image.save(buffered, format="PNG") | |
image_base64 = base64.b64encode(buffered.getvalue()).decode() | |
input_params["image"] = f"data:image/png;base64,{image_base64}" | |
progress(0.3, desc="Replicate API ํธ์ถ ์ค...") | |
# Replicate ์คํ | |
output = replicate.run( | |
"bytedance/seedance-1-lite", | |
input=input_params | |
) | |
progress(0.7, desc="๋น๋์ค ๋ค์ด๋ก๋ ์ค...") | |
# ๋น๋์ค ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ | |
if hasattr(output, 'read'): | |
video_data = output.read() | |
else: | |
# URL์ธ ๊ฒฝ์ฐ ๋ค์ด๋ก๋ | |
response = requests.get(output) | |
video_data = response.content | |
# ์์ ํ์ผ๋ก ์ ์ฅ | |
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file: | |
tmp_file.write(video_data) | |
video_path = tmp_file.name | |
# output.mp4๋ก๋ ์ ์ฅ | |
with open("output.mp4", "wb") as file: | |
file.write(video_data) | |
progress(1.0, desc="์๋ฃ!") | |
# ์์ฑ ์ ๋ณด | |
info = f"""โ ๋น๋์ค๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์์ฑ๋์์ต๋๋ค! | |
๐ ์์ฑ ์ ๋ณด: | |
- ๋ชจ๋: {mode} | |
- ํ๋ฉด ๋น์จ: {aspect_ratio} | |
- Seed: {seed} | |
- ์ฌ์ ์๊ฐ: 5์ด | |
- ํด์๋: 480p | |
- ํ์ผ: output.mp4""" | |
return video_path, info | |
except Exception as e: | |
error_msg = f"โ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}" | |
return None, error_msg | |
# Gradio ์ธํฐํ์ด์ค ์์ฑ | |
with gr.Blocks(title="AI Video Generator", theme=gr.themes.Soft()) as app: | |
gr.Markdown(""" | |
# ๐ฌ AI Video Generator | |
**Replicate API**๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ ์ด๋ฏธ์ง๋ก๋ถํฐ ๋น๋์ค๋ฅผ ์์ฑํฉ๋๋ค. | |
[](https://ginigen.com/) | |
""") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
# API ์ค์ | |
with gr.Accordion("โ๏ธ API ์ค์ ", open=not bool(api_token)): | |
if api_token: | |
gr.Markdown("โ API ํ ํฐ์ด ํ๊ฒฝ๋ณ์์์ ๋ก๋๋์์ต๋๋ค.") | |
api_key_input = gr.Textbox( | |
label="Replicate API Token (์ ํ์ฌํญ)", | |
type="password", | |
placeholder="ํ๊ฒฝ๋ณ์๋ฅผ ๋ฎ์ด์ฐ๋ ค๋ฉด ์ฌ๊ธฐ์ ์ ๋ ฅ", | |
value="" | |
) | |
else: | |
gr.Markdown("โ ๏ธ ํ๊ฒฝ๋ณ์ RAPI_TOKEN์ด ์ค์ ๋์ง ์์์ต๋๋ค.") | |
api_key_input = gr.Textbox( | |
label="Replicate API Token (ํ์)", | |
type="password", | |
placeholder="Replicate API ํ ํฐ์ ์ ๋ ฅํ์ธ์", | |
value="" | |
) | |
# ์์ฑ ๋ชจ๋ | |
mode = gr.Radio( | |
label="๐ฏ ์์ฑ ๋ชจ๋", | |
choices=["ํ ์คํธ to ๋น๋์ค", "์ด๋ฏธ์ง to ๋น๋์ค"], | |
value="ํ ์คํธ to ๋น๋์ค" | |
) | |
# ์ด๋ฏธ์ง ์ ๋ก๋ | |
image_input = gr.Image( | |
label="๐ท ์ด๋ฏธ์ง ์ ๋ก๋", | |
type="pil", | |
visible=False | |
) | |
# ํ๋ฉด ๋น์จ | |
aspect_ratio = gr.Dropdown( | |
label="๐ ํ๋ฉด ๋น์จ", | |
choices=list(ASPECT_RATIOS.keys()), | |
value="16:9", | |
info="SNS ํ๋ซํผ์ ์ต์ ํ๋ ๋น์จ์ ์ ํํ์ธ์" | |
) | |
# ๋น์จ ์ค๋ช ํ์ | |
ratio_info = gr.Markdown(value=f"์ ํ๋ ๋น์จ: {ASPECT_RATIOS['16:9']}") | |
# Seed ์ค์ | |
seed = gr.Number( | |
label="๐ฒ ๋๋ค ์๋", | |
value=42, | |
precision=0, | |
info="๋์ผํ ์๋๊ฐ์ผ๋ก ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์ฌํํ ์ ์์ต๋๋ค" | |
) | |
# ๊ณ ์ ์ค์ ํ์ | |
gr.Markdown(""" | |
### ๐ ๊ณ ์ ์ค์ | |
- **์ฌ์ ์๊ฐ**: 5์ด | |
- **ํด์๋**: 480p | |
""") | |
with gr.Column(scale=2): | |
# ํ๋กฌํํธ ์ ๋ ฅ | |
prompt = gr.Textbox( | |
label="โ๏ธ ํ๋กฌํํธ", | |
lines=5, | |
placeholder="์์ฑํ ๋น๋์ค๋ฅผ ์ค๋ช ํด์ฃผ์ธ์.\n์: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn." | |
) | |
# ์์ฑ ๋ฒํผ | |
generate_btn = gr.Button("๐ฌ ๋น๋์ค ์์ฑ", variant="primary", size="lg") | |
# ๊ฒฐ๊ณผ ํ์ | |
with gr.Column(): | |
output_video = gr.Video( | |
label="๐น ์์ฑ๋ ๋น๋์ค", | |
autoplay=True | |
) | |
output_info = gr.Textbox( | |
label="์ ๋ณด", | |
lines=8, | |
interactive=False | |
) | |
# ์ฌ์ฉ ๋ฐฉ๋ฒ | |
with gr.Accordion("๐ ์ฌ์ฉ ๋ฐฉ๋ฒ", open=False): | |
gr.Markdown(""" | |
### ์ค์น ๋ฐฉ๋ฒ | |
1. **ํ์ํ ํจํค์ง ์ค์น**: | |
```bash | |
pip install gradio replicate pillow requests | |
``` | |
2. **ํ๊ฒฝ๋ณ์ ์ค์ ** (์ ํ์ฌํญ): | |
```bash | |
export RAPI_TOKEN="your-replicate-api-token" | |
``` | |
3. **์คํ**: | |
```bash | |
python app.py | |
``` | |
### ๊ธฐ๋ฅ ์ค๋ช | |
- **ํ ์คํธ to ๋น๋์ค**: ํ ์คํธ ์ค๋ช ๋ง์ผ๋ก ๋น๋์ค๋ฅผ ์์ฑํฉ๋๋ค. | |
- **์ด๋ฏธ์ง to ๋น๋์ค**: ์ ๋ก๋ํ ์ด๋ฏธ์ง๋ฅผ ์์ง์ด๋ ๋น๋์ค๋ก ๋ณํํฉ๋๋ค. | |
- **ํ๋ฉด ๋น์จ**: ๋ค์ํ SNS ํ๋ซํผ์ ์ต์ ํ๋ ๋น์จ์ ์ ํํ ์ ์์ต๋๋ค. | |
- **Seed ๊ฐ**: ๋์ผํ ์๋๊ฐ์ผ๋ก ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์ฌํํ ์ ์์ต๋๋ค. | |
### ํ๋กฌํํธ ์์ฑ ํ | |
- ๊ตฌ์ฒด์ ์ด๊ณ ์์ธํ ์ค๋ช ์ ์ฌ์ฉํ์ธ์ | |
- ์นด๋ฉ๋ผ ์์ง์์ ๋ช ์ํ์ธ์ (์: zoom in, pan left, tracking shot) | |
- ์กฐ๋ช ๊ณผ ๋ถ์๊ธฐ๋ฅผ ์ค๋ช ํ์ธ์ (์: golden hour, dramatic lighting) | |
- ์์ง์์ ์๋๋ฅผ ์ง์ ํ์ธ์ (์: slowly, rapidly, gently) | |
""") | |
# ์์ | |
gr.Examples( | |
examples=[ | |
["ํ ์คํธ to ๋น๋์ค", "A serene lake at sunrise with mist rolling over the water. Camera slowly pans across the landscape as birds fly overhead.", None, "16:9", 42], | |
["ํ ์คํธ to ๋น๋์ค", "Urban street scene at night with neon lights reflecting on wet pavement. People walking with umbrellas, camera tracking forward.", None, "9:16", 123], | |
["ํ ์คํธ to ๋น๋์ค", "Close-up of a flower blooming in time-lapse, soft natural lighting, shallow depth of field.", None, "1:1", 789], | |
], | |
inputs=[mode, prompt, image_input, aspect_ratio, seed], | |
label="์์ ํ๋กฌํํธ" | |
) | |
# ์ด๋ฒคํธ ํธ๋ค๋ฌ | |
mode.change( | |
fn=update_prompt_placeholder, | |
inputs=[mode], | |
outputs=[prompt] | |
) | |
mode.change( | |
fn=update_image_input, | |
inputs=[mode], | |
outputs=[image_input] | |
) | |
aspect_ratio.change( | |
fn=lambda x: f"์ ํ๋ ๋น์จ: {ASPECT_RATIOS[x]}", | |
inputs=[aspect_ratio], | |
outputs=[ratio_info] | |
) | |
generate_btn.click( | |
fn=generate_video, | |
inputs=[mode, prompt, image_input, aspect_ratio, seed, api_key_input], | |
outputs=[output_video, output_info] | |
) | |
# ์ฑ ์คํ | |
if __name__ == "__main__": | |
app.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=False, | |
inbrowser=True | |
) |