ginipick commited on
Commit
239e0d9
ยท
verified ยท
1 Parent(s): b16eb58

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +291 -0
app.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import streamlit as st
3
+ import replicate
4
+ from PIL import Image
5
+ import io
6
+ import base64
7
+ import tempfile
8
+
9
+ # ํŽ˜์ด์ง€ ์„ค์ •
10
+ st.set_page_config(
11
+ page_title="AI Video Generator",
12
+ page_icon="๐ŸŽฌ",
13
+ layout="wide"
14
+ )
15
+
16
+ # ์Šคํƒ€์ผ ์ ์šฉ
17
+ st.markdown("""
18
+ <style>
19
+ .main {
20
+ padding-top: 2rem;
21
+ }
22
+ .stButton>button {
23
+ width: 100%;
24
+ background-color: #4CAF50;
25
+ color: white;
26
+ font-weight: bold;
27
+ padding: 0.5rem;
28
+ border-radius: 0.5rem;
29
+ }
30
+ .stButton>button:hover {
31
+ background-color: #45a049;
32
+ }
33
+ </style>
34
+ """, unsafe_allow_html=True)
35
+
36
+ # ํƒ€์ดํ‹€
37
+ st.title("๐ŸŽฌ AI Video Generator")
38
+ st.markdown("**Replicate API**๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…์ŠคํŠธ๋‚˜ ์ด๋ฏธ์ง€๋กœ๋ถ€ํ„ฐ ๋น„๋””์˜ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
39
+
40
+ # API ํ† ํฐ ์„ค์ •
41
+ api_token = os.getenv("RAPI_TOKEN")
42
+
43
+ # ์‚ฌ์ด๋“œ๋ฐ” ์„ค์ •
44
+ with st.sidebar:
45
+ st.header("โš™๏ธ ์„ค์ •")
46
+
47
+ # API ํ† ํฐ ์ž…๋ ฅ (ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ)
48
+ if not api_token:
49
+ api_token_input = st.text_input(
50
+ "Replicate API Token",
51
+ type="password",
52
+ help="ํ™˜๊ฒฝ๋ณ€์ˆ˜ RAPI_TOKEN์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. API ํ† ํฐ์„ ์ž…๋ ฅํ•˜์„ธ์š”."
53
+ )
54
+ if api_token_input:
55
+ api_token = api_token_input
56
+ os.environ["REPLICATE_API_TOKEN"] = api_token
57
+ else:
58
+ st.success("โœ… API ํ† ํฐ์ด ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
59
+ os.environ["REPLICATE_API_TOKEN"] = api_token
60
+
61
+ st.divider()
62
+
63
+ # ํ™”๋ฉด ๋น„์œจ ์„ค์ •
64
+ st.subheader("๐Ÿ“ ํ™”๋ฉด ๋น„์œจ")
65
+ aspect_ratios = {
66
+ "16:9": "16:9 (YouTube, ์ผ๋ฐ˜ ๋™์˜์ƒ)",
67
+ "4:3": "4:3 (์ „ํ†ต์ ์ธ TV ํ˜•์‹)",
68
+ "1:1": "1:1 (Instagram ํ”ผ๋“œ)",
69
+ "3:4": "3:4 (Instagram ํฌํŠธ๋ ˆ์ดํŠธ)",
70
+ "9:16": "9:16 (Instagram ๋ฆด์Šค, TikTok)",
71
+ "21:9": "21:9 (์‹œ๋„ค๋งˆํ‹ฑ ์™€์ด๋“œ)",
72
+ "9:21": "9:21 (์šธํŠธ๋ผ ์„ธ๋กœํ˜•)"
73
+ }
74
+
75
+ selected_ratio = st.selectbox(
76
+ "๋น„์œจ ์„ ํƒ",
77
+ options=list(aspect_ratios.keys()),
78
+ format_func=lambda x: aspect_ratios[x],
79
+ index=0
80
+ )
81
+
82
+ st.divider()
83
+
84
+ # Seed ์„ค์ •
85
+ st.subheader("๐ŸŽฒ ๋žœ๋ค ์‹œ๋“œ")
86
+ seed = st.number_input(
87
+ "Seed ๊ฐ’",
88
+ min_value=0,
89
+ max_value=999999,
90
+ value=42,
91
+ help="๋™์ผํ•œ ์‹œ๋“œ๊ฐ’์œผ๋กœ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
92
+ )
93
+
94
+ st.divider()
95
+
96
+ # ๊ณ ์ • ์„ค์ • ํ‘œ์‹œ
97
+ st.subheader("๐Ÿ“‹ ๊ณ ์ • ์„ค์ •")
98
+ st.info("""
99
+ - **์žฌ์ƒ ์‹œ๊ฐ„**: 5์ดˆ
100
+ - **ํ•ด์ƒ๋„**: 480p
101
+ """)
102
+
103
+ # ๋ฉ”์ธ ์ปจํ…์ธ 
104
+ col1, col2 = st.columns([1, 1])
105
+
106
+ with col1:
107
+ st.header("๐ŸŽฏ ์ƒ์„ฑ ๋ชจ๋“œ ์„ ํƒ")
108
+ mode = st.radio(
109
+ "๋ชจ๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š”:",
110
+ ["ํ…์ŠคํŠธ to ๋น„๋””์˜ค", "์ด๋ฏธ์ง€ to ๋น„๋””์˜ค"],
111
+ help="ํ…์ŠคํŠธ ์„ค๋ช…์ด๋‚˜ ์ด๋ฏธ์ง€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋น„๋””์˜ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค."
112
+ )
113
+
114
+ # ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (์ด๋ฏธ์ง€ to ๋น„๋””์˜ค ๋ชจ๋“œ)
115
+ uploaded_image = None
116
+ image_base64 = None
117
+
118
+ if mode == "์ด๋ฏธ์ง€ to ๋น„๋””์˜ค":
119
+ st.subheader("๐Ÿ“ท ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ")
120
+ uploaded_file = st.file_uploader(
121
+ "์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜์„ธ์š”",
122
+ type=['png', 'jpg', 'jpeg', 'webp'],
123
+ help="์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋น„๋””์˜ค๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค."
124
+ )
125
+
126
+ if uploaded_file is not None:
127
+ # ์ด๋ฏธ์ง€ ํ‘œ์‹œ
128
+ uploaded_image = Image.open(uploaded_file)
129
+ st.image(uploaded_image, caption="์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€", use_column_width=True)
130
+
131
+ # ์ด๋ฏธ์ง€๋ฅผ base64๋กœ ๋ณ€ํ™˜
132
+ buffered = io.BytesIO()
133
+ uploaded_image.save(buffered, format="PNG")
134
+ image_base64 = base64.b64encode(buffered.getvalue()).decode()
135
+
136
+ with col2:
137
+ st.header("โœ๏ธ ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ")
138
+
139
+ if mode == "ํ…์ŠคํŠธ to ๋น„๋””์˜ค":
140
+ prompt_placeholder = "์ƒ์„ฑํ•  ๋น„๋””์˜ค๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.\n์˜ˆ: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn."
141
+ else:
142
+ prompt_placeholder = "์ด๋ฏธ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ์›€์ง์ด๊ฒŒ ํ• ์ง€ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.\n์˜ˆ: Camera slowly zooms in while clouds move across the sky. The subject's hair gently moves in the wind."
143
+
144
+ prompt = st.text_area(
145
+ "ํ”„๋กฌํ”„ํŠธ",
146
+ height=150,
147
+ placeholder=prompt_placeholder,
148
+ help="์ž์„ธํ•˜๊ณ  ๊ตฌ์ฒด์ ์ธ ์„ค๋ช…์ผ์ˆ˜๋ก ๋” ์ข‹์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
149
+ )
150
+
151
+ # ์ƒ์„ฑ ๋ฒ„ํŠผ
152
+ st.divider()
153
+
154
+ if st.button("๐ŸŽฌ ๋น„๋””์˜ค ์ƒ์„ฑ", type="primary", use_container_width=True):
155
+ # ์ž…๋ ฅ ๊ฒ€์ฆ
156
+ if not api_token:
157
+ st.error("โŒ API ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.")
158
+ elif not prompt:
159
+ st.error("โŒ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.")
160
+ elif mode == "์ด๋ฏธ์ง€ to ๋น„๋””์˜ค" and uploaded_image is None:
161
+ st.error("โŒ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.")
162
+ else:
163
+ try:
164
+ # ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”
165
+ progress_text = "๋น„๋””์˜ค ์ƒ์„ฑ ์ค‘... ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”."
166
+ progress_bar = st.progress(0, text=progress_text)
167
+
168
+ # ์ž…๋ ฅ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
169
+ input_params = {
170
+ "prompt": prompt,
171
+ "duration": 5,
172
+ "resolution": "480p",
173
+ "aspect_ratio": selected_ratio,
174
+ "seed": seed
175
+ }
176
+
177
+ # ์ด๋ฏธ์ง€ to ๋น„๋””์˜ค ๋ชจ๋“œ์ธ ๊ฒฝ์šฐ
178
+ if mode == "์ด๋ฏธ์ง€ to ๋น„๋””์˜ค" and image_base64:
179
+ input_params["image"] = f"data:image/png;base64,{image_base64}"
180
+
181
+ # ์ง„ํ–‰๋ฅ  ์—…๋ฐ์ดํŠธ
182
+ progress_bar.progress(25, text="Replicate API ํ˜ธ์ถœ ์ค‘...")
183
+
184
+ # Replicate ์‹คํ–‰
185
+ output = replicate.run(
186
+ "bytedance/seedance-1-lite",
187
+ input=input_params
188
+ )
189
+
190
+ # ์ง„ํ–‰๋ฅ  ์—…๋ฐ์ดํŠธ
191
+ progress_bar.progress(75, text="๋น„๋””์˜ค ๋‹ค์šด๋กœ๋“œ ์ค‘...")
192
+
193
+ # ๋น„๋””์˜ค ์ €์žฅ
194
+ if hasattr(output, 'read'):
195
+ video_data = output.read()
196
+ else:
197
+ # URL์ธ ๊ฒฝ์šฐ ๋‹ค์šด๋กœ๋“œ
198
+ import requests
199
+ response = requests.get(output)
200
+ video_data = response.content
201
+
202
+ # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
203
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
204
+ tmp_file.write(video_data)
205
+ tmp_filename = tmp_file.name
206
+
207
+ # ๋กœ์ปฌ ํŒŒ์ผ๋กœ๋„ ์ €์žฅ
208
+ output_filename = "output.mp4"
209
+ with open(output_filename, "wb") as file:
210
+ file.write(video_data)
211
+
212
+ # ์ง„ํ–‰๋ฅ  ์™„๋ฃŒ
213
+ progress_bar.progress(100, text="์™„๋ฃŒ!")
214
+
215
+ # ์„ฑ๊ณต ๋ฉ”์‹œ์ง€
216
+ st.success(f"โœ… ๋น„๋””์˜ค๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ({output_filename})")
217
+
218
+ # ๋น„๋””์˜ค ํ‘œ์‹œ
219
+ st.subheader("๐Ÿ“น ์ƒ์„ฑ๋œ ๋น„๋””์˜ค")
220
+ st.video(tmp_filename)
221
+
222
+ # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ
223
+ col1, col2, col3 = st.columns([1, 2, 1])
224
+ with col2:
225
+ st.download_button(
226
+ label="โฌ‡๏ธ ๋น„๋””์˜ค ๋‹ค์šด๋กœ๋“œ",
227
+ data=video_data,
228
+ file_name="generated_video.mp4",
229
+ mime="video/mp4",
230
+ use_container_width=True
231
+ )
232
+
233
+ # ์ƒ์„ฑ ์ •๋ณด ํ‘œ์‹œ
234
+ with st.expander("๐Ÿ“Š ์ƒ์„ฑ ์ •๋ณด"):
235
+ st.json({
236
+ "mode": mode,
237
+ "aspect_ratio": selected_ratio,
238
+ "seed": seed,
239
+ "duration": "5์ดˆ",
240
+ "resolution": "480p",
241
+ "prompt": prompt[:100] + "..." if len(prompt) > 100 else prompt
242
+ })
243
+
244
+ except Exception as e:
245
+ st.error(f"โŒ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}")
246
+ st.info("๐Ÿ’ก API ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅธ์ง€, ๋ชจ๋ธ์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
247
+
248
+ # ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
249
+ with st.expander("๐Ÿ“– ์‚ฌ์šฉ ๋ฐฉ๋ฒ•"):
250
+ st.markdown("""
251
+ ### ์„ค์น˜ ๋ฐ ์‹คํ–‰
252
+
253
+ 1. **ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜**:
254
+ ```bash
255
+ pip install streamlit replicate pillow requests
256
+ ```
257
+
258
+ 2. **ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •** (์„ ํƒ์‚ฌํ•ญ):
259
+ ```bash
260
+ export RAPI_TOKEN="your-replicate-api-token"
261
+ ```
262
+
263
+ 3. **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰**:
264
+ ```bash
265
+ streamlit run video_generator.py
266
+ ```
267
+
268
+ ### ๊ธฐ๋Šฅ ์„ค๋ช…
269
+
270
+ - **ํ…์ŠคํŠธ to ๋น„๋””์˜ค**: ํ…์ŠคํŠธ ์„ค๋ช…๋งŒ์œผ๋กœ ๋น„๋””์˜ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
271
+ - **์ด๋ฏธ์ง€ to ๋น„๋””์˜ค**: ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ ์›€์ง์ด๋Š” ๋น„๋””์˜ค๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
272
+ - **ํ™”๋ฉด ๋น„์œจ**: ๋‹ค์–‘ํ•œ SNS ํ”Œ๋žซํผ์— ์ตœ์ ํ™”๋œ ๋น„์œจ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
273
+ - **Seed ๊ฐ’**: ๋™์ผํ•œ ์‹œ๋“œ๊ฐ’์œผ๋กœ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
274
+
275
+ ### ํŒ
276
+
277
+ - ๊ตฌ์ฒด์ ์ด๊ณ  ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ์ผ์ˆ˜๋ก ๋” ์ข‹์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
278
+ - ์นด๋ฉ”๋ผ ์›€์ง์ž„, ์กฐ๋ช…, ๋ถ„์œ„๊ธฐ ๋“ฑ์„ ์„ค๋ช…์— ํฌํ•จ์‹œ์ผœ๋ณด์„ธ์š”.
279
+ - ์ด๋ฏธ์ง€ to ๋น„๋””์˜ค ๋ชจ๋“œ์—์„œ๋Š” ์ด๋ฏธ์ง€์˜ ์–ด๋–ค ๋ถ€๋ถ„์„ ์–ด๋–ป๊ฒŒ ์›€์ง์ผ์ง€ ์„ค๋ช…ํ•˜์„ธ์š”.
280
+ """)
281
+
282
+ # ํ‘ธํ„ฐ
283
+ st.divider()
284
+ st.markdown(
285
+ """
286
+ <div style='text-align: center; color: gray;'>
287
+ <p>Powered by Replicate AI and bytedance/seedance-1-lite model</p>
288
+ </div>
289
+ """,
290
+ unsafe_allow_html=True
291
+ )