ginipick commited on
Commit
dc72519
·
verified ·
1 Parent(s): 4d975d1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +274 -254
app.py CHANGED
@@ -1,291 +1,311 @@
 
 
 
 
 
 
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
- )
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ AI Video Generator with Gradio
4
+ Single file application - app.py
5
+ """
6
+
7
  import os
8
+ import gradio as gr
9
  import replicate
10
+ import base64
11
  from PIL import Image
12
  import io
13
+ import requests
14
+ from datetime import datetime
15
  import tempfile
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  # API 토큰 설정
18
  api_token = os.getenv("RAPI_TOKEN")
19
+ if api_token:
20
+ os.environ["REPLICATE_API_TOKEN"] = api_token
21
 
22
+ # 화면 비율 옵션
23
+ ASPECT_RATIOS = {
24
+ "16:9": "16:9 (YouTube, 일반 동영상)",
25
+ "4:3": "4:3 (전통적인 TV 형식)",
26
+ "1:1": "1:1 (Instagram 피드)",
27
+ "3:4": "3:4 (Instagram 포트레이트)",
28
+ "9:16": "9:16 (Instagram 릴스, TikTok)",
29
+ "21:9": "21:9 (시네마틱 와이드)",
30
+ "9:21": "9:21 (울트라 세로형)"
31
+ }
32
+
33
+ def update_prompt_placeholder(mode):
34
+ """모드에 따라 프롬프트 플레이스홀더 업데이트"""
35
+ if mode == "텍스트 to 비디오":
36
+ 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.")
37
  else:
38
+ return gr.update(placeholder="이미지를 어떻게 움직이게 할지 설명해주세요.\n예: Camera slowly zooms in while clouds move across the sky. The subject's hair gently moves in the wind.")
39
+
40
+ def update_image_input(mode):
41
+ """모드에 따라 이미지 입력 표시/숨김"""
42
+ if mode == "이미지 to 비디오":
43
+ return gr.update(visible=True)
44
+ else:
45
+ return gr.update(visible=False)
46
+
47
+ def generate_video(mode, prompt, image, aspect_ratio, seed, api_key_input, progress=gr.Progress()):
48
+ """비디오 생성 메인 함수"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ # API 토큰 확인
51
+ token = api_key_input or api_token
52
+ if not token:
53
+ return None, " API 토큰이 필요합니다. 환경변수 RAPI_TOKEN을 설정하거나 API 키를 입력하세요."
 
 
 
 
 
54
 
55
+ os.environ["REPLICATE_API_TOKEN"] = token
56
 
57
+ # 입력 검증
58
+ if not prompt:
59
+ return None, "❌ 프롬프트를 입력해주세요."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ if mode == "이미지 to 비디오" and image is None:
62
+ return None, "❌ 이미지를 업로드해주세요."
 
63
 
64
+ try:
65
+ progress(0, desc="비디오 생성 준비 중...")
 
 
 
 
 
66
 
67
+ # 입력 파라미터 설정
68
+ input_params = {
69
+ "prompt": prompt,
70
+ "duration": 5,
71
+ "resolution": "480p",
72
+ "aspect_ratio": aspect_ratio,
73
+ "seed": seed
74
+ }
75
+
76
+ # 이미지 to 비디오 모드
77
+ if mode == "이미지 to 비디오" and image is not None:
78
+ progress(0.1, desc="이미지 처리 중...")
79
+
80
+ # PIL Image를 base64로 변환
81
+ if isinstance(image, str): # 파일 경로인 경우
82
+ with Image.open(image) as img:
83
+ buffered = io.BytesIO()
84
+ img.save(buffered, format="PNG")
85
+ image_base64 = base64.b64encode(buffered.getvalue()).decode()
86
+ else: # PIL Image 객체인 경우
87
+ buffered = io.BytesIO()
88
+ image.save(buffered, format="PNG")
89
+ image_base64 = base64.b64encode(buffered.getvalue()).decode()
90
 
91
+ input_params["image"] = f"data:image/png;base64,{image_base64}"
92
+
93
+ progress(0.3, desc="Replicate API 호출 중...")
94
+
95
+ # Replicate 실행
96
+ output = replicate.run(
97
+ "bytedance/seedance-1-lite",
98
+ input=input_params
99
+ )
100
+
101
+ progress(0.7, desc="비디오 다운로드 중...")
102
+
103
+ # 비디오 데이터 가져오기
104
+ if hasattr(output, 'read'):
105
+ video_data = output.read()
106
+ else:
107
+ # URL인 경우 다운로드
108
+ response = requests.get(output)
109
+ video_data = response.content
110
+
111
+ # 임시 파일로 저장
112
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
113
+ tmp_file.write(video_data)
114
+ video_path = tmp_file.name
115
+
116
+ # output.mp4로도 저장
117
+ with open("output.mp4", "wb") as file:
118
+ file.write(video_data)
119
+
120
+ progress(1.0, desc="완료!")
121
+
122
+ # 생성 정보
123
+ info = f"""✅ 비디오가 성공적으로 생성되었습니다!
124
 
125
+ 📊 생성 정보:
126
+ - 모드: {mode}
127
+ - 화면 비율: {aspect_ratio}
128
+ - Seed: {seed}
129
+ - 재생 시간: 5초
130
+ - 해상도: 480p
131
+ - 파일: output.mp4"""
132
+
133
+ return video_path, info
134
+
135
+ except Exception as e:
136
+ error_msg = f"❌ 오류가 발생했습니다: {str(e)}"
137
+ return None, error_msg
138
+
139
+ # Gradio 인터페이스 생성
140
+ with gr.Blocks(title="AI Video Generator", theme=gr.themes.Soft()) as app:
141
+ gr.Markdown("""
142
+ # 🎬 AI Video Generator
143
 
144
+ **Replicate API**를 사용하여 텍스트나 이미지로부터 비디오를 생성합니다.
 
 
 
145
 
146
+ [![Powered by Replicate](https://img.shields.io/badge/Powered%20by-Replicate-blue)](https://replicate.com/)
147
+ """)
148
+
149
+ with gr.Row():
150
+ with gr.Column(scale=1):
151
+ # API 설정
152
+ with gr.Accordion("⚙️ API 설정", open=not bool(api_token)):
153
+ if api_token:
154
+ gr.Markdown("✅ API 토큰이 환경변수에서 로드되었습니다.")
155
+ api_key_input = gr.Textbox(
156
+ label="Replicate API Token (선택사항)",
157
+ type="password",
158
+ placeholder="환경변수를 덮어쓰려면 여기에 입력",
159
+ value=""
160
+ )
161
+ else:
162
+ gr.Markdown("⚠️ 환경변수 RAPI_TOKEN이 설정되지 않았습니다.")
163
+ api_key_input = gr.Textbox(
164
+ label="Replicate API Token (필수)",
165
+ type="password",
166
+ placeholder="Replicate API 토큰을 입력하세요",
167
+ value=""
168
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
+ # 생성 모드
171
+ mode = gr.Radio(
172
+ label="🎯 생성 모드",
173
+ choices=["텍스트 to 비디오", "이미지 to 비디오"],
174
+ value="텍스트 to 비디오"
175
  )
176
 
177
+ # 이미지 업로드
178
+ image_input = gr.Image(
179
+ label="📷 이미지 업로드",
180
+ type="pil",
181
+ visible=False
182
+ )
 
 
 
 
 
183
 
184
+ # 화면 비율
185
+ aspect_ratio = gr.Dropdown(
186
+ label="📐 화면 비율",
187
+ choices=list(ASPECT_RATIOS.keys()),
188
+ value="16:9",
189
+ info="SNS 플랫폼에 최적화된 비율을 선택하세요"
190
+ )
191
 
192
+ # 비율 설명 표시
193
+ ratio_info = gr.Markdown(value=f"선택된 비율: {ASPECT_RATIOS['16:9']}")
 
 
194
 
195
+ # Seed 설정
196
+ seed = gr.Number(
197
+ label="🎲 랜덤 시드",
198
+ value=42,
199
+ precision=0,
200
+ info="동일한 시드값으로 동일한 결과를 재현할 수 있습니다"
201
+ )
202
 
203
+ # 고정 설정 표시
204
+ gr.Markdown("""
205
+ ### 📋 고정 설정
206
+ - **재생 시간**: 5초
207
+ - **해상도**: 480p
208
+ """)
209
+
210
+ with gr.Column(scale=2):
211
+ # 프롬프트 입력
212
+ prompt = gr.Textbox(
213
+ label="✍️ 프롬프트",
214
+ lines=5,
215
+ placeholder="생성할 비디오를 설명해주세요.\n예: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn."
216
+ )
217
 
218
+ # 생성 버튼
219
+ generate_btn = gr.Button("🎬 비디오 생성", variant="primary", size="lg")
 
220
 
221
+ # 결과 표시
222
+ with gr.Column():
223
+ output_video = gr.Video(
224
+ label="📹 생성된 비디오",
225
+ autoplay=True
226
+ )
227
+ output_info = gr.Textbox(
228
+ label="정보",
229
+ lines=8,
230
+ interactive=False
231
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
+ # 사용 방법
234
+ with gr.Accordion("📖 사용 방법", open=False):
235
+ gr.Markdown("""
236
+ ### 설치 방법
237
+
238
+ 1. **필요한 패키지 설치**:
239
+ ```bash
240
+ pip install gradio replicate pillow requests
241
+ ```
242
+
243
+ 2. **환경변수 설정** (선택사항):
244
+ ```bash
245
+ export RAPI_TOKEN="your-replicate-api-token"
246
+ ```
247
+
248
+ 3. **실행**:
249
+ ```bash
250
+ python app.py
251
+ ```
252
+
253
+ ### 기능 설명
254
+
255
+ - **텍스트 to 비디오**: 텍스트 설명만으로 비디오를 생성합니다.
256
+ - **이미지 to 비디오**: 업로드한 이미지를 움직이는 비디오로 변환합니다.
257
+ - **화면 비율**: 다양한 SNS 플랫폼에 최적화된 비율을 선택할 수 있습니다.
258
+ - **Seed 값**: 동일한 시드값으로 동일한 결과를 재현할 수 있습니다.
259
+
260
+ ### 프롬프트 작성 팁
261
+
262
+ - 구체적이고 상세한 설명을 사용하세요
263
+ - 카메라 움직임을 명시하세요 (예: zoom in, pan left, tracking shot)
264
+ - 조명과 분위기를 설명하세요 (예: golden hour, dramatic lighting)
265
+ - 움직임의 속도를 지정하세요 (예: slowly, rapidly, gently)
266
+ """)
267
 
268
+ # 예시
269
+ gr.Examples(
270
+ examples=[
271
+ ["텍스트 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],
272
+ ["텍스트 to 비디오", "Urban street scene at night with neon lights reflecting on wet pavement. People walking with umbrellas, camera tracking forward.", None, "9:16", 123],
273
+ ["텍스트 to 비디오", "Close-up of a flower blooming in time-lapse, soft natural lighting, shallow depth of field.", None, "1:1", 789],
274
+ ],
275
+ inputs=[mode, prompt, image_input, aspect_ratio, seed],
276
+ label="예시 프롬프트"
277
+ )
278
 
279
+ # 이벤트 핸들러
280
+ mode.change(
281
+ fn=update_prompt_placeholder,
282
+ inputs=[mode],
283
+ outputs=[prompt]
284
+ )
285
 
286
+ mode.change(
287
+ fn=update_image_input,
288
+ inputs=[mode],
289
+ outputs=[image_input]
290
+ )
291
 
292
+ aspect_ratio.change(
293
+ fn=lambda x: f"선택된 비율: {ASPECT_RATIOS[x]}",
294
+ inputs=[aspect_ratio],
295
+ outputs=[ratio_info]
296
+ )
297
 
298
+ generate_btn.click(
299
+ fn=generate_video,
300
+ inputs=[mode, prompt, image_input, aspect_ratio, seed, api_key_input],
301
+ outputs=[output_video, output_info]
302
+ )
303
 
304
+ # 앱 실행
305
+ if __name__ == "__main__":
306
+ app.launch(
307
+ server_name="0.0.0.0",
308
+ server_port=7860,
309
+ share=False,
310
+ inbrowser=True
311
+ )