fix: troca except Exception genérico por exceções específicas nos scripts do Scanner

executar_scanner.py: nos dois pontos que processavam clipe-a-clipe (análise
visual e transcrição), o except genérico escondia bugs de programação junto
com falhas esperadas de mídia/dependência — foi assim que o bug do soundfile
ausente passou despercebido até um teste manual. Agora captura só
(OSError, RuntimeError, ValueError, json.JSONDecodeError) na visual e
(ImportError, OSError, RuntimeError, ValueError) na transcrição; erros de
programação propagam e aparecem de verdade.

executar_retakes.py e gerar_relatorio_de_analises.py: o except Exception no
main() é uma fronteira de processo legítima (traduz qualquer falha em JSON
pra tela), mantido, mas agora imprime o traceback completo no stderr antes
de reportar — hoje esse traceback se perdia, sobrando só str(erro).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
João Henrique
2026-09-09 23:38:52 -04:00
co-authored by Claude Sonnet 5
parent 88f9af28cf
commit 1156619937
3 changed files with 64 additions and 9 deletions
+34 -4
View File
@@ -54,10 +54,31 @@ CAMINHO_DO_BANCO = CAMINHO_DO_CODIGO.parent / ".jhonny" / "analises.db"
def nome_seguro(nome: str) -> str:
"""Converte o nome de uma sequência em nome de pasta seguro no filesystem.
Parâmetros:
nome: Nome original da sequência, como vem do Premiere.
Retorna:
O nome sem caracteres problemáticos, ou "timeline" quando nada sobra.
"""
return re.sub(r"[^A-Za-z0-9._-]+", "-", nome.strip()).strip(".-") or "timeline"
def executar(entrada: Path, saida: Path) -> Path:
"""Executa uma análise do Scanner conforme o pedido do painel.
Persiste a timeline, as evidências visuais, as cenas e a transcrição no
banco de análises, e grava os relatórios JSON que o painel exibe.
Parâmetros:
entrada: Arquivo JSON com o perfil e a timeline a analisar.
saida: Caminho de referência para a pasta onde o relatório será
escrito; o nome final é derivado do nome da sequência.
Retorna:
O caminho do relatório JSON gerado.
"""
pedido = json.loads(entrada.read_text(encoding="utf-8"))
configuracao = ConfiguracaoVisualDoScanner.de_dict(pedido["perfil"])
timeline = ConversorDeTimeline().converter(pedido["timeline"])
@@ -100,11 +121,17 @@ def executar(entrada: Path, saida: Path) -> Path:
gerar_relatorio_visual(clipe, resultado, arquivo, configuracao.intervalo_em_segundos)
resultados.append(json.loads(arquivo.read_text(encoding="utf-8")))
# Grava no banco os fatos visuais e as cenas detectadas deste clipe.
repositorio_de_analises.registrar_evidencias_visuais(
# Substitui em vez de acrescentar: reanalisar o mesmo clipe tem de
# deixar uma cópia só de cada evidência, não empilhar execuções.
repositorio_de_analises.substituir_evidencias_visuais(
timeline.identificador, clipe.identificador, resultado.evidencias)
repositorio_de_analises.registrar_cenas(
repositorio_de_analises.substituir_cenas(
timeline.identificador, resultado.cenas, clipe.identificador)
except Exception as erro:
except (OSError, RuntimeError, ValueError, json.JSONDecodeError) as erro:
# Falha esperada de mídia/detector (arquivo ilegível, subprocesso do
# Apple Vision indisponível, resposta malformada): não deve descartar
# os demais clipes. Erros de programação (TypeError, AttributeError
# etc.) propagam normalmente para não serem mascarados como isso.
resultados.append({"clipe": {"identificador": clipe.identificador, "nome": clipe.nome,
"arquivo_original": clipe.arquivo}, "erro": str(erro)})
concluidos += peso
@@ -176,7 +203,10 @@ def executar(entrada: Path, saida: Path) -> Path:
"confianca": item.confianca, "falante": item.falante,
"caracteristicas_acusticas": item.caracteristicas_acusticas}
for item in transcricao.segmentos]
except Exception as erro:
except (ImportError, OSError, RuntimeError, ValueError) as erro:
# Dependência ausente (ex.: soundfile), modelo/áudio ilegível, ou
# falha de runtime do Whisper/pyannote: registra e segue sem
# transcrição. Erros de programação propagam normalmente.
transcricao_configurada = {**transcricao_configurada, "erro": str(erro)}
for faixa in audio:
arquivo = f"{caso}-audio-faixa-{faixa['indice'] + 1}.json"