Update app.py
Browse files
app.py
CHANGED
@@ -87,7 +87,7 @@ class YouTubeDownloader:
|
|
87 |
return youtube_regex.match(url) is not None
|
88 |
|
89 |
# ---------------------------------------------------------
|
90 |
-
# Gemini-AI
|
91 |
# ---------------------------------------------------------
|
92 |
def generate_scene_breakdown_gemini(self, video_info):
|
93 |
if not self.gemini_model:
|
@@ -105,28 +105,38 @@ class YouTubeDownloader:
|
|
105 |
}
|
106 |
|
107 |
prompt = f"""
|
108 |
-
이 YouTube
|
109 |
|
110 |
제목: {title}
|
111 |
재생시간: {duration}초
|
112 |
설명: {description}
|
113 |
|
114 |
-
|
115 |
-
1.
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
-
|
120 |
-
-
|
121 |
-
|
122 |
-
|
123 |
-
-
|
124 |
-
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
"""
|
131 |
response = self.gemini_model.generate_content(prompt)
|
132 |
|
@@ -149,11 +159,19 @@ class YouTubeDownloader:
|
|
149 |
|
150 |
# 영어 번역 생성
|
151 |
english_prompt = f"""
|
152 |
-
Translate the following Korean
|
|
|
153 |
|
154 |
{chr(10).join(korean_scenes)}
|
155 |
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
"""
|
158 |
english_response = self.gemini_model.generate_content(english_prompt)
|
159 |
|
@@ -184,7 +202,7 @@ Keep the format exactly the same: **[MM:SS-MM:SS]**: English translation...
|
|
184 |
return self.generate_scene_breakdown_fallback(video_info)
|
185 |
|
186 |
# ---------------------------------------------------------
|
187 |
-
# Fallback
|
188 |
# ---------------------------------------------------------
|
189 |
def generate_scene_breakdown_fallback(self, video_info):
|
190 |
duration = video_info.get("duration", 0)
|
@@ -198,14 +216,15 @@ Keep the format exactly the same: **[MM:SS-MM:SS]**: English translation...
|
|
198 |
"english": ["**[Duration Unknown]**: Unable to generate timestamped breakdown"]
|
199 |
}
|
200 |
|
|
|
201 |
if duration <= 60:
|
202 |
-
segment_length =
|
203 |
elif duration <= 300:
|
204 |
-
segment_length =
|
205 |
elif duration <= 900:
|
206 |
-
segment_length =
|
207 |
else:
|
208 |
-
segment_length =
|
209 |
|
210 |
korean_scenes = []
|
211 |
english_scenes = []
|
@@ -219,12 +238,16 @@ Keep the format exactly the same: **[MM:SS-MM:SS]**: English translation...
|
|
219 |
start_fmt = f"{start_time//60}:{start_time%60:02d}"
|
220 |
end_fmt = f"{end_time//60}:{end_time%60:02d}"
|
221 |
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
|
|
|
|
|
|
|
|
228 |
|
229 |
korean_scenes.append(f"**[{start_fmt}-{end_fmt}]**: {korean_desc}")
|
230 |
english_scenes.append(f"**[{start_fmt}-{end_fmt}]**: {english_desc}")
|
@@ -492,11 +515,11 @@ Keep the format exactly the same: **[MM:SS-MM:SS]**: English translation...
|
|
492 |
🎵 **배경음악/Background Music:** {bgm}
|
493 |
👑 **제작자 상태/Creator Status:** {creator}
|
494 |
|
495 |
-
|
496 |
{'─'*30}
|
497 |
{chr(10).join(korean_scenes)}
|
498 |
|
499 |
-
|
500 |
{'─'*30}
|
501 |
{chr(10).join(english_scenes)}
|
502 |
|
|
|
87 |
return youtube_regex.match(url) is not None
|
88 |
|
89 |
# ---------------------------------------------------------
|
90 |
+
# Gemini-AI 음성/대사 추출 (한글 우선)
|
91 |
# ---------------------------------------------------------
|
92 |
def generate_scene_breakdown_gemini(self, video_info):
|
93 |
if not self.gemini_model:
|
|
|
105 |
}
|
106 |
|
107 |
prompt = f"""
|
108 |
+
이 YouTube 비디오의 음성/대사를 타임스탬프별로 추출해주세요.
|
109 |
|
110 |
제목: {title}
|
111 |
재생시간: {duration}초
|
112 |
설명: {description}
|
113 |
|
114 |
+
매우 중요한 지침:
|
115 |
+
1. 실제 영상에서 들리는 대사, 내레이션, 음성을 그대로 적어주세요
|
116 |
+
2. 장면 설명이 아닌 실제 음성 내용만 작성하세요
|
117 |
+
3. 음성이 없는 부분은 (...) 또는 (배경음악) 등으로 표시
|
118 |
+
4. 타임스탬프 가이드라인:
|
119 |
+
- 대사나 내레이션이 시작하고 끝나는 지점 기준
|
120 |
+
- 연속된 대사는 하나로 묶어서 표시
|
121 |
+
- 최대한 자연스러운 단위로 구분
|
122 |
+
5. 형식:
|
123 |
+
**[MM:SS-MM:SS]**: "실제 대사나 내레이션 내용"
|
124 |
+
**[MM:SS-MM:SS]**: (배경음악) 또는 (...장면 전환...)
|
125 |
+
6. 모든 음성 내용을 빠짐없이 적어주세요
|
126 |
+
7. 자막이나 화면에 표시된 텍스트도 포함하세요
|
127 |
+
8. 장면 설명은 절대 하지 마세요. 오직 음성과 텍스트만 추출하세요.
|
128 |
+
|
129 |
+
예시:
|
130 |
+
**[00:00-00:05]**: "안녕하세요. 오늘은 미륵산에서 발견된 백제 유적에 대해 알아보겠습니다."
|
131 |
+
**[00:05-00:08]**: (배경음악)
|
132 |
+
**[00:08-00:15]**: "미륵사지를 품고 있는 익산 미륵산의 정상부에서 백제시대에 만든 것으로 추정되는 저수조가 발굴됐습니다."
|
133 |
+
**[00:15-00:18]**: (인터뷰 준비 중...)
|
134 |
+
**[00:18-00:25]**: [이도학 교수] "이번 발굴은 백제 역사 연구에 중요한 전환점이 될 것입니다."
|
135 |
+
|
136 |
+
뉴스의 경우:
|
137 |
+
- 앵커나 기자의 멘트는 그대로 적기
|
138 |
+
- 인터뷰는 [인터뷰이 이름] "내용" 형식으로
|
139 |
+
- 자막은 [자막] 내용 형식으로
|
140 |
"""
|
141 |
response = self.gemini_model.generate_content(prompt)
|
142 |
|
|
|
159 |
|
160 |
# 영어 번역 생성
|
161 |
english_prompt = f"""
|
162 |
+
Translate the following Korean speech/dialogue transcription to English, maintaining the exact same timestamps.
|
163 |
+
Translate ONLY the actual speech content, not descriptions:
|
164 |
|
165 |
{chr(10).join(korean_scenes)}
|
166 |
|
167 |
+
Important rules:
|
168 |
+
- Keep the format exactly the same: **[MM:SS-MM:SS]**: "English translation of speech"
|
169 |
+
- For non-speech parts like (배경음악), translate as (background music)
|
170 |
+
- For (...) keep as is
|
171 |
+
- For interview tags like [이도학 교수], translate as [Professor Lee Do-hak]
|
172 |
+
- For [자막], translate as [Subtitle]
|
173 |
+
- Keep quotation marks for actual speech
|
174 |
+
- Do NOT add any scene descriptions or explanations
|
175 |
"""
|
176 |
english_response = self.gemini_model.generate_content(english_prompt)
|
177 |
|
|
|
202 |
return self.generate_scene_breakdown_fallback(video_info)
|
203 |
|
204 |
# ---------------------------------------------------------
|
205 |
+
# Fallback 음성/대사 추출 (한글/영어)
|
206 |
# ---------------------------------------------------------
|
207 |
def generate_scene_breakdown_fallback(self, video_info):
|
208 |
duration = video_info.get("duration", 0)
|
|
|
216 |
"english": ["**[Duration Unknown]**: Unable to generate timestamped breakdown"]
|
217 |
}
|
218 |
|
219 |
+
# 비디오 타입에 따른 대사 템플릿
|
220 |
if duration <= 60:
|
221 |
+
segment_length = 10
|
222 |
elif duration <= 300:
|
223 |
+
segment_length = 15
|
224 |
elif duration <= 900:
|
225 |
+
segment_length = 20
|
226 |
else:
|
227 |
+
segment_length = 30
|
228 |
|
229 |
korean_scenes = []
|
230 |
english_scenes = []
|
|
|
238 |
start_fmt = f"{start_time//60}:{start_time%60:02d}"
|
239 |
end_fmt = f"{end_time//60}:{end_time%60:02d}"
|
240 |
|
241 |
+
# 음성 추출이 불가능한 경우의 기본 템플릿
|
242 |
+
if i == 0:
|
243 |
+
korean_desc = f"(음성 추출을 위해 Gemini API가 필요합니다. 인트로 부분...)"
|
244 |
+
english_desc = f"(Gemini API required for speech extraction. Intro section...)"
|
245 |
+
elif i == num_segments - 1:
|
246 |
+
korean_desc = f"(아웃트로 부분...)"
|
247 |
+
english_desc = f"(Outro section...)"
|
248 |
+
else:
|
249 |
+
korean_desc = f"(본문 내용...)"
|
250 |
+
english_desc = f"(Main content...)"
|
251 |
|
252 |
korean_scenes.append(f"**[{start_fmt}-{end_fmt}]**: {korean_desc}")
|
253 |
english_scenes.append(f"**[{start_fmt}-{end_fmt}]**: {english_desc}")
|
|
|
515 |
🎵 **배경음악/Background Music:** {bgm}
|
516 |
👑 **제작자 상태/Creator Status:** {creator}
|
517 |
|
518 |
+
🎙️ 음성/대사 추출 (한국어) / SPEECH/DIALOGUE EXTRACTION (KOREAN)
|
519 |
{'─'*30}
|
520 |
{chr(10).join(korean_scenes)}
|
521 |
|
522 |
+
🎙️ 음성/대사 추출 (영어) / SPEECH/DIALOGUE EXTRACTION (ENGLISH)
|
523 |
{'─'*30}
|
524 |
{chr(10).join(english_scenes)}
|
525 |
|