leicam commited on
Commit
0e92a90
·
verified ·
1 Parent(s): 047f823

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -59
app.py CHANGED
@@ -17,11 +17,14 @@ try:
17
  genai.configure(api_key=GEMINI_API_KEY)
18
  LLM = genai.GenerativeModel(LLM_MODEL_NAME)
19
  LLM_AVAILABLE = True
 
20
  else:
21
  LLM = None
22
- except Exception:
 
23
  LLM = None
24
  LLM_AVAILABLE = False
 
25
 
26
  # Config
27
  FPS = 24
@@ -66,20 +69,13 @@ def parse_transcript_full(txt: str) -> List[Segment]:
66
  lines = txt.splitlines()
67
  results: List[Segment] = []
68
 
69
- # Regex flexível: aceita vários formatos
70
- # [00:00:00:00 - 00:00:10:00] Texto
71
- # 00:00:00:00 - 00:00:10:00 Texto
72
- # 00:00:00:00 — 00:00:10:00 Texto
73
  pattern = re.compile(
74
  r'^\s*\[?\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*[-—–]\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*\]?\s*(.*)$'
75
  )
76
 
77
- current_text_buffer = []
78
-
79
  for line in lines:
80
  line = line.strip()
81
 
82
- # Pula linhas vazias ou apenas "Desconhecido"
83
  if not line or line == "Desconhecido":
84
  continue
85
 
@@ -89,7 +85,6 @@ def parse_transcript_full(txt: str) -> List[Segment]:
89
  start_tc, end_tc, text = match.groups()
90
  text = text.strip()
91
 
92
- # Se não tem texto na mesma linha, pega da próxima
93
  if not text or text == "Desconhecido":
94
  continue
95
 
@@ -107,15 +102,10 @@ def parse_transcript_full(txt: str) -> List[Segment]:
107
  score=0.0
108
  ))
109
  except Exception as e:
110
- print(f"⚠ Erro ao processar: {line[:60]}... -> {e}")
111
  continue
112
 
113
  print(f"✓ Encontrados {len(results)} segmentos na transcrição")
114
-
115
- if not results:
116
- print("⚠ AVISO: Nenhum segmento válido encontrado!")
117
- print("Formato esperado: 00:00:00:00 - 00:00:10:00 Texto")
118
-
119
  return results
120
 
121
  # ============ MANUAL TIMECODES ============
@@ -156,12 +146,12 @@ def extract_duration_and_keywords(instructions: str) -> Tuple[Optional[float], L
156
  match = re.search(pattern, instructions_lower)
157
  if match:
158
  duration = float(match.group(1))
 
159
  break
160
 
161
  # Extrai palavras-chave importantes
162
  keywords = []
163
 
164
- # Busca por tópicos específicos
165
  topic_keywords = {
166
  'tenista': ['tenista', 'tênis', 'jogador', 'kinguios'],
167
  'maria': ['maria', 'josé', 'casal', 'seguro', 'carro'],
@@ -174,6 +164,7 @@ def extract_duration_and_keywords(instructions: str) -> Tuple[Optional[float], L
174
  if any(term in instructions_lower for term in terms):
175
  keywords.append(key)
176
 
 
177
  return duration, keywords
178
 
179
  def find_segment_by_content(segs: List[Segment], keywords: List[str]) -> int:
@@ -192,11 +183,13 @@ def find_segment_by_content(segs: List[Segment], keywords: List[str]) -> int:
192
  best_score = score
193
  best_idx = idx
194
 
 
195
  return best_idx
196
 
197
  def ai_find_start_point(segs: List[Segment], instructions: str, keywords: List[str]) -> int:
198
  """Usa IA para encontrar ponto de início"""
199
  if not LLM_AVAILABLE:
 
200
  return find_segment_by_content(segs, keywords)
201
 
202
  # Cria resumo dos primeiros 150 segmentos
@@ -204,7 +197,7 @@ def ai_find_start_point(segs: List[Segment], instructions: str, keywords: List[s
204
  for i, s in enumerate(segs[:150]):
205
  duration = (s.end_f - s.start_f) / FPS
206
  segments_preview.append(
207
- f"{i}. [{s.start_tc}] ({duration:.1f}s) {s.text[:100]}"
208
  )
209
 
210
  prompt = f"""Você é um editor de vídeo. Encontre o índice do segmento onde deve COMEÇAR o corte.
@@ -218,18 +211,20 @@ SEGMENTOS DISPONÍVEIS:
218
  IMPORTANTE:
219
  - Analise onde está o conteúdo solicitado
220
  - Retorne APENAS o número do índice (exemplo: 87)
221
- - Se mencionar "tenista", procure por "tenista", "tênis", "Kinguios"
222
- - Se mencionar "Maria", procure por "Maria", "José", "carro"
223
 
224
  RESPONDA APENAS COM O NÚMERO:"""
225
 
226
  try:
 
227
  response = LLM.generate_content(prompt, generation_config={
228
  "temperature": 0.1,
229
  "max_output_tokens": 50
230
  })
231
 
232
  text = (response.text or "").strip()
 
 
233
  match = re.search(r'\b(\d+)\b', text)
234
 
235
  if match:
@@ -239,11 +234,11 @@ RESPONDA APENAS COM O NÚMERO:"""
239
  return idx
240
 
241
  except Exception as e:
242
- print(f"⚠ Erro na IA, usando busca por palavras-chave: {e}")
243
 
244
- # Fallback: busca por palavras-chave
245
  fallback_idx = find_segment_by_content(segs, keywords)
246
- print(f"✓ Usando busca por palavras-chave, início no segmento {fallback_idx}")
247
  return fallback_idx
248
 
249
  def create_continuous_cut(segs: List[Segment], start_idx: int, duration_minutes: float) -> List[Segment]:
@@ -257,7 +252,14 @@ def create_continuous_cut(segs: List[Segment], start_idx: int, duration_minutes:
257
  start_frame = start_seg.start_f
258
  end_frame = start_frame + target_frames
259
 
260
- # Cria texto combinado dos segmentos envolvidos
 
 
 
 
 
 
 
261
  involved_segs = []
262
  for seg in segs[start_idx:]:
263
  if seg.start_f < end_frame:
@@ -276,6 +278,7 @@ def create_continuous_cut(segs: List[Segment], start_idx: int, duration_minutes:
276
  score=100.0
277
  )
278
 
 
279
  return [result]
280
 
281
  def ai_select_segments(segs: List[Segment], instructions: str) -> List[Segment]:
@@ -283,26 +286,22 @@ def ai_select_segments(segs: List[Segment], instructions: str) -> List[Segment]:
283
  if not segs:
284
  raise ValueError("Nenhum segmento disponível")
285
 
 
 
286
  # Extrai duração e palavras-chave
287
  duration, keywords = extract_duration_and_keywords(instructions)
288
 
289
- print(f"Instruções analisadas - Duração: {duration}min, Keywords: {keywords}")
290
-
291
  if duration:
292
  # Modo: corte contínuo de X minutos
 
293
  start_idx = ai_find_start_point(segs, instructions, keywords)
294
  result = create_continuous_cut(segs, start_idx, duration)
295
-
296
- print(f"✓ Corte criado: {result[0].start_tc} → {result[0].end_tc} ({duration}min)")
297
  return result
298
 
299
  else:
300
- # Modo: seleção múltipla de trechos
301
  print("⚠ Duração não especificada, usando modo de seleção múltipla")
302
-
303
  start_idx = ai_find_start_point(segs, instructions, keywords)
304
-
305
- # Retorna 10 segmentos a partir do ponto encontrado
306
  selected = segs[start_idx:start_idx + 10]
307
 
308
  if not selected:
@@ -352,10 +351,14 @@ def select_segments(transcript_txt: str, use_llm: bool, num_segments: int,
352
  weight_learn: float, weight_viral: float) -> List[Segment]:
353
  """Função principal de seleção"""
354
 
 
 
 
 
355
  # Prioridade 1: Timecodes manuais
356
  manual_ranges = parse_manual_timecodes(manual_timecodes)
357
  if manual_ranges:
358
- print(f"Modo: MANUAL - {len(manual_ranges)} ranges")
359
  result_segs = []
360
  for start_tc, end_tc in manual_ranges:
361
  try:
@@ -378,15 +381,19 @@ def select_segments(transcript_txt: str, use_llm: bool, num_segments: int,
378
  segs = parse_transcript_full(transcript_txt)
379
 
380
  if not segs:
381
- raise ValueError("Nenhum segmento válido encontrado na transcrição. Verifique o formato: 00:00:00:00 - 00:00:10:00 Texto")
382
 
383
- # Prioridade 2: Instruções em linguagem natural com IA
384
- if natural_instructions.strip() and use_llm and LLM_AVAILABLE:
385
- print(f"Modo: IA com linguagem natural")
 
 
 
 
386
  return ai_select_segments(segs, natural_instructions)
387
 
388
  # Prioridade 3: Modo automático com pontuação
389
- print(f"Modo: AUTOMÁTICO por pontuação")
390
  weights = {
391
  "emotion": weight_emotion,
392
  "break": weight_break,
@@ -421,7 +428,6 @@ def edit_sequence_with_segments(tree: ET.ElementTree, segs: List[Segment]) -> ET
421
  if v_tpl is None or a_tpl is None:
422
  raise ValueError("Clipitem template não encontrado")
423
 
424
- # Copia estrutura do template
425
  def deep_copy(elem):
426
  new = ET.Element(elem.tag, attrib=elem.attrib)
427
  new.text = elem.text
@@ -447,7 +453,7 @@ def edit_sequence_with_segments(tree: ET.ElementTree, segs: List[Segment]) -> ET
447
  v_id = f"clip-v-{idx}"
448
  a_id = f"clip-a-{idx}"
449
 
450
- # Cria video clip
451
  v_ci = ET.Element("clipitem", {"id": v_id})
452
  v_name = ET.SubElement(v_ci, "name")
453
  v_name.text = f"Clip {idx}"
@@ -467,7 +473,7 @@ def edit_sequence_with_segments(tree: ET.ElementTree, segs: List[Segment]) -> ET
467
  v_link = ET.SubElement(v_ci, "link")
468
  ET.SubElement(v_link, "linkclipref").text = a_id
469
 
470
- # Cria audio clip
471
  a_ci = ET.Element("clipitem", {"id": a_id})
472
  a_name = ET.SubElement(a_ci, "name")
473
  a_name.text = f"Clip {idx}"
@@ -501,7 +507,7 @@ def process_xml_and_transcript(xml_file, txt_file, use_llm, num_segments,
501
  """Processa XML e transcrição"""
502
 
503
  if not xml_file:
504
- return "❌ Envie o arquivo XML do Premiere", None, f"LLM: {LLM_AVAILABLE}"
505
 
506
  manual_ranges = parse_manual_timecodes(manual_timecodes)
507
  has_instructions = natural_instructions.strip() != ""
@@ -511,18 +517,16 @@ def process_xml_and_transcript(xml_file, txt_file, use_llm, num_segments,
511
  mode = "MANUAL"
512
  transcript = ""
513
  elif has_instructions:
514
- mode = "IA (Linguagem Natural)"
515
  if not txt_file:
516
- return "❌ Para usar IA, envie a transcrição (.txt)", None, f"LLM: {LLM_AVAILABLE}"
517
- if not LLM_AVAILABLE:
518
- return "❌ IA não disponível. Configure GEMINI_API_KEY nas variáveis de ambiente", None, "LLM: False"
519
 
520
  with open(txt_file.name, "r", encoding="utf-8") as f:
521
  transcript = f.read()
522
  else:
523
  mode = "AUTOMÁTICO"
524
  if not txt_file:
525
- return "❌ Envie a transcrição (.txt)", None, f"LLM: {LLM_AVAILABLE}"
526
 
527
  with open(txt_file.name, "r", encoding="utf-8") as f:
528
  transcript = f.read()
@@ -536,7 +540,7 @@ def process_xml_and_transcript(xml_file, txt_file, use_llm, num_segments,
536
  )
537
 
538
  if not segs:
539
- return "❌ Nenhum segmento foi selecionado", None, f"LLM: {LLM_AVAILABLE}"
540
 
541
  # Edita XML
542
  tree = ET.parse(xml_file.name)
@@ -550,7 +554,7 @@ def process_xml_and_transcript(xml_file, txt_file, use_llm, num_segments,
550
  # Gera resumo
551
  total_duration = sum((s.end_f - s.start_f) / FPS for s in segs)
552
 
553
- resumo = f"✂️ {len(segs)} corte(s) | Duração total: {total_duration/60:.1f} min | Modo: {mode}\n\n"
554
 
555
  for i, s in enumerate(segs, 1):
556
  dur = (s.end_f - s.start_f) / FPS
@@ -559,15 +563,17 @@ def process_xml_and_transcript(xml_file, txt_file, use_llm, num_segments,
559
  resumo += f" {s.text[:150]}\n"
560
  resumo += "\n"
561
 
562
- status = f" Sucesso! | Modo: {mode} | Duração: {total_duration/60:.1f} min | LLM: {LLM_AVAILABLE}"
 
 
563
 
564
  return resumo, out_path, status
565
 
566
  except Exception as e:
567
  import traceback
568
  error_detail = traceback.format_exc()
569
- print(f"ERRO COMPLETO:\n{error_detail}")
570
- return f"❌ Erro: {str(e)}", None, f"LLM: {LLM_AVAILABLE}"
571
 
572
  # ============ CSS & GRADIO APP ============
573
  css = """
@@ -596,7 +602,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css, title="Editor XML Premiere") as
596
  use_llm = gr.Checkbox(
597
  label="🤖 Usar IA (Gemini)",
598
  value=USE_LLM_DEFAULT and LLM_AVAILABLE,
599
- info="Requer GEMINI_API_KEY configurada"
600
  )
601
  num_segments = gr.Slider(
602
  2, 20, 5, step=1,
@@ -610,6 +616,8 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css, title="Editor XML Premiere") as
610
  - `Crie um corte de 15 minutos com os melhores momentos`
611
  - `Faça um corte de 5 minutos sobre Maria e José`
612
  - `Corte de 8 minutos a partir de onde fala sobre protocolo`
 
 
613
  """)
614
  natural_instructions = gr.Textbox(
615
  label="Suas instruções",
@@ -659,10 +667,4 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css, title="Editor XML Premiere") as
659
  gr.Markdown("""
660
  ---
661
  **💡 Dicas:**
662
- - Formato da transcrição: `00:00:00:00 - 00:00:10:00 Texto aqui`
663
- - Para cortes contínuos, especifique a duração (ex: "10 minutos")
664
- - Use palavras-chave específicas do conteúdo para melhor precisão
665
- """)
666
-
667
- if __name__ == "__main__":
668
- demo.launch()
 
17
  genai.configure(api_key=GEMINI_API_KEY)
18
  LLM = genai.GenerativeModel(LLM_MODEL_NAME)
19
  LLM_AVAILABLE = True
20
+ print("✓ IA Gemini configurada com sucesso")
21
  else:
22
  LLM = None
23
+ print("⚠ GEMINI_API_KEY não encontrada")
24
+ except Exception as e:
25
  LLM = None
26
  LLM_AVAILABLE = False
27
+ print(f"⚠ Erro ao configurar IA: {e}")
28
 
29
  # Config
30
  FPS = 24
 
69
  lines = txt.splitlines()
70
  results: List[Segment] = []
71
 
 
 
 
 
72
  pattern = re.compile(
73
  r'^\s*\[?\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*[-—–]\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*\]?\s*(.*)$'
74
  )
75
 
 
 
76
  for line in lines:
77
  line = line.strip()
78
 
 
79
  if not line or line == "Desconhecido":
80
  continue
81
 
 
85
  start_tc, end_tc, text = match.groups()
86
  text = text.strip()
87
 
 
88
  if not text or text == "Desconhecido":
89
  continue
90
 
 
102
  score=0.0
103
  ))
104
  except Exception as e:
105
+ print(f"⚠ Erro ao processar linha: {e}")
106
  continue
107
 
108
  print(f"✓ Encontrados {len(results)} segmentos na transcrição")
 
 
 
 
 
109
  return results
110
 
111
  # ============ MANUAL TIMECODES ============
 
146
  match = re.search(pattern, instructions_lower)
147
  if match:
148
  duration = float(match.group(1))
149
+ print(f"✓ Duração extraída: {duration} minutos")
150
  break
151
 
152
  # Extrai palavras-chave importantes
153
  keywords = []
154
 
 
155
  topic_keywords = {
156
  'tenista': ['tenista', 'tênis', 'jogador', 'kinguios'],
157
  'maria': ['maria', 'josé', 'casal', 'seguro', 'carro'],
 
164
  if any(term in instructions_lower for term in terms):
165
  keywords.append(key)
166
 
167
+ print(f"✓ Keywords encontradas: {keywords}")
168
  return duration, keywords
169
 
170
  def find_segment_by_content(segs: List[Segment], keywords: List[str]) -> int:
 
183
  best_score = score
184
  best_idx = idx
185
 
186
+ print(f"✓ Melhor match no segmento {best_idx} (score: {best_score})")
187
  return best_idx
188
 
189
  def ai_find_start_point(segs: List[Segment], instructions: str, keywords: List[str]) -> int:
190
  """Usa IA para encontrar ponto de início"""
191
  if not LLM_AVAILABLE:
192
+ print("⚠ IA não disponível, usando busca por keywords")
193
  return find_segment_by_content(segs, keywords)
194
 
195
  # Cria resumo dos primeiros 150 segmentos
 
197
  for i, s in enumerate(segs[:150]):
198
  duration = (s.end_f - s.start_f) / FPS
199
  segments_preview.append(
200
+ f"{i}. [{s.start_tc}] ({duration:.1f}s) {s.text[:80]}"
201
  )
202
 
203
  prompt = f"""Você é um editor de vídeo. Encontre o índice do segmento onde deve COMEÇAR o corte.
 
211
  IMPORTANTE:
212
  - Analise onde está o conteúdo solicitado
213
  - Retorne APENAS o número do índice (exemplo: 87)
214
+ - Considere o contexto e o início da história relevante
 
215
 
216
  RESPONDA APENAS COM O NÚMERO:"""
217
 
218
  try:
219
+ print("🤖 Consultando IA...")
220
  response = LLM.generate_content(prompt, generation_config={
221
  "temperature": 0.1,
222
  "max_output_tokens": 50
223
  })
224
 
225
  text = (response.text or "").strip()
226
+ print(f"IA respondeu: {text}")
227
+
228
  match = re.search(r'\b(\d+)\b', text)
229
 
230
  if match:
 
234
  return idx
235
 
236
  except Exception as e:
237
+ print(f"⚠ Erro na IA: {e}")
238
 
239
+ # Fallback
240
  fallback_idx = find_segment_by_content(segs, keywords)
241
+ print(f"✓ Usando fallback no segmento {fallback_idx}")
242
  return fallback_idx
243
 
244
  def create_continuous_cut(segs: List[Segment], start_idx: int, duration_minutes: float) -> List[Segment]:
 
252
  start_frame = start_seg.start_f
253
  end_frame = start_frame + target_frames
254
 
255
+ # Garante que não ultrapassa o último segmento
256
+ max_frame = segs[-1].end_f
257
+ if end_frame > max_frame:
258
+ end_frame = max_frame
259
+ actual_duration = (end_frame - start_frame) / FPS / 60
260
+ print(f"⚠ Ajustado para {actual_duration:.1f} min (limite da transcrição)")
261
+
262
+ # Cria texto combinado
263
  involved_segs = []
264
  for seg in segs[start_idx:]:
265
  if seg.start_f < end_frame:
 
278
  score=100.0
279
  )
280
 
281
+ print(f"✓ Corte criado: {result.start_tc} → {result.end_tc}")
282
  return [result]
283
 
284
  def ai_select_segments(segs: List[Segment], instructions: str) -> List[Segment]:
 
286
  if not segs:
287
  raise ValueError("Nenhum segmento disponível")
288
 
289
+ print(f"📝 Processando instruções: {instructions[:100]}...")
290
+
291
  # Extrai duração e palavras-chave
292
  duration, keywords = extract_duration_and_keywords(instructions)
293
 
 
 
294
  if duration:
295
  # Modo: corte contínuo de X minutos
296
+ print(f"Modo: CORTE CONTÍNUO de {duration} minutos")
297
  start_idx = ai_find_start_point(segs, instructions, keywords)
298
  result = create_continuous_cut(segs, start_idx, duration)
 
 
299
  return result
300
 
301
  else:
302
+ # Modo: seleção múltipla (fallback)
303
  print("⚠ Duração não especificada, usando modo de seleção múltipla")
 
304
  start_idx = ai_find_start_point(segs, instructions, keywords)
 
 
305
  selected = segs[start_idx:start_idx + 10]
306
 
307
  if not selected:
 
351
  weight_learn: float, weight_viral: float) -> List[Segment]:
352
  """Função principal de seleção"""
353
 
354
+ print("\n" + "="*60)
355
+ print("INICIANDO SELEÇÃO DE SEGMENTOS")
356
+ print("="*60)
357
+
358
  # Prioridade 1: Timecodes manuais
359
  manual_ranges = parse_manual_timecodes(manual_timecodes)
360
  if manual_ranges:
361
+ print(f"Modo: MANUAL - {len(manual_ranges)} ranges")
362
  result_segs = []
363
  for start_tc, end_tc in manual_ranges:
364
  try:
 
381
  segs = parse_transcript_full(transcript_txt)
382
 
383
  if not segs:
384
+ raise ValueError("Nenhum segmento válido encontrado. Formato esperado: 00:00:00:00 - 00:00:10:00 Texto")
385
 
386
+ # Prioridade 2: Instruções em linguagem natural
387
+ if natural_instructions.strip():
388
+ print(f"Modo: LINGUAGEM NATURAL")
389
+ print(f" Instruções: {natural_instructions[:100]}...")
390
+ print(f" IA disponível: {LLM_AVAILABLE}")
391
+
392
+ # Funciona mesmo sem IA, usando keywords
393
  return ai_select_segments(segs, natural_instructions)
394
 
395
  # Prioridade 3: Modo automático com pontuação
396
+ print(f"Modo: AUTOMÁTICO por pontuação")
397
  weights = {
398
  "emotion": weight_emotion,
399
  "break": weight_break,
 
428
  if v_tpl is None or a_tpl is None:
429
  raise ValueError("Clipitem template não encontrado")
430
 
 
431
  def deep_copy(elem):
432
  new = ET.Element(elem.tag, attrib=elem.attrib)
433
  new.text = elem.text
 
453
  v_id = f"clip-v-{idx}"
454
  a_id = f"clip-a-{idx}"
455
 
456
+ # Video clip
457
  v_ci = ET.Element("clipitem", {"id": v_id})
458
  v_name = ET.SubElement(v_ci, "name")
459
  v_name.text = f"Clip {idx}"
 
473
  v_link = ET.SubElement(v_ci, "link")
474
  ET.SubElement(v_link, "linkclipref").text = a_id
475
 
476
+ # Audio clip
477
  a_ci = ET.Element("clipitem", {"id": a_id})
478
  a_name = ET.SubElement(a_ci, "name")
479
  a_name.text = f"Clip {idx}"
 
507
  """Processa XML e transcrição"""
508
 
509
  if not xml_file:
510
+ return "❌ Envie o arquivo XML do Premiere", None, f"LLM: {'✓' if LLM_AVAILABLE else '✗'}"
511
 
512
  manual_ranges = parse_manual_timecodes(manual_timecodes)
513
  has_instructions = natural_instructions.strip() != ""
 
517
  mode = "MANUAL"
518
  transcript = ""
519
  elif has_instructions:
520
+ mode = "IA (Linguagem Natural)" if (use_llm and LLM_AVAILABLE) else "Linguagem Natural (sem IA)"
521
  if not txt_file:
522
+ return "❌ Para usar linguagem natural, envie a transcrição (.txt)", None, f"LLM: {'✓' if LLM_AVAILABLE else '✗'}"
 
 
523
 
524
  with open(txt_file.name, "r", encoding="utf-8") as f:
525
  transcript = f.read()
526
  else:
527
  mode = "AUTOMÁTICO"
528
  if not txt_file:
529
+ return "❌ Envie a transcrição (.txt)", None, f"LLM: {'✓' if LLM_AVAILABLE else '✗'}"
530
 
531
  with open(txt_file.name, "r", encoding="utf-8") as f:
532
  transcript = f.read()
 
540
  )
541
 
542
  if not segs:
543
+ return "❌ Nenhum segmento foi selecionado", None, f"LLM: {'✓' if LLM_AVAILABLE else '✗'}"
544
 
545
  # Edita XML
546
  tree = ET.parse(xml_file.name)
 
554
  # Gera resumo
555
  total_duration = sum((s.end_f - s.start_f) / FPS for s in segs)
556
 
557
+ resumo = f" {len(segs)} corte(s) criado(s) | Duração total: {total_duration/60:.1f} min | Modo: {mode}\n\n"
558
 
559
  for i, s in enumerate(segs, 1):
560
  dur = (s.end_f - s.start_f) / FPS
 
563
  resumo += f" {s.text[:150]}\n"
564
  resumo += "\n"
565
 
566
+ status = f" Sucesso! | Modo: {mode} | Duração: {total_duration/60:.1f} min | LLM: {'✓' if LLM_AVAILABLE else '✗'}"
567
+
568
+ print(f"\n{status}\n")
569
 
570
  return resumo, out_path, status
571
 
572
  except Exception as e:
573
  import traceback
574
  error_detail = traceback.format_exc()
575
+ print(f"\n❌ ERRO:\n{error_detail}\n")
576
+ return f"❌ Erro: {str(e)}\n\nDetalhes no console", None, f"LLM: {'✓' if LLM_AVAILABLE else '✗'}"
577
 
578
  # ============ CSS & GRADIO APP ============
579
  css = """
 
602
  use_llm = gr.Checkbox(
603
  label="🤖 Usar IA (Gemini)",
604
  value=USE_LLM_DEFAULT and LLM_AVAILABLE,
605
+ info="Requer GEMINI_API_KEY configurada" if not LLM_AVAILABLE else "IA configurada ✓"
606
  )
607
  num_segments = gr.Slider(
608
  2, 20, 5, step=1,
 
616
  - `Crie um corte de 15 minutos com os melhores momentos`
617
  - `Faça um corte de 5 minutos sobre Maria e José`
618
  - `Corte de 8 minutos a partir de onde fala sobre protocolo`
619
+
620
+ **IMPORTANTE:** Sempre especifique a duração desejada (ex: "10 minutos")
621
  """)
622
  natural_instructions = gr.Textbox(
623
  label="Suas instruções",
 
667
  gr.Markdown("""
668
  ---
669
  **💡 Dicas:**
670
+ - Formato da transcrição: `00:00:00:00 - 00