leicam commited on
Commit
03c0164
·
verified ·
1 Parent(s): f9e0f33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -28
app.py CHANGED
@@ -108,6 +108,26 @@ def keyword_score(text: str, custom_keywords: str = "", weight_emotion: float =
108
  score += 0.0005 * len(text)
109
  return score
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  def llm_rank_segments(candidates: List[Segment], num_segments: int, custom_instructions: str = "") -> List[Segment]:
112
  """Ask the LLM to pick segments based on criteria."""
113
  if not LLM_AVAILABLE:
@@ -140,9 +160,39 @@ def llm_rank_segments(candidates: List[Segment], num_segments: int, custom_instr
140
  return candidates[:num_segments]
141
 
142
  def select_segments(transcript_txt: str, use_llm: bool, num_segments: int,
143
- custom_keywords: str, custom_instructions: str,
144
  weight_emotion: float, weight_break: float,
145
  weight_learn: float, weight_viral: float) -> List[Segment]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  segs = parse_transcript(transcript_txt)
147
  if not segs:
148
  raise ValueError("Nenhum trecho válido encontrado na transcrição.")
@@ -154,7 +204,7 @@ def select_segments(transcript_txt: str, use_llm: bool, num_segments: int,
154
  top = segs[:min(20, len(segs))]
155
 
156
  if use_llm and LLM_AVAILABLE:
157
- ranked = llm_rank_segments(top, num_segments, custom_instructions)
158
  return ranked
159
 
160
  return top[:num_segments]
@@ -266,16 +316,26 @@ def edit_sequence_with_segments(tree: ET.ElementTree, segs: List[Segment]) -> ET
266
 
267
  # ---- Gradio app ----
268
  def process_xml_and_transcript(premiere_xml_file, transcript_txt_file, use_llm,
269
- num_segments, custom_keywords, custom_instructions,
270
  weight_emotion, weight_break, weight_learn, weight_viral):
271
- if premiere_xml_file is None or transcript_txt_file is None:
272
- return "Envie o XML do Premiere e a transcrição em .txt.", None, f"LLM disponível: {LLM_AVAILABLE}"
 
 
 
273
 
274
- with open(transcript_txt_file.name, "r", encoding="utf-8") as f:
275
- transcript = f.read()
 
 
 
 
 
 
 
276
 
277
  segs = select_segments(transcript, use_llm and LLM_AVAILABLE, num_segments,
278
- custom_keywords, custom_instructions,
279
  weight_emotion, weight_break, weight_learn, weight_viral)
280
 
281
  tree = ET.parse(premiere_xml_file.name)
@@ -285,13 +345,16 @@ def process_xml_and_transcript(premiere_xml_file, transcript_txt_file, use_llm,
285
  out_path = os.path.join(OUTPUT_DIR, f"{base}_EDITADO.xml")
286
  tree.write(out_path, encoding="utf-8", xml_declaration=True)
287
 
288
- resumo = f"✂️ {len(segs)} cortes aplicados (24 fps):\n\n"
 
289
  for i, s in enumerate(segs, 1):
290
  dur_sec = (s.end_f - s.start_f) / FPS
291
  resumo += f"{i}. {s.start_tc} → {s.end_tc} ({dur_sec:.1f}s)\n"
292
- resumo += f" Score: {s.score:.1f} | {s.text[:150]}\n\n"
 
 
293
 
294
- status = f"✓ LLM disponível: {LLM_AVAILABLE} | LLM usado: {use_llm and LLM_AVAILABLE}"
295
  return resumo, out_path, status
296
 
297
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
@@ -301,7 +364,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
301
  with gr.Row():
302
  with gr.Column():
303
  xml_in = gr.File(label="📁 XML da sequência (FCP XML)", file_types=[".xml"])
304
- txt_in = gr.File(label="📄 Transcrição (.txt) com timecodes", file_types=[".txt"])
305
 
306
  with gr.Column():
307
  gr.Markdown("### ⚙️ Configurações")
@@ -312,23 +375,29 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
312
  )
313
  num_segments = gr.Slider(
314
  minimum=2, maximum=10, step=1, value=5,
315
- label="📊 Número de segmentos a selecionar",
316
  info="Quantos trechos incluir no vídeo final"
317
  )
318
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  with gr.Accordion("🎯 Palavras-chave Personalizadas", open=False):
320
  custom_keywords = gr.Textbox(
321
  label="Adicione palavras-chave importantes (separadas por vírgula)",
322
  placeholder="Exemplo: transformação, resultado, método, estratégia",
323
- info="Trechos com essas palavras terão prioridade máxima (peso 3.0)"
324
- )
325
-
326
- with gr.Accordion("📝 Instruções em Texto Livre para o LLM", open=False):
327
- custom_instructions = gr.Textbox(
328
- label="Instruções adicionais para o LLM",
329
- placeholder="Exemplo: Prefira trechos que mostrem resultados concretos e evite introduções longas",
330
- lines=3,
331
- info="Só funciona se o LLM estiver ativado"
332
  )
333
 
334
  with gr.Accordion("⚖️ Ajuste Fino dos Pesos de Pontuação", open=False):
@@ -354,16 +423,31 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
354
  run_btn.click(
355
  process_xml_and_transcript,
356
  inputs=[xml_in, txt_in, use_llm, num_segments, custom_keywords,
357
- custom_instructions, weight_emotion, weight_break, weight_learn, weight_viral],
358
  outputs=[resumo_out, file_out, status_out]
359
  )
360
 
361
  gr.Markdown("""
362
- ### 💡 Dicas de uso:
363
- - **Modo Heurístico**: Desative o LLM e ajuste os pesos para controle total baseado em palavras-chave
364
- - **Modo LLM**: Ative o LLM e use as instruções em texto livre para guiar a seleção semanticamente
365
- - **Híbrido**: Combine palavras-chave personalizadas + instruções LLM para máximo controle
366
- - **Palavras-chave**: Adicione termos específicos do seu nicho que devem ter alta prioridade
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  """)
368
 
369
  if __name__ == "__main__":
 
108
  score += 0.0005 * len(text)
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
+
121
+ pat = re.compile(r"(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*[-–—]\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})")
122
+
123
+ for line in lines:
124
+ m = pat.search(line)
125
+ if m:
126
+ start_tc, end_tc = m.groups()
127
+ manual_ranges.append((start_tc, end_tc))
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
  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:
175
+ start_f = parse_timecode_to_frames(start_tc)
176
+ end_f = parse_timecode_to_frames(end_tc)
177
+ if end_f > start_f:
178
+ result_segs.append(Segment(
179
+ start_tc=start_tc,
180
+ end_tc=end_tc,
181
+ start_f=start_f,
182
+ end_f=end_f,
183
+ text=f"Corte manual {start_tc} - {end_tc}",
184
+ score=100.0
185
+ ))
186
+ except Exception as e:
187
+ print(f"Erro ao processar timecode manual {start_tc}-{end_tc}: {e}")
188
+ continue
189
+
190
+ if not result_segs:
191
+ raise ValueError("Nenhum timecode manual válido encontrado.")
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.")
 
204
  top = segs[:min(20, len(segs))]
205
 
206
  if use_llm and LLM_AVAILABLE:
207
+ ranked = llm_rank_segments(top, num_segments, "")
208
  return ranked
209
 
210
  return top[:num_segments]
 
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
  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
352
  resumo += f"{i}. {s.start_tc} → {s.end_tc} ({dur_sec:.1f}s)\n"
353
+ if not manual_ranges:
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
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
364
  with gr.Row():
365
  with gr.Column():
366
  xml_in = gr.File(label="📁 XML da sequência (FCP XML)", file_types=[".xml"])
367
+ txt_in = gr.File(label="📄 Transcrição (.txt) - Opcional se usar minutagens manuais", file_types=[".txt"])
368
 
369
  with gr.Column():
370
  gr.Markdown("### ⚙️ Configurações")
 
375
  )
376
  num_segments = gr.Slider(
377
  minimum=2, maximum=10, step=1, value=5,
378
+ label="📊 Número de segmentos (só no modo automático)",
379
  info="Quantos trechos incluir no vídeo final"
380
  )
381
 
382
+ with gr.Accordion("✂️ MINUTAGENS MANUAIS (Sobrescreve tudo)", open=True):
383
+ manual_timecodes = gr.Textbox(
384
+ label="Cole aqui os timecodes exatos que você quer cortar",
385
+ placeholder="Exemplo:\n00:01:23:15 - 00:02:45:10\n00:05:30:00 - 00:07:15:22\n00:10:00:05 - 00:12:30:18",
386
+ lines=5,
387
+ info="⚠️ Se preencher este campo, o app ignora a transcrição e todos os outros parâmetros, cortando EXATAMENTE o que você especificou"
388
+ )
389
+ gr.Markdown("""
390
+ **Formatos aceitos:**
391
+ - `hh:mm:ss:ff - hh:mm:ss:ff` (um por linha)
392
+ - Pode separar por vírgula também
393
+ - Exemplo: `00:01:30:00 - 00:02:00:15, 00:05:10:00 - 00:06:20:10`
394
+ """)
395
+
396
  with gr.Accordion("🎯 Palavras-chave Personalizadas", open=False):
397
  custom_keywords = gr.Textbox(
398
  label="Adicione palavras-chave importantes (separadas por vírgula)",
399
  placeholder="Exemplo: transformação, resultado, método, estratégia",
400
+ info="Trechos com essas palavras terão prioridade máxima (peso 3.0) - Só funciona no modo automático"
 
 
 
 
 
 
 
 
401
  )
402
 
403
  with gr.Accordion("⚖️ Ajuste Fino dos Pesos de Pontuação", open=False):
 
423
  run_btn.click(
424
  process_xml_and_transcript,
425
  inputs=[xml_in, txt_in, use_llm, num_segments, custom_keywords,
426
+ manual_timecodes, weight_emotion, weight_break, weight_learn, weight_viral],
427
  outputs=[resumo_out, file_out, status_out]
428
  )
429
 
430
  gr.Markdown("""
431
+ ### 💡 Modos de uso:
432
+
433
+ **🎯 MODO MANUAL (Recomendado para controle total)**
434
+ - Preencha o campo "Minutagens Manuais" com seus timecodes exatos
435
+ - A transcrição se torna opcional
436
+ - Todos os outros parâmetros são ignorados
437
+ - O corte será feito EXATAMENTE como você especificou
438
+
439
+ **🤖 MODO AUTOMÁTICO**
440
+ - Deixe as minutagens manuais vazias
441
+ - Envie a transcrição com timecodes
442
+ - Configure LLM, palavras-chave e pesos conforme desejado
443
+ - O app escolhe os melhores trechos automaticamente
444
+
445
+ **Exemplos de minutagens manuais:**
446
+ ```
447
+ 00:01:23:15 - 00:02:45:10
448
+ 00:05:30:00 - 00:07:15:22
449
+ 00:10:00:05 - 00:12:30:18
450
+ ```
451
  """)
452
 
453
  if __name__ == "__main__":