feat: destacado no deploy o resumo que será gravado na mensagem do
- destacado no deploy o resumo que será gravado na mensagem do commit Resumo: - 8 arquivos alterados - 1 novos - 7 modificados - 0 removidos 7 files changed, 143 insertions(+), 11 deletions(-) Arquivos: - .jhonny/analises.db - admin/deploy.command - code/engine/executar_scanner.py - code/engine/integracoes/midia/__init__.py - code/engine/integracoes/visual/apple_vision.py - code/engine/scanner/configuracao_visual.py - code/engine/scanner/visual.py - code/engine/integracoes/midia/compactador_de_proxy.py
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from ..integracoes.midia import RESOLUCAO_PADRAO_DO_PROXY
|
||||
|
||||
|
||||
RECURSOS_VISION = frozenset((
|
||||
"faces", "qualidade_facial", "pessoas", "pose", "maos", "ocr", "categorias",
|
||||
@@ -21,6 +23,13 @@ SENSIBILIDADES_VISUAIS: dict[str, float] = {
|
||||
}
|
||||
|
||||
|
||||
def _inteiro_ou_nulo(valor: Any) -> int | None:
|
||||
"""Converte um valor de perfil em int, preservando None."""
|
||||
if valor is None or valor == "":
|
||||
return None
|
||||
return int(valor)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConfiguracaoVisualDoScanner:
|
||||
"""Interface curta para uma execução de leitura visual da timeline."""
|
||||
@@ -38,6 +47,13 @@ class ConfiguracaoVisualDoScanner:
|
||||
usar_analise_visual: bool = False
|
||||
detectar_cortes: bool = True
|
||||
sensibilidade: str = "media"
|
||||
# Leitura via proxy compactado. Para arquivos de origem pesados (MOV de
|
||||
# vários GB), ler os frames de uma cópia leve é muito mais rápido.
|
||||
usar_proxy: bool = True
|
||||
resolucao_do_proxy: int = RESOLUCAO_PADRAO_DO_PROXY
|
||||
# Limita quantos frames por clipe recebem a interpretação editorial da Apple
|
||||
# Intelligence (Foundation Models) — a etapa mais cara. None interpreta todos.
|
||||
maximo_de_frames_interpretados: int | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not 0.04 <= self.intervalo_em_segundos <= 60:
|
||||
@@ -75,6 +91,9 @@ class ConfiguracaoVisualDoScanner:
|
||||
usar_analise_visual=bool(dados.get("usar_analise_visual", False)),
|
||||
detectar_cortes=bool(dados.get("detectar_cortes", True)),
|
||||
sensibilidade=str(dados.get("sensibilidade", "media")),
|
||||
usar_proxy=bool(dados.get("usar_proxy", True)),
|
||||
resolucao_do_proxy=int(dados.get("resolucao_do_proxy", RESOLUCAO_PADRAO_DO_PROXY)),
|
||||
maximo_de_frames_interpretados=_inteiro_ou_nulo(dados.get("maximo_de_frames_interpretados", None)),
|
||||
)
|
||||
|
||||
def para_dict(self) -> dict[str, Any]:
|
||||
@@ -92,4 +111,7 @@ class ConfiguracaoVisualDoScanner:
|
||||
"usar_analise_visual": self.usar_analise_visual,
|
||||
"detectar_cortes": self.detectar_cortes,
|
||||
"sensibilidade": self.sensibilidade,
|
||||
"usar_proxy": self.usar_proxy,
|
||||
"resolucao_do_proxy": self.resolucao_do_proxy,
|
||||
"maximo_de_frames_interpretados": self.maximo_de_frames_interpretados,
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class DetectorDeCenas:
|
||||
|
||||
def detectar(self, clipe: Clipe) -> ResultadoVisualDoClipe:
|
||||
quadros = self.extrator.extrair(clipe)
|
||||
self._preparar_analisadores(quadros)
|
||||
total = len(quadros) * len(self.analisadores_de_frame) + len(self.analisadores_de_sequencia)
|
||||
progresso = [0]
|
||||
evidencias = self._analisar_quadros(quadros, progresso, total) + self._analisar_sequencia(quadros, progresso, total)
|
||||
@@ -51,6 +52,19 @@ class DetectorDeCenas:
|
||||
) for cena in cenas]
|
||||
return ResultadoVisualDoClipe(clipe.identificador, evidencias, cenas, cenas_visuais)
|
||||
|
||||
def _preparar_analisadores(self, quadros: list[QuadroDeVideo]) -> None:
|
||||
"""Prepara analisadores que precisam saber a quantidade de frames do clipe.
|
||||
|
||||
Caso especial do `AnalisadorAppleVisionNativo`: ele distribui a
|
||||
interpretação editorial (Apple Intelligence / Foundation Models) em um
|
||||
subconjunto dos frames para manter a cobertura de cena com custo menor.
|
||||
"""
|
||||
quantidade_de_quadros = len(quadros)
|
||||
for analisador in self.analisadores_de_frame:
|
||||
preparar = getattr(analisador, "preparar_interpretacao_por_clipe", None)
|
||||
if callable(preparar):
|
||||
preparar(quantidade_de_quadros)
|
||||
|
||||
def _analisar_quadros(self, quadros: list[QuadroDeVideo], progresso: list[int] | None = None,
|
||||
total: int = 0) -> list[EvidenciaVisual]:
|
||||
if not self.analisadores_de_frame or not quadros:
|
||||
@@ -205,18 +219,35 @@ def criar_detector_apple_vision(
|
||||
from ..integracoes.visual import (AnalisadorAppleVisionNativo,
|
||||
AnalisadorSequenciaAppleVisionNativo,
|
||||
ExtratorDeQuadrosFFmpeg)
|
||||
from ..integracoes.midia import CompactadorDeProxy, ExtratorDeQuadrosDoProxy
|
||||
|
||||
perfil = configuracao or ConfiguracaoVisualDoScanner(intervalo_em_segundos=intervalo_em_segundos)
|
||||
|
||||
# Quantidade fixa de trabalhadores para extração e análise de frames.
|
||||
# Em volumes externos (ex.: /Volumes/Merongo), mais threads do que isso
|
||||
# satura a E/S e trava os processos de mídia a ~0% de CPU.
|
||||
QUANTIDADE_DE_TRABALHADORES = 4
|
||||
|
||||
extrator = ExtratorDeQuadrosFFmpeg(intervalo_em_segundos=perfil.intervalo_em_segundos,
|
||||
diretorio_de_cache=diretorio_de_cache or ".frames",
|
||||
resolucao_maxima=perfil.resolucao_maxima,
|
||||
trabalhadores_em_paralelo=QUANTIDADE_DE_TRABALHADORES)
|
||||
# Leitura via proxy compactado: para arquivos pesados, evita buscar frame
|
||||
# por frame no arquivo original (um -ss por frame num MOV de vários GB).
|
||||
if perfil.usar_proxy:
|
||||
extrator = ExtratorDeQuadrosDoProxy(
|
||||
extrator, CompactadorDeProxy(), largura_maxima=perfil.resolucao_do_proxy
|
||||
)
|
||||
|
||||
return DetectorDeCenas(
|
||||
ExtratorDeQuadrosFFmpeg(intervalo_em_segundos=perfil.intervalo_em_segundos,
|
||||
diretorio_de_cache=diretorio_de_cache or ".frames",
|
||||
resolucao_maxima=perfil.resolucao_maxima),
|
||||
extrator,
|
||||
analisadores_de_frame=[AnalisadorAppleVisionNativo(
|
||||
recursos=tuple(sorted(perfil.recursos_vision)),
|
||||
interpretar_com_apple_intelligence=perfil.interpretar_cena,
|
||||
maximo_de_frames_interpretados=perfil.maximo_de_frames_interpretados,
|
||||
)],
|
||||
analisadores_de_sequencia=[AnalisadorSequenciaAppleVisionNativo()]
|
||||
if perfil.analisar_continuidade else [],
|
||||
detectores_de_intervalo=_detectores_de_cena_do_perfil(perfil),
|
||||
trabalhadores_em_paralelo=QUANTIDADE_DE_TRABALHADORES,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user