criado repositório jhonny-editor no Gitea adicionado script admin/deploy.command com commit automático atualizado admin/DEV-NOTES.md com template limpo Resumo: - 48 arquivos alterados - 26 novos - 19 modificados - 3 removidos 22 files changed, 658 insertions(+), 568 deletions(-) Arquivos: - AGENTS.md - admin/DEV-NOTES.md - admin/OpenCut.command - admin/commit.command - admin/deploy.command - admin/update.command - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/ARQUITETURA.md - code/engine/arquitetura/README.md - code/engine/gerar_relatorio_timeline.py - code/engine/integracoes/apple_speech/apple_speech_transcriber.swift - code/engine/integracoes/apple_speech/provider_de_transcricao_apple.py - code/engine/integracoes/midia/__init__.py - code/engine/integracoes/whisper/provider_de_transcricao_local.py - code/engine/scanner/__init__.py - code/engine/scanner/coordenacao/__init__.py - code/engine/scanner/descoberta/__init__.py - code/engine/scanner/modelos.py - code/engine/scanner/transcricao_da_timeline.py - code/src/tools/discovery.ts - :memory:.ses - admin/Jhonny.command - admin/inativos/OpenCut.command - admin/inativos/update.command - code/docs/glossario-analise-emocional.md - code/docs/levantamento-apple-vision-e-apple-intelligence.md - code/docs/levantamento-ferramentas-analise-visual-local.md - code/engine/arquitetura/biblioteca-inteligente-de-videos.md - code/engine/executar_scanner.py - code/engine/integracoes/huggingface/ - code/engine/integracoes/midia/extracao_de_metadados.py - code/engine/integracoes/visual/ - code/engine/requirements-visual.txt - code/engine/scanner/configuracao_visual.py - code/engine/scanner/descoberta/descoberta_de_arquivos.py - code/engine/scanner/metadados.py - code/engine/scanner/relatorio_visual.py - code/engine/scanner/retakes/ - code/engine/scanner/visual.py - code/engine/testes/test_analise_visual_local.py - code/engine/testes/test_arquivos_e_metadados.py - code/engine/testes/test_provider_de_transcricao_apple.py - code/relatorios/analise-brools/ - code/relatorios/analise-visual/ - code/relatorios/audio/arquivos/ - code/relatorios/transcricao-timeline.md
127 lines
6.6 KiB
Python
127 lines
6.6 KiB
Python
"""Adapter Python para o runner Swift que concentra Vision e Apple Intelligence."""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
from typing import Any, Callable
|
|
|
|
from ...scanner.modelos import EvidenciaVisual
|
|
from .contratos import AnalisadorDeFrame, AnalisadorDeSequenciaDeQuadros
|
|
from .modelos import QuadroDeVideo
|
|
|
|
|
|
RECURSOS_PADRAO = (
|
|
"faces", "qualidade_facial", "pessoas", "pose", "maos", "ocr", "categorias",
|
|
"estetica", "codigos", "horizonte", "retangulos", "contornos", "segmentacao_de_pessoas",
|
|
)
|
|
|
|
|
|
class ExecutorDoRunnerApple:
|
|
"""Compila o runner Swift quando necessário e o executa em processo isolado."""
|
|
|
|
def __init__(self, fonte: str | Path | None = None, compilador: str = "swiftc",
|
|
diretorio_de_build: str | Path | None = None) -> None:
|
|
self.fonte = Path(fonte) if fonte else Path(__file__).with_name("apple_vision_runner.swift")
|
|
self.compilador = compilador
|
|
self.diretorio_de_build = Path(diretorio_de_build) if diretorio_de_build else (
|
|
Path(tempfile.gettempdir()) / "jhonny-apple-vision")
|
|
|
|
def executar(self, carga: dict[str, Any], timeout: float) -> dict[str, Any]:
|
|
executavel = self._garantir_compilado()
|
|
processo = subprocess.run([str(executavel)], input=json.dumps(carga), capture_output=True,
|
|
text=True, timeout=timeout)
|
|
if processo.returncode != 0:
|
|
detalhe = processo.stderr.strip() or "runner Apple terminou sem diagnóstico"
|
|
raise RuntimeError(f"Runner Apple Vision falhou: {detalhe}")
|
|
try:
|
|
return json.loads(processo.stdout)
|
|
except json.JSONDecodeError as exc:
|
|
raise RuntimeError(f"Runner Apple Vision devolveu JSON inválido: {processo.stdout!r}") from exc
|
|
|
|
def _garantir_compilado(self) -> Path:
|
|
if not self.fonte.is_file():
|
|
raise FileNotFoundError(f"Fonte do runner Apple não encontrada: {self.fonte}")
|
|
self.diretorio_de_build.mkdir(parents=True, exist_ok=True)
|
|
executavel = self.diretorio_de_build / "apple-vision-runner"
|
|
if not executavel.exists() or executavel.stat().st_mtime < self.fonte.stat().st_mtime:
|
|
ambiente = os.environ | {"CLANG_MODULE_CACHE_PATH": str(self.diretorio_de_build / "module-cache")}
|
|
processo = subprocess.run([self.compilador, "-parse-as-library", str(self.fonte), "-o", str(executavel)],
|
|
capture_output=True, text=True, timeout=120, env=ambiente)
|
|
if processo.returncode != 0:
|
|
raise RuntimeError(f"Não foi possível compilar runner Apple Vision: {processo.stderr.strip()}")
|
|
return executavel
|
|
|
|
|
|
class AnalisadorAppleVisionNativo(AnalisadorDeFrame):
|
|
"""Módulo profundo: pede fatos visuais nativos e opcionalmente interpretação local.
|
|
|
|
A interface recebe somente um `QuadroDeVideo`; Vision, Foundation Models,
|
|
Swift, compilação e erros nativos permanecem atrás deste adapter.
|
|
"""
|
|
|
|
nome = "apple_vision"
|
|
|
|
def __init__(self, recursos: tuple[str, ...] = RECURSOS_PADRAO,
|
|
interpretar_com_apple_intelligence: bool = True,
|
|
executor: ExecutorDoRunnerApple | None = None,
|
|
timeout_em_segundos: float = 90.0) -> None:
|
|
self.recursos = recursos
|
|
self.interpretar_com_apple_intelligence = interpretar_com_apple_intelligence
|
|
self.executor = executor or ExecutorDoRunnerApple()
|
|
self.timeout_em_segundos = timeout_em_segundos
|
|
|
|
def analisar(self, quadro: QuadroDeVideo) -> list[EvidenciaVisual]:
|
|
if quadro.caminho is None:
|
|
raise ValueError("Apple Vision requer que o frame tenha caminho persistido em disco.")
|
|
dados = self.executor.executar({"frame": str(quadro.caminho), "recursos": list(self.recursos),
|
|
"resumir": self.interpretar_com_apple_intelligence},
|
|
self.timeout_em_segundos)
|
|
evidencias = [self._converter(item, quadro) for item in dados.get("evidencias", [])]
|
|
avisos = dados.get("avisos", [])
|
|
if avisos:
|
|
evidencias.append(EvidenciaVisual("diagnostico_apple_vision", quadro.timestamp, quadro.timestamp,
|
|
{"avisos": avisos, "recursos_solicitados": list(self.recursos)}, provider=self.nome))
|
|
return evidencias
|
|
|
|
def _converter(self, item: dict[str, Any], quadro: QuadroDeVideo) -> EvidenciaVisual:
|
|
return EvidenciaVisual(item["tipo"], quadro.timestamp, quadro.timestamp, item["valor"],
|
|
confianca=item.get("confianca"), provider=self.nome,
|
|
modelo=item.get("modelo"))
|
|
|
|
|
|
class AnalisadorSequenciaAppleVisionNativo(AnalisadorDeSequenciaDeQuadros):
|
|
"""Compara frames com feature prints nativos sem expor o protocolo Swift."""
|
|
|
|
nome = "apple_vision_sequencia"
|
|
|
|
def __init__(self, executor: ExecutorDoRunnerApple | None = None,
|
|
timeout_em_segundos: float = 90.0) -> None:
|
|
self.executor = executor or ExecutorDoRunnerApple()
|
|
self.timeout_em_segundos = timeout_em_segundos
|
|
|
|
def analisar(self, quadros: list[QuadroDeVideo]) -> list[EvidenciaVisual]:
|
|
if len(quadros) < 2:
|
|
return []
|
|
if any(quadro.caminho is None for quadro in quadros):
|
|
raise ValueError("Apple Vision de sequência requer frames persistidos em disco.")
|
|
dados = self.executor.executar({"frames": [str(quadro.caminho) for quadro in quadros],
|
|
"recursos": [], "resumir": False}, self.timeout_em_segundos)
|
|
evidencias: list[EvidenciaVisual] = []
|
|
for item in dados.get("evidencias", []):
|
|
valor = dict(item["valor"])
|
|
inicio = int(valor.pop("frame_inicial", 0))
|
|
fim = int(valor.pop("frame_final", inicio + 1))
|
|
if inicio < 0 or fim >= len(quadros) or fim < inicio:
|
|
raise RuntimeError("Runner Apple Vision devolveu índices temporais inválidos.")
|
|
evidencias.append(EvidenciaVisual(item["tipo"], quadros[inicio].timestamp,
|
|
quadros[fim].timestamp, valor,
|
|
confianca=item.get("confianca"), provider=self.nome,
|
|
modelo=item.get("modelo")))
|
|
avisos = dados.get("avisos", [])
|
|
if avisos:
|
|
evidencias.append(EvidenciaVisual("diagnostico_apple_vision", quadros[0].timestamp,
|
|
quadros[-1].timestamp, {"avisos": avisos}, provider=self.nome))
|
|
return evidencias
|