leicam commited on
Commit
152c610
·
verified ·
1 Parent(s): a472a48

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +206 -92
app.py CHANGED
@@ -109,12 +109,8 @@ def keyword_score(text: str, custom_keywords: str = "", weight_emotion: float =
109
  return score
110
 
111
  def parse_manual_timecodes(manual_input: str) -> List[tuple]:
112
- """Parse manual timecode ranges from user input.
113
- Expected format: hh:mm:ss:ff - hh:mm:ss:ff (one per line or comma-separated)
114
- Returns list of (start_tc, end_tc) tuples
115
- """
116
  manual_ranges = []
117
- # Replace commas with newlines for flexibility
118
  normalized = manual_input.replace(",", "\n")
119
  lines = [l.strip() for l in normalized.splitlines() if l.strip()]
120
 
@@ -128,6 +124,60 @@ def parse_manual_timecodes(manual_input: str) -> List[tuple]:
128
 
129
  return manual_ranges
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  def llm_rank_segments(candidates: List[Segment], num_segments: int, custom_instructions: str = "") -> List[Segment]:
132
  """Ask the LLM to pick segments based on criteria."""
133
  if not LLM_AVAILABLE:
@@ -160,15 +210,13 @@ def llm_rank_segments(candidates: List[Segment], num_segments: int, custom_instr
160
  return candidates[:num_segments]
161
 
162
  def select_segments(transcript_txt: str, use_llm: bool, num_segments: int,
163
- custom_keywords: str, manual_timecodes: str,
164
  weight_emotion: float, weight_break: float,
165
  weight_learn: float, weight_viral: float) -> List[Segment]:
166
 
167
- # Check if user provided manual timecodes
168
  manual_ranges = parse_manual_timecodes(manual_timecodes)
169
-
170
  if manual_ranges:
171
- # Manual mode: use only the timecodes provided by user
172
  result_segs = []
173
  for start_tc, end_tc in manual_ranges:
174
  try:
@@ -192,7 +240,11 @@ def select_segments(transcript_txt: str, use_llm: bool, num_segments: int,
192
 
193
  return result_segs
194
 
195
- # Automatic mode: use transcript + scoring
 
 
 
 
196
  segs = parse_transcript(transcript_txt)
197
  if not segs:
198
  raise ValueError("Nenhum trecho válido encontrado na transcrição.")
@@ -316,26 +368,36 @@ def edit_sequence_with_segments(tree: ET.ElementTree, segs: List[Segment]) -> ET
316
 
317
  # ---- Gradio app ----
318
  def process_xml_and_transcript(premiere_xml_file, transcript_txt_file, use_llm,
319
- num_segments, custom_keywords, manual_timecodes,
320
  weight_emotion, weight_break, weight_learn, weight_viral):
321
  if premiere_xml_file is None:
322
  return "Envie o XML do Premiere.", None, f"LLM disponível: {LLM_AVAILABLE}"
323
 
324
- # Check if manual timecodes were provided
325
  manual_ranges = parse_manual_timecodes(manual_timecodes)
 
326
 
 
327
  if manual_ranges:
328
- # Manual mode: don't need transcript
329
  transcript = ""
 
 
 
 
 
 
 
 
330
  else:
331
- # Automatic mode: need transcript
332
  if transcript_txt_file is None:
333
  return "Envie a transcrição em .txt ou forneça minutagens manuais.", None, f"LLM disponível: {LLM_AVAILABLE}"
334
  with open(transcript_txt_file.name, "r", encoding="utf-8") as f:
335
  transcript = f.read()
336
 
337
  segs = select_segments(transcript, use_llm and LLM_AVAILABLE, num_segments,
338
- custom_keywords, manual_timecodes,
339
  weight_emotion, weight_break, weight_learn, weight_viral)
340
 
341
  tree = ET.parse(premiere_xml_file.name)
@@ -345,7 +407,6 @@ def process_xml_and_transcript(premiere_xml_file, transcript_txt_file, use_llm,
345
  out_path = os.path.join(OUTPUT_DIR, f"{base}_EDITADO.xml")
346
  tree.write(out_path, encoding="utf-8", xml_declaration=True)
347
 
348
- mode = "MANUAL" if manual_ranges else "AUTOMÁTICO"
349
  resumo = f"✂️ {len(segs)} cortes aplicados - Modo: {mode} (24 fps):\n\n"
350
  for i, s in enumerate(segs, 1):
351
  dur_sec = (s.end_f - s.start_f) / FPS
@@ -354,16 +415,16 @@ def process_xml_and_transcript(premiere_xml_file, transcript_txt_file, use_llm,
354
  resumo += f" Score: {s.score:.1f} | {s.text[:150]}\n"
355
  resumo += "\n"
356
 
357
- status = f"✓ Modo: {mode} | LLM disponível: {LLM_AVAILABLE} | LLM usado: {use_llm and LLM_AVAILABLE and not manual_ranges}"
358
  return resumo, out_path, status
359
 
360
  css = """
361
  /* Design Tokens */
362
  :root {
363
  --neon: #39FF14;
364
- --txt: #0a0a0a;
365
- --muted: #6b7280;
366
- --line: #e5e7eb;
367
  --bg: #ffffff;
368
  }
369
 
@@ -392,6 +453,12 @@ css = """
392
  font-size: 16px !important;
393
  }
394
 
 
 
 
 
 
 
395
  /* Buttons */
396
  .gradio-container button.primary {
397
  background: var(--neon) !important;
@@ -423,6 +490,7 @@ css = """
423
  border-radius: 12px !important;
424
  background: #fff !important;
425
  transition: all 0.2s ease !important;
 
426
  }
427
 
428
  .gradio-container input:focus, .gradio-container textarea:focus {
@@ -447,7 +515,7 @@ css = """
447
  /* Accordion */
448
  .gradio-container .label-wrap {
449
  font-weight: 700 !important;
450
- color: #fff !important;
451
  }
452
 
453
  /* Checkboxes */
@@ -472,25 +540,14 @@ css = """
472
  background: #fafafa !important;
473
  }
474
 
475
- /* Badges/Tags */
476
- .gradio-container .badge {
477
- background: var(--neon) !important;
478
- color: #000 !important;
479
- font-size: 12px !important;
480
- font-weight: 900 !important;
481
- padding: 4px 8px !important;
482
- border-radius: 999px !important;
483
- }
484
-
485
- /* Status indicators */
486
- .gradio-container .generating {
487
- border-color: var(--neon) !important;
488
  }
489
 
490
- /* Container spacing */
491
- .gradio-container .contain {
492
- max-width: 1120px !important;
493
- margin: 0 auto !important;
494
  }
495
  """
496
 
@@ -500,11 +557,11 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
500
  <div style="text-align: center; padding: 24px 0 16px;">
501
  <div style="display: inline-flex; align-items: center; gap: 8px; margin-bottom: 12px;">
502
  <div style="width: 12px; height: 12px; border-radius: 50%; background: #39FF14; box-shadow: 0 0 20px rgba(57,255,20,0.4);"></div>
503
- <h1 style="margin: 0; font-weight: 800; letter-spacing: -0.4px;">Agente de Edição XML · Premiere</h1>
504
  </div>
505
- <p style="color: #6b7280; max-width: 720px; margin: 0 auto; line-height: 1.65;">
506
  Edite sua sequência do Premiere com <strong>controle total</strong> sobre a seleção de trechos.
507
- Modo manual ou automático com IA.
508
  </p>
509
  </div>
510
  """)
@@ -513,8 +570,8 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
513
  with gr.Column():
514
  gr.HTML("""<div style="background: linear-gradient(135deg, #f9fafb 0%, #fff 100%);
515
  padding: 16px; border-radius: 16px; border: 1px solid #e5e7eb; margin-bottom: 16px;">
516
- <div style="font-weight: 700; color: #0a0a0a; margin-bottom: 8px;">Arquivos de entrada</div>
517
- <p style="color: #6b7280; font-size: 14px; margin: 0;">Envie o XML exportado do Premiere e a transcrição</p>
518
  </div>""")
519
  xml_in = gr.File(label="XML da sequência (FCP XML)", file_types=[".xml"])
520
  txt_in = gr.File(label="Transcrição (.txt) - Opcional no modo manual", file_types=[".txt"])
@@ -522,26 +579,59 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
522
  with gr.Column():
523
  gr.HTML("""<div style="background: linear-gradient(135deg, rgba(57,255,20,0.08) 0%, rgba(57,255,20,0.02) 100%);
524
  padding: 16px; border-radius: 16px; border: 1px solid #e5e7eb; margin-bottom: 16px;">
525
- <div style="font-weight: 700; color: #0a0a0a; margin-bottom: 8px;">Configurações básicas</div>
526
- <p style="color: #6b7280; font-size: 14px; margin: 0;">Ajuste o comportamento do processamento</p>
527
  </div>""")
528
  use_llm = gr.Checkbox(
529
- label="Usar Potência Criativa",
530
  value=USE_LLM_DEFAULT and LLM_AVAILABLE,
531
  info="Usa IA para escolher os melhores trechos narrativamente"
532
  )
533
  num_segments = gr.Slider(
534
  minimum=2, maximum=10, step=1, value=5,
535
- label="Número de segmentos (só no modo automático)",
536
  info="Quantos trechos incluir no vídeo final"
537
  )
538
 
539
- with gr.Accordion("MINUTAGENS MANUAIS (Sobrescreve tudo)", open=True):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  gr.HTML("""<div style="background: #fffbeb; padding: 12px; border-radius: 10px; border: 1px solid #fde68a; margin-bottom: 12px;">
541
- <strong style="color: #92400e;">Modo de Controle Total</strong>
542
  <p style="color: #78350f; font-size: 13px; margin: 6px 0 0;">
543
- Se preencher este campo, o app ignora a transcrição e todos os outros parâmetros,
544
- cortando EXATAMENTE o que você especificou.
545
  </p>
546
  </div>""")
547
  manual_timecodes = gr.Textbox(
@@ -551,8 +641,8 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
551
  )
552
  gr.HTML("""
553
  <div style="padding: 12px; background: #f9fafb; border-radius: 10px; margin-top: 12px;">
554
- <div style="font-weight: 600; margin-bottom: 8px; color: #0a0a0a;">Formatos aceitos:</div>
555
- <ul style="margin: 0; padding-left: 20px; color: #0a0a0a; font-size: 13px;">
556
  <li><code>hh:mm:ss:ff - hh:mm:ss:ff</code> (um por linha)</li>
557
  <li>Pode separar por vírgula também</li>
558
  <li>Exemplo: <code>00:01:30:00 - 00:02:00:15, 00:05:10:00 - 00:06:20:10</code></li>
@@ -560,36 +650,36 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
560
  </div>
561
  """)
562
 
563
- with gr.Accordion("Palavras-chave Personalizadas", open=False):
564
  custom_keywords = gr.Textbox(
565
  label="Adicione palavras-chave importantes (separadas por vírgula)",
566
  placeholder="Exemplo: transformação, resultado, método, estratégia",
567
  info="Trechos com essas palavras terão prioridade máxima (peso 3.0) - Só funciona no modo automático"
568
  )
569
 
570
- with gr.Accordion("Ajuste Fino dos Pesos de Pontuação", open=False):
571
- gr.HTML("""<p style="color: #6b7280; margin-bottom: 16px;">
572
  Ajuste a importância de cada categoria na pontuação heurística (modo automático)</p>""")
573
  with gr.Row():
574
- weight_emotion = gr.Slider(0, 5, value=2.0, step=0.1, label="Emoção")
575
- weight_break = gr.Slider(0, 5, value=1.5, step=0.1, label="Quebra de expectativa")
576
  with gr.Row():
577
- weight_learn = gr.Slider(0, 5, value=1.2, step=0.1, label="Aprendizado")
578
- weight_viral = gr.Slider(0, 5, value=1.0, step=0.1, label="Viralização")
579
 
580
  gr.HTML("""<div style="margin: 24px 0; text-align: center;">
581
  <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 16px 0;">
582
  </div>""")
583
 
584
- run_btn = gr.Button("Processar e Gerar XML Editado", variant="primary", size="lg")
585
 
586
  gr.HTML("""<div style="margin: 20px 0;">
587
  <hr style="border: none; border-top: 1px solid #e5e7eb;">
588
  </div>""")
589
 
590
  gr.HTML("""<div style="text-align: center; margin-bottom: 16px;">
591
- <div style="font-weight: 700; color: #0a0a0a; margin-bottom: 4px;">Resultados</div>
592
- <p style="color: #6b7280; font-size: 14px; margin: 0;">Resumo dos cortes e arquivo para download</p>
593
  </div>""")
594
 
595
  with gr.Row():
@@ -602,49 +692,73 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
602
  run_btn.click(
603
  process_xml_and_transcript,
604
  inputs=[xml_in, txt_in, use_llm, num_segments, custom_keywords,
605
- manual_timecodes, weight_emotion, weight_break, weight_learn, weight_viral],
606
  outputs=[resumo_out, file_out, status_out]
607
  )
608
 
609
- with gr.Accordion("Guia de Uso", open=False):
610
  gr.HTML("""
611
  <div style="padding: 16px; background: #f9fafb; border-radius: 12px;">
612
- <div style="margin-bottom: 20px;">
613
- <div style="display: inline-block; background: #39FF14; color: #000; padding: 4px 10px;
614
- border-radius: 999px; font-size: 12px; font-weight: 900; margin-bottom: 8px;">
615
- MODO MANUAL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  </div>
617
- <h3 style="margin: 8px 0; font-weight: 700; color: #0a0a0a;">Controle Total</h3>
618
- <ul style="color: #0a0a0a; line-height: 1.65; padding-left: 20px;">
619
- <li>Preencha o campo "Minutagens Manuais" com seus timecodes exatos</li>
 
620
  <li>A transcrição se torna opcional</li>
621
- <li>Todos os outros parâmetros são ignorados</li>
622
- <li>O corte será feito EXATAMENTE como você especificou</li>
623
  </ul>
624
  </div>
625
 
626
- <div style="margin-bottom: 20px;">
627
- <div style="display: inline-block; background: #dbeafe; color: #1e40af; padding: 4px 10px;
628
- border-radius: 999px; font-size: 12px; font-weight: 900; margin-bottom: 8px;">
629
- MODO AUTOMÁTICO
630
  </div>
631
- <h3 style="margin: 8px 0; font-weight: 700; color: #0a0a0a;">Seleção Inteligente</h3>
632
- <ul style="color: #0a0a0a; line-height: 1.65; padding-left: 20px;">
633
- <li>Deixe as minutagens manuais vazias</li>
634
  <li>Envie a transcrição com timecodes</li>
635
- <li>Configure LLM, palavras-chave e pesos conforme desejado</li>
636
- <li>O app escolhe os melhores trechos automaticamente</li>
 
637
  </ul>
638
  </div>
639
 
640
- <div style="background: #fff; padding: 12px; border-radius: 8px; border: 1px solid #e5e7eb;">
641
- <div style="font-weight: 700; color: #0a0a0a; margin-bottom: 8px;">Exemplos de minutagens manuais:</div>
642
- <code style="display: block; background: #f3f4f6; padding: 10px; border-radius: 6px;
643
- font-size: 13px; color: #374151; font-family: monospace;">
644
- 00:01:23:15 - 00:02:45:10<br>
645
- 00:05:30:00 - 00:07:15:22<br>
646
- 00:10:00:05 - 00:12:30:18
647
- </code>
648
  </div>
649
  </div>
650
  """)
@@ -653,7 +767,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
653
  <footer style="margin-top: 40px; padding: 24px 0; border-top: 1px solid #e5e7eb; text-align: center;">
654
  <div style="display: inline-flex; align-items: center; gap: 8px; margin-bottom: 8px;">
655
  <div style="width: 10px; height: 10px; border-radius: 50%; background: #39FF14;"></div>
656
- <span style="font-weight: 700; color: #0a0a0a;">Leicam · Tech</span>
657
  </div>
658
  <p style="color: #6b7280; font-size: 13px; margin: 0;">
659
  Ferramentas práticas para produção de conteúdo
 
109
  return score
110
 
111
  def parse_manual_timecodes(manual_input: str) -> List[tuple]:
112
+ """Parse manual timecode ranges from user input."""
 
 
 
113
  manual_ranges = []
 
114
  normalized = manual_input.replace(",", "\n")
115
  lines = [l.strip() for l in normalized.splitlines() if l.strip()]
116
 
 
124
 
125
  return manual_ranges
126
 
127
+ def llm_process_natural_instructions(transcript_txt: str, natural_instructions: str, num_segments: int) -> List[Segment]:
128
+ """Use LLM to interpret natural language instructions and select segments."""
129
+ if not LLM_AVAILABLE:
130
+ raise ValueError("LLM não disponível. Configure GEMINI_API_KEY para usar instruções em linguagem natural.")
131
+
132
+ segs = parse_transcript(transcript_txt)
133
+ if not segs:
134
+ raise ValueError("Nenhum trecho válido encontrado na transcrição.")
135
+
136
+ # Format segments for LLM
137
+ segments_text = "\n".join([
138
+ f"{i}. [{s.start_tc} - {s.end_tc}] {s.text}"
139
+ for i, s in enumerate(segs)
140
+ ])
141
+
142
+ prompt = f"""Você é um editor de vídeo profissional. Analise a transcrição abaixo e as instruções do usuário.
143
+
144
+ INSTRUÇÕES DO USUÁRIO:
145
+ {natural_instructions}
146
+
147
+ TRANSCRIÇÃO COM TIMECODES:
148
+ {segments_text}
149
+
150
+ TAREFA:
151
+ 1. Interprete as instruções do usuário (ex: "separe os melhores momentos", "recorte só a parte sobre medo", "remova quando fala almondega")
152
+ 2. Selecione os {num_segments} trechos que melhor atendem às instruções
153
+ 3. Se a instrução for para REMOVER algo, selecione os trechos que NÃO contêm aquilo
154
+ 4. Se a instrução for para INCLUIR algo específico, selecione apenas os trechos que contêm aquilo
155
+ 5. Priorize trechos com narrativa coerente e impactantes
156
+
157
+ RESPONDA APENAS com os índices dos trechos selecionados, separados por vírgula (ex: 0,3,5,8,12).
158
+ Não adicione explicações, apenas os números."""
159
+
160
+ try:
161
+ response = LLM.generate_content(prompt, generation_config={"temperature": 0.3})
162
+ txt = (response.text or "").strip()
163
+
164
+ # Extract indices
165
+ idxs = [int(x) for x in re.findall(r"\d+", txt)]
166
+ idxs = [i for i in idxs if 0 <= i < len(segs)]
167
+
168
+ if not idxs:
169
+ raise ValueError("LLM não retornou índices válidos")
170
+
171
+ selected = [segs[i] for i in idxs[:num_segments]]
172
+
173
+ # Sort by timeline order
174
+ selected.sort(key=lambda x: x.start_f)
175
+
176
+ return selected
177
+
178
+ except Exception as e:
179
+ raise ValueError(f"Erro ao processar instruções com LLM: {e}")
180
+
181
  def llm_rank_segments(candidates: List[Segment], num_segments: int, custom_instructions: str = "") -> List[Segment]:
182
  """Ask the LLM to pick segments based on criteria."""
183
  if not LLM_AVAILABLE:
 
210
  return candidates[:num_segments]
211
 
212
  def select_segments(transcript_txt: str, use_llm: bool, num_segments: int,
213
+ custom_keywords: str, manual_timecodes: str, natural_instructions: str,
214
  weight_emotion: float, weight_break: float,
215
  weight_learn: float, weight_viral: float) -> List[Segment]:
216
 
217
+ # Priority 1: Manual timecodes
218
  manual_ranges = parse_manual_timecodes(manual_timecodes)
 
219
  if manual_ranges:
 
220
  result_segs = []
221
  for start_tc, end_tc in manual_ranges:
222
  try:
 
240
 
241
  return result_segs
242
 
243
+ # Priority 2: Natural language instructions with LLM
244
+ if natural_instructions.strip() and use_llm and LLM_AVAILABLE:
245
+ return llm_process_natural_instructions(transcript_txt, natural_instructions, num_segments)
246
+
247
+ # Priority 3: Automatic mode with scoring
248
  segs = parse_transcript(transcript_txt)
249
  if not segs:
250
  raise ValueError("Nenhum trecho válido encontrado na transcrição.")
 
368
 
369
  # ---- Gradio app ----
370
  def process_xml_and_transcript(premiere_xml_file, transcript_txt_file, use_llm,
371
+ num_segments, custom_keywords, manual_timecodes, natural_instructions,
372
  weight_emotion, weight_break, weight_learn, weight_viral):
373
  if premiere_xml_file is None:
374
  return "Envie o XML do Premiere.", None, f"LLM disponível: {LLM_AVAILABLE}"
375
 
376
+ # Check priorities
377
  manual_ranges = parse_manual_timecodes(manual_timecodes)
378
+ has_natural_instructions = natural_instructions.strip() != ""
379
 
380
+ # Determine mode
381
  if manual_ranges:
382
+ mode = "MANUAL"
383
  transcript = ""
384
+ elif has_natural_instructions:
385
+ mode = "INSTRUÇÕES NATURAIS (IA)"
386
+ if transcript_txt_file is None:
387
+ return "Para usar instruções em linguagem natural, envie a transcrição.", None, f"LLM disponível: {LLM_AVAILABLE}"
388
+ if not LLM_AVAILABLE:
389
+ return "LLM não disponível. Configure GEMINI_API_KEY para usar instruções naturais.", None, f"LLM disponível: {LLM_AVAILABLE}"
390
+ with open(transcript_txt_file.name, "r", encoding="utf-8") as f:
391
+ transcript = f.read()
392
  else:
393
+ mode = "AUTOMÁTICO"
394
  if transcript_txt_file is None:
395
  return "Envie a transcrição em .txt ou forneça minutagens manuais.", None, f"LLM disponível: {LLM_AVAILABLE}"
396
  with open(transcript_txt_file.name, "r", encoding="utf-8") as f:
397
  transcript = f.read()
398
 
399
  segs = select_segments(transcript, use_llm and LLM_AVAILABLE, num_segments,
400
+ custom_keywords, manual_timecodes, natural_instructions,
401
  weight_emotion, weight_break, weight_learn, weight_viral)
402
 
403
  tree = ET.parse(premiere_xml_file.name)
 
407
  out_path = os.path.join(OUTPUT_DIR, f"{base}_EDITADO.xml")
408
  tree.write(out_path, encoding="utf-8", xml_declaration=True)
409
 
 
410
  resumo = f"✂️ {len(segs)} cortes aplicados - Modo: {mode} (24 fps):\n\n"
411
  for i, s in enumerate(segs, 1):
412
  dur_sec = (s.end_f - s.start_f) / FPS
 
415
  resumo += f" Score: {s.score:.1f} | {s.text[:150]}\n"
416
  resumo += "\n"
417
 
418
+ status = f"✓ Modo: {mode} | LLM disponível: {LLM_AVAILABLE} | LLM usado: {use_llm and LLM_AVAILABLE}"
419
  return resumo, out_path, status
420
 
421
  css = """
422
  /* Design Tokens */
423
  :root {
424
  --neon: #39FF14;
425
+ --txt: #1a1a1a;
426
+ --muted: #4b5563;
427
+ --line: #d1d5db;
428
  --bg: #ffffff;
429
  }
430
 
 
453
  font-size: 16px !important;
454
  }
455
 
456
+ /* Labels */
457
+ .gradio-container label {
458
+ color: var(--txt) !important;
459
+ font-weight: 600 !important;
460
+ }
461
+
462
  /* Buttons */
463
  .gradio-container button.primary {
464
  background: var(--neon) !important;
 
490
  border-radius: 12px !important;
491
  background: #fff !important;
492
  transition: all 0.2s ease !important;
493
+ color: var(--txt) !important;
494
  }
495
 
496
  .gradio-container input:focus, .gradio-container textarea:focus {
 
515
  /* Accordion */
516
  .gradio-container .label-wrap {
517
  font-weight: 700 !important;
518
+ color: var(--txt) !important;
519
  }
520
 
521
  /* Checkboxes */
 
540
  background: #fafafa !important;
541
  }
542
 
543
+ /* Info text */
544
+ .gradio-container .gr-form-info {
545
+ color: var(--muted) !important;
 
 
 
 
 
 
 
 
 
 
546
  }
547
 
548
+ /* Textbox content */
549
+ .gradio-container textarea {
550
+ color: var(--txt) !important;
 
551
  }
552
  """
553
 
 
557
  <div style="text-align: center; padding: 24px 0 16px;">
558
  <div style="display: inline-flex; align-items: center; gap: 8px; margin-bottom: 12px;">
559
  <div style="width: 12px; height: 12px; border-radius: 50%; background: #39FF14; box-shadow: 0 0 20px rgba(57,255,20,0.4);"></div>
560
+ <h1 style="margin: 0; font-weight: 800; letter-spacing: -0.4px; color: #1a1a1a;">Agente de Edição XML · Premiere</h1>
561
  </div>
562
+ <p style="color: #4b5563; max-width: 720px; margin: 0 auto; line-height: 1.65;">
563
  Edite sua sequência do Premiere com <strong>controle total</strong> sobre a seleção de trechos.
564
+ Use linguagem natural, modo manual ou automático com IA.
565
  </p>
566
  </div>
567
  """)
 
570
  with gr.Column():
571
  gr.HTML("""<div style="background: linear-gradient(135deg, #f9fafb 0%, #fff 100%);
572
  padding: 16px; border-radius: 16px; border: 1px solid #e5e7eb; margin-bottom: 16px;">
573
+ <div style="font-weight: 700; color: #1a1a1a; margin-bottom: 8px;">📁 Arquivos de entrada</div>
574
+ <p style="color: #4b5563; font-size: 14px; margin: 0;">Envie o XML exportado do Premiere e a transcrição</p>
575
  </div>""")
576
  xml_in = gr.File(label="XML da sequência (FCP XML)", file_types=[".xml"])
577
  txt_in = gr.File(label="Transcrição (.txt) - Opcional no modo manual", file_types=[".txt"])
 
579
  with gr.Column():
580
  gr.HTML("""<div style="background: linear-gradient(135deg, rgba(57,255,20,0.08) 0%, rgba(57,255,20,0.02) 100%);
581
  padding: 16px; border-radius: 16px; border: 1px solid #e5e7eb; margin-bottom: 16px;">
582
+ <div style="font-weight: 700; color: #1a1a1a; margin-bottom: 8px;">⚙️ Configurações básicas</div>
583
+ <p style="color: #4b5563; font-size: 14px; margin: 0;">Ajuste o comportamento do processamento</p>
584
  </div>""")
585
  use_llm = gr.Checkbox(
586
+ label="🤖 Usar Potência Criativa (IA)",
587
  value=USE_LLM_DEFAULT and LLM_AVAILABLE,
588
  info="Usa IA para escolher os melhores trechos narrativamente"
589
  )
590
  num_segments = gr.Slider(
591
  minimum=2, maximum=10, step=1, value=5,
592
+ label="Número de segmentos",
593
  info="Quantos trechos incluir no vídeo final"
594
  )
595
 
596
+ with gr.Accordion("🎯 INSTRUÇÕES EM LINGUAGEM NATURAL (IA) - NOVO!", open=True):
597
+ gr.HTML("""<div style="background: linear-gradient(135deg, #dbeafe 0%, #eff6ff 100%);
598
+ padding: 14px; border-radius: 12px; border: 1px solid #93c5fd; margin-bottom: 12px;">
599
+ <strong style="color: #1e3a8a;">💬 Fale naturalmente com a IA!</strong>
600
+ <p style="color: #1e40af; font-size: 13px; margin: 6px 0 0; line-height: 1.6;">
601
+ Descreva o que você quer em texto simples. A IA vai interpretar e selecionar os trechos certos.
602
+ <br><strong>Prioridade:</strong> Se preencher este campo, ele tem preferência sobre palavras-chave e pesos.
603
+ </p>
604
+ </div>""")
605
+ natural_instructions = gr.Textbox(
606
+ label="Suas instruções para a IA",
607
+ placeholder="""Exemplos:
608
+ • "Separe os 5 melhores momentos desse vídeo"
609
+ • "Recorte apenas a parte que ele fala sobre medo e superação"
610
+ • "Remova todas as vezes que ele menciona a palavra 'almôndega'"
611
+ • "Quero só os momentos engraçados e emocionantes"
612
+ • "Extraia as partes onde ele ensina alguma técnica"
613
+ • "Pegue os trechos mais virais para o TikTok"""",
614
+ lines=4,
615
+ info="Requer LLM ativo e transcrição. Tem prioridade sobre modo automático."
616
+ )
617
+ gr.HTML("""
618
+ <div style="padding: 12px; background: #f0f9ff; border-radius: 10px; margin-top: 12px; border: 1px solid #bae6fd;">
619
+ <div style="font-weight: 600; margin-bottom: 8px; color: #0c4a6e;">✨ Como funciona:</div>
620
+ <ul style="margin: 0; padding-left: 20px; color: #0c4a6e; font-size: 13px; line-height: 1.7;">
621
+ <li>A IA lê toda a transcrição com timecodes</li>
622
+ <li>Interpreta suas instruções em português natural</li>
623
+ <li>Seleciona automaticamente os trechos que atendem ao seu pedido</li>
624
+ <li>Funciona para incluir, excluir ou filtrar conteúdo específico</li>
625
+ </ul>
626
+ </div>
627
+ """)
628
+
629
+ with gr.Accordion("⏱️ MINUTAGENS MANUAIS (Controle Absoluto)", open=False):
630
  gr.HTML("""<div style="background: #fffbeb; padding: 12px; border-radius: 10px; border: 1px solid #fde68a; margin-bottom: 12px;">
631
+ <strong style="color: #92400e;">🎬 Modo de Controle Total</strong>
632
  <p style="color: #78350f; font-size: 13px; margin: 6px 0 0;">
633
+ Se preencher este campo, o app ignora TUDO (transcrição, IA, instruções, etc)
634
+ e corta EXATAMENTE o que você especificou.
635
  </p>
636
  </div>""")
637
  manual_timecodes = gr.Textbox(
 
641
  )
642
  gr.HTML("""
643
  <div style="padding: 12px; background: #f9fafb; border-radius: 10px; margin-top: 12px;">
644
+ <div style="font-weight: 600; margin-bottom: 8px; color: #1a1a1a;">Formatos aceitos:</div>
645
+ <ul style="margin: 0; padding-left: 20px; color: #374151; font-size: 13px;">
646
  <li><code>hh:mm:ss:ff - hh:mm:ss:ff</code> (um por linha)</li>
647
  <li>Pode separar por vírgula também</li>
648
  <li>Exemplo: <code>00:01:30:00 - 00:02:00:15, 00:05:10:00 - 00:06:20:10</code></li>
 
650
  </div>
651
  """)
652
 
653
+ with gr.Accordion("🔑 Palavras-chave Personalizadas (Modo Automático)", open=False):
654
  custom_keywords = gr.Textbox(
655
  label="Adicione palavras-chave importantes (separadas por vírgula)",
656
  placeholder="Exemplo: transformação, resultado, método, estratégia",
657
  info="Trechos com essas palavras terão prioridade máxima (peso 3.0) - Só funciona no modo automático"
658
  )
659
 
660
+ with gr.Accordion("⚖️ Ajuste Fino dos Pesos de Pontuação (Modo Automático)", open=False):
661
+ gr.HTML("""<p style="color: #4b5563; margin-bottom: 16px;">
662
  Ajuste a importância de cada categoria na pontuação heurística (modo automático)</p>""")
663
  with gr.Row():
664
+ weight_emotion = gr.Slider(0, 5, value=2.0, step=0.1, label="😢 Emoção")
665
+ weight_break = gr.Slider(0, 5, value=1.5, step=0.1, label="Quebra de expectativa")
666
  with gr.Row():
667
+ weight_learn = gr.Slider(0, 5, value=1.2, step=0.1, label="🎓 Aprendizado")
668
+ weight_viral = gr.Slider(0, 5, value=1.0, step=0.1, label="🚀 Viralização")
669
 
670
  gr.HTML("""<div style="margin: 24px 0; text-align: center;">
671
  <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 16px 0;">
672
  </div>""")
673
 
674
+ run_btn = gr.Button("🎬 Processar e Gerar XML Editado", variant="primary", size="lg")
675
 
676
  gr.HTML("""<div style="margin: 20px 0;">
677
  <hr style="border: none; border-top: 1px solid #e5e7eb;">
678
  </div>""")
679
 
680
  gr.HTML("""<div style="text-align: center; margin-bottom: 16px;">
681
+ <div style="font-weight: 700; color: #1a1a1a; margin-bottom: 4px;">📊 Resultados</div>
682
+ <p style="color: #4b5563; font-size: 14px; margin: 0;">Resumo dos cortes e arquivo para download</p>
683
  </div>""")
684
 
685
  with gr.Row():
 
692
  run_btn.click(
693
  process_xml_and_transcript,
694
  inputs=[xml_in, txt_in, use_llm, num_segments, custom_keywords,
695
+ manual_timecodes, natural_instructions, weight_emotion, weight_break, weight_learn, weight_viral],
696
  outputs=[resumo_out, file_out, status_out]
697
  )
698
 
699
+ with gr.Accordion("📖 Guia de Uso Completo", open=False):
700
  gr.HTML("""
701
  <div style="padding: 16px; background: #f9fafb; border-radius: 12px;">
702
+ <div style="margin-bottom: 24px;">
703
+ <div style="display: inline-block; background: #3b82f6; color: #fff; padding: 6px 12px;
704
+ border-radius: 999px; font-size: 12px; font-weight: 900; margin-bottom: 10px;">
705
+ 🎯 MODO 1: INSTRUÇÕES NATURAIS (NOVO!)
706
+ </div>
707
+ <h3 style="margin: 8px 0; font-weight: 700; color: #1a1a1a;">Fale com a IA</h3>
708
+ <ul style="color: #374151; line-height: 1.8; padding-left: 20px;">
709
+ <li><strong>Prioridade:</strong> Se preencher as instruções naturais, ignora palavras-chave e pesos</li>
710
+ <li>Escreva em português simples o que você quer</li>
711
+ <li>Exemplos: "separe os melhores momentos", "só a parte sobre medo", "remova quando fala X"</li>
712
+ <li>Requer LLM ativo e transcrição com timecodes</li>
713
+ <li>A IA interpreta e seleciona automaticamente</li>
714
+ </ul>
715
+ <div style="background: #e0f2fe; padding: 10px; border-radius: 8px; margin-top: 10px; border-left: 3px solid #0284c7;">
716
+ <strong style="color: #0c4a6e;">💡 Exemplos de comandos:</strong><br>
717
+ <code style="color: #0369a1; font-size: 12px;">
718
+ • "Pegue só as partes emocionantes"<br>
719
+ • "Remova tudo que fala sobre política"<br>
720
+ • "Extraia os 3 melhores ensinamentos"<br>
721
+ • "Corte apenas os momentos engraçados"
722
+ </code>
723
+ </div>
724
+ </div>
725
+
726
+ <div style="margin-bottom: 24px;">
727
+ <div style="display: inline-block; background: #fbbf24; color: #000; padding: 6px 12px;
728
+ border-radius: 999px; font-size: 12px; font-weight: 900; margin-bottom: 10px;">
729
+ ⏱️ MODO 2: MINUTAGENS MANUAIS
730
  </div>
731
+ <h3 style="margin: 8px 0; font-weight: 700; color: #1a1a1a;">Controle Absoluto</h3>
732
+ <ul style="color: #374151; line-height: 1.8; padding-left: 20px;">
733
+ <li><strong>Máxima prioridade:</strong> Sobrescreve TUDO (IA, transcrição, etc)</li>
734
+ <li>Cole os timecodes exatos no formato <code>hh:mm:ss:ff - hh:mm:ss:ff</code></li>
735
  <li>A transcrição se torna opcional</li>
736
+ <li>Perfeito quando você sabe exatamente o que quer</li>
 
737
  </ul>
738
  </div>
739
 
740
+ <div style="margin-bottom: 24px;">
741
+ <div style="display: inline-block; background: #39FF14; color: #000; padding: 6px 12px;
742
+ border-radius: 999px; font-size: 12px; font-weight: 900; margin-bottom: 10px;">
743
+ 🤖 MODO 3: AUTOMÁTICO COM IA
744
  </div>
745
+ <h3 style="margin: 8px 0; font-weight: 700; color: #1a1a1a;">Seleção Inteligente</h3>
746
+ <ul style="color: #374151; line-height: 1.8; padding-left: 20px;">
747
+ <li>Deixe instruções naturais e minutagens vazias</li>
748
  <li>Envie a transcrição com timecodes</li>
749
+ <li>Ative "Usar Potência Criativa" para melhor seleção narrativa</li>
750
+ <li>Configure palavras-chave personalizadas se quiser</li>
751
+ <li>Ajuste os pesos de pontuação (opcional)</li>
752
  </ul>
753
  </div>
754
 
755
+ <div style="background: #fef3c7; padding: 14px; border-radius: 10px; border: 1px solid #fcd34d; margin-top: 20px;">
756
+ <strong style="color: #78350f;">⚠️ Ordem de Prioridade:</strong>
757
+ <ol style="margin: 8px 0 0; padding-left: 20px; color: #92400e; line-height: 1.7;">
758
+ <li><strong>Minutagens Manuais</strong> - Ignora tudo</li>
759
+ <li><strong>Instruções Naturais com IA</strong> - Ignora palavras-chave e pesos</li>
760
+ <li><strong>Modo Automático</strong> - Usa pontuação + palavras-chave + IA (se ativada)</li>
761
+ </ol>
 
762
  </div>
763
  </div>
764
  """)
 
767
  <footer style="margin-top: 40px; padding: 24px 0; border-top: 1px solid #e5e7eb; text-align: center;">
768
  <div style="display: inline-flex; align-items: center; gap: 8px; margin-bottom: 8px;">
769
  <div style="width: 10px; height: 10px; border-radius: 50%; background: #39FF14;"></div>
770
+ <span style="font-weight: 700; color: #1a1a1a;">Leicam · Tech</span>
771
  </div>
772
  <p style="color: #6b7280; font-size: 13px; margin: 0;">
773
  Ferramentas práticas para produção de conteúdo