TEDDyx86 commited on
Commit
ffd044a
·
1 Parent(s): bd81950

fix: Reverter motor para a versão estável pré-motor pessoal (v2.7)

Browse files
execution/ensemble_manager.py CHANGED
@@ -11,65 +11,47 @@ def get_combined_verdict(file_path):
11
 
12
  # 2. Executa Motor 2 (AST - Espectrograma e Frequência)
13
  res_ast = run_ast(file_path)
14
-
15
- # 1. Extração de scores brutos
16
- # Se houver erro em algum motor, o score padrão é 0.0, mas o veredito deve refletir o erro
17
- has_error = False
18
- error_msg = ""
19
-
20
- if "error" in res_w2v:
21
- has_error = True
22
- error_msg += f"Wav2Vec2 Error: {res_w2v['error']}. "
23
-
24
- if "error" in res_ast:
25
- # Erro no AST não é fatal, usamos apenas W2V se possível
26
- print(f"AST Warning: {res_ast['error']}")
27
-
28
- score_w2v = res_w2v.get("deepfake_probability", 0.0)
29
  score_ast = res_ast.get("risk_score", 0.0)
30
 
31
- # 2. Pesos do Ensemble (Protocolo de Rigor)
32
- # Aumentamos o peso do Wav2Vec2 para 0.8 pois o HyperMoon é mais específico para Deepfakes
33
- # O AST serve como um "sanity check" de anomalias acústicas
34
- WEIGHT_W2V = 0.8
35
- WEIGHT_AST = 0.2
 
36
 
37
- # 3. Cálculo do Score Final
38
  final_score = (score_w2v * WEIGHT_W2V) + (score_ast * WEIGHT_AST)
39
 
40
- # 4. Lógica de Decisão Rigorosa (Protocolo de Rigor V5)
41
- # Aumentamos a sensibilidade: qualquer sinal forte de fraude em um dos motores ou um consenso moderado dispara o alerta.
42
- SPOOF_THRESHOLD = 0.35 # Sensibilidade aumentada (antes 0.42)
43
 
 
44
  verdict = "AUTHENTIC"
45
 
46
- # Critérios de Decisão:
47
- # 1. Soberania Wav2Vec2: Se o modelo especializado detectar > 45% de chance de fraude.
48
- # 2. Consenso Ponderado: Se a média ponderada ultrapassar o threshold de rigor.
49
- # 3. Alerta de Anomalia AST: Se o AST detectar risco extremo (> 80%), mesmo que o W2V esteja em dúvida.
50
-
51
- if has_error and score_w2v == 0.0:
52
- verdict = "ERROR"
53
- elif score_w2v > 0.45:
54
- verdict = "SPOOF" # Detecção direta pelo motor principal
55
  elif final_score >= SPOOF_THRESHOLD:
56
- verdict = "SPOOF" # Detecção por consenso de ensemble
57
- elif score_ast > 0.85:
58
- verdict = "SPOOF" # Anomalia acústica crítica detectada
59
-
60
- # 5. Formatação da Resposta
 
61
  return {
62
  "verdict": verdict,
63
  "fraud_probability": final_score,
64
  "wav2vec_score": score_w2v,
65
  "ast_score": score_ast,
66
- "temporal_scores": res_w2v.get("temporal_scores", []),
67
- "engines_consensus": f"Rigor V5: {int(WEIGHT_W2V*100)}% W2V / {int(WEIGHT_AST*100)}% AST",
68
- "error": error_msg if has_error else None,
69
  "details": {
70
- "protocol": "Protocolo de Rigor V5 (Forense de Alta Sensibilidade)",
71
- "weights": {"wav2vec": WEIGHT_W2V, "ast": WEIGHT_AST},
72
- "threshold": SPOOF_THRESHOLD
73
  },
74
  "engines": ["HyperMoon-Wav2Vec2", "AST-Spectrogram"]
75
  }
 
11
 
12
  # 2. Executa Motor 2 (AST - Espectrograma e Frequência)
13
  res_ast = run_ast(file_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  score_ast = res_ast.get("risk_score", 0.0)
15
 
16
+ # 3. Lógica do Protocolo de Rigor V3 (Soberania HyperMoon)
17
+ # O motor Wav2Vec2 (HyperMoon) é o comandante principal (70% do peso)
18
+
19
+ # Pesos definidos para dar comando ao HyperMoon
20
+ WEIGHT_W2V = 0.7
21
+ WEIGHT_AST = 0.3
22
 
23
+ # Cálculo ponderado do score
24
  final_score = (score_w2v * WEIGHT_W2V) + (score_ast * WEIGHT_AST)
25
 
26
+ # Thresholds de decisão
27
+ SPOOF_THRESHOLD = 0.50
28
+ HIGH_CONFIDENCE = 0.85
29
 
30
+ is_fraud = False
31
  verdict = "AUTHENTIC"
32
 
33
+ # Lógica de decisão baseada na confiança do comandante (HyperMoon)
34
+ if score_w2v >= HIGH_CONFIDENCE:
35
+ is_fraud = True
36
+ verdict = "SPOOF"
37
+ message = "COMANDO HYPERMOON: Detecção crítica de padrões neurais sintéticos com alta convicção."
 
 
 
 
38
  elif final_score >= SPOOF_THRESHOLD:
39
+ is_fraud = True
40
+ verdict = "SPOOF"
41
+ message = f"ALERTA COMBINADO: Risco de {(final_score*100):.1f}% identificado, com base na análise espectral e fonética."
42
+ else:
43
+ message = "INTEGRIDADE CONFIRMADA: Padrões de voz condizentes com gravação humana autêntica."
44
+
45
  return {
46
  "verdict": verdict,
47
  "fraud_probability": final_score,
48
  "wav2vec_score": score_w2v,
49
  "ast_score": score_ast,
50
+ "temporal_scores": res_w2v.get("temporal_scores", []),
51
+ "engines_consensus": message,
 
52
  "details": {
53
+ "protocol": "Protocolo de Rigor V3 (Soberania HyperMoon)",
54
+ "weights": {"wav2vec": WEIGHT_W2V, "ast": WEIGHT_AST}
 
55
  },
56
  "engines": ["HyperMoon-Wav2Vec2", "AST-Spectrogram"]
57
  }
execution/fastapi_server.py CHANGED
@@ -12,10 +12,6 @@ import zipfile
12
  import rarfile
13
  import uuid
14
  import uvicorn
15
- import secrets
16
- from slowapi import Limiter, _rate_limit_exceeded_handler
17
- from slowapi.util import get_remote_address
18
- from slowapi.errors import RateLimitExceeded
19
 
20
  # Carrega variáveis do arquivo .env
21
  load_dotenv()
@@ -25,14 +21,7 @@ from execution.feature_extractor import extract_features
25
  from execution.ensemble_manager import get_combined_verdict
26
 
27
  # Configurações de Segurança e Limites
28
- ADMIN_TOKEN = os.environ.get("ADMIN_TOKEN")
29
- if not ADMIN_TOKEN:
30
- # Gera um token aleatório e seguro caso não esteja no .env (Hardening)
31
- ADMIN_TOKEN = secrets.token_urlsafe(32)
32
- print(f"\n[SECURITY WARNING] ADMIN_TOKEN não configurado no ambiente.")
33
- print(f"[SECURITY WARNING] Token gerado dinamicamente: {ADMIN_TOKEN}\n")
34
-
35
- limiter = Limiter(key_func=get_remote_address)
36
  UPLOAD_MAX_SIZE = 10 * 1024 * 1024 # 10MB para análises comuns
37
  ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "*").split(",")
38
 
@@ -46,24 +35,15 @@ app.add_middleware(
46
  allow_methods=["*"],
47
  allow_headers=["*"],
48
  )
49
- app.state.limiter = limiter
50
- app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
51
 
52
- # --- MIDDLEWARE E ARQUIVOS ESTÁTICOS ---
53
  from fastapi import Request
54
  from fastapi.responses import JSONResponse
55
 
56
- # Garante que o diretório .tmp existe
57
- if not os.path.exists(".tmp"):
58
- os.makedirs(".tmp")
59
-
60
- # Monta o diretório .tmp para servir os espectrogramas gerados
61
- app.mount("/tmp", StaticFiles(directory=".tmp"), name="tmp")
62
-
63
  @app.middleware("http")
64
  async def limit_upload_size(request: Request, call_next):
65
  # O limite de 10MB não se aplica às rotas de admin (datasets são maiores)
66
- if request.method == "POST" and not request.url.path.startswith("/admin") and request.url.path == "/analyze":
67
  if "content-length" in request.headers:
68
  if int(request.headers["content-length"]) > UPLOAD_MAX_SIZE:
69
  return JSONResponse(
@@ -122,8 +102,7 @@ class AnalysisResult(BaseModel):
122
  temporal_scores: list = []
123
 
124
  @app.post("/analyze", response_model=AnalysisResult)
125
- @limiter.limit("5/minute")
126
- async def analyze_audio_endpoint(request: Request, background_tasks: BackgroundTasks, file: UploadFile = File(...)):
127
  # Validação rigorosa de extensão
128
  ALLOWED_EXTENSIONS = {'.wav', '.mp3', '.flac', '.ogg', '.m4a', '.aac'}
129
  ext = os.path.splitext(file.filename)[1].lower()
@@ -152,19 +131,19 @@ async def analyze_audio_endpoint(request: Request, background_tasks: BackgroundT
152
  # 2. Inferência via Ensemble (Wav2Vec2 + AST)
153
  analysis = get_combined_verdict(file_path)
154
 
155
- # 3. Agenda limpeza em background (após 10 minutos para dar tempo do front ler a imagem)
156
  def cleanup_temp_files(paths):
157
  import time
158
- time.sleep(600) # 10 minutos
159
  for p in paths:
160
- if p and os.path.exists(p):
161
  try:
162
  os.remove(p)
163
- except: pass
 
 
164
 
165
- # O spectrogram_path vem de features
166
- spec_local_path = features.get("spectrogram_path")
167
- background_tasks.add_task(cleanup_temp_files, [file_path, spec_local_path])
168
 
169
  # 4. Resposta Consolidada
170
  return AnalysisResult(
@@ -172,7 +151,7 @@ async def analyze_audio_endpoint(request: Request, background_tasks: BackgroundT
172
  fraud_score=analysis.get("fraud_probability", 0.0),
173
  verdict=analysis.get("verdict", "UNKNOWN"),
174
  spectrogram_url=features.get("spectrogram_path", "").replace(".tmp/", "/tmp/"),
175
- engine="HyperMoon Ensemble v2 (Wav2Vec2 + AST)",
176
  wav2vec_score=analysis.get("wav2vec_score", 0.0),
177
  ast_score=analysis.get("ast_score", 0.0),
178
  engines_consensus=analysis.get("engines_consensus", ""),
@@ -181,11 +160,7 @@ async def analyze_audio_endpoint(request: Request, background_tasks: BackgroundT
181
 
182
  except Exception as e:
183
  print(f"Erro na análise: {e}")
184
- # Tratamento de erro amigável (Production Ready)
185
- raise HTTPException(
186
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
187
- detail="Não foi possível processar este arquivo de áudio. Verifique se o arquivo não está corrompido ou protegido por DRM."
188
- )
189
 
190
  # --- ADMIN ENDPOINTS ---
191
 
 
12
  import rarfile
13
  import uuid
14
  import uvicorn
 
 
 
 
15
 
16
  # Carrega variáveis do arquivo .env
17
  load_dotenv()
 
21
  from execution.ensemble_manager import get_combined_verdict
22
 
23
  # Configurações de Segurança e Limites
24
+ ADMIN_TOKEN = os.environ.get("ADMIN_TOKEN", "confereai_admin_token_2026")
 
 
 
 
 
 
 
25
  UPLOAD_MAX_SIZE = 10 * 1024 * 1024 # 10MB para análises comuns
26
  ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "*").split(",")
27
 
 
35
  allow_methods=["*"],
36
  allow_headers=["*"],
37
  )
 
 
38
 
39
+ # --- MIDDLEWARE DE TAMANHO DE UPLOAD ---
40
  from fastapi import Request
41
  from fastapi.responses import JSONResponse
42
 
 
 
 
 
 
 
 
43
  @app.middleware("http")
44
  async def limit_upload_size(request: Request, call_next):
45
  # O limite de 10MB não se aplica às rotas de admin (datasets são maiores)
46
+ if request.method == "POST" and not request.url.path.startswith("/admin"):
47
  if "content-length" in request.headers:
48
  if int(request.headers["content-length"]) > UPLOAD_MAX_SIZE:
49
  return JSONResponse(
 
102
  temporal_scores: list = []
103
 
104
  @app.post("/analyze", response_model=AnalysisResult)
105
+ async def analyze_audio_endpoint(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
 
106
  # Validação rigorosa de extensão
107
  ALLOWED_EXTENSIONS = {'.wav', '.mp3', '.flac', '.ogg', '.m4a', '.aac'}
108
  ext = os.path.splitext(file.filename)[1].lower()
 
131
  # 2. Inferência via Ensemble (Wav2Vec2 + AST)
132
  analysis = get_combined_verdict(file_path)
133
 
134
+ # 3. Agenda limpeza em background (após 5 minutos para dar tempo do front ler a imagem)
135
  def cleanup_temp_files(paths):
136
  import time
137
+ time.sleep(300) # 5 minutos
138
  for p in paths:
139
+ if os.path.exists(p):
140
  try:
141
  os.remove(p)
142
+ print(f"Cleanup: {p} removido.")
143
+ except Exception as e:
144
+ print(f"Cleanup error: {e}")
145
 
146
+ background_tasks.add_task(cleanup_temp_files, [file_path, features.get("spectrogram_path")])
 
 
147
 
148
  # 4. Resposta Consolidada
149
  return AnalysisResult(
 
151
  fraud_score=analysis.get("fraud_probability", 0.0),
152
  verdict=analysis.get("verdict", "UNKNOWN"),
153
  spectrogram_url=features.get("spectrogram_path", "").replace(".tmp/", "/tmp/"),
154
+ engine="Dual Engine (Wav2Vec2 + AST) - Protocolo de Rigor",
155
  wav2vec_score=analysis.get("wav2vec_score", 0.0),
156
  ast_score=analysis.get("ast_score", 0.0),
157
  engines_consensus=analysis.get("engines_consensus", ""),
 
160
 
161
  except Exception as e:
162
  print(f"Erro na análise: {e}")
163
+ raise e
 
 
 
 
164
 
165
  # --- ADMIN ENDPOINTS ---
166
 
execution/inference_wav2vec.py CHANGED
@@ -8,7 +8,7 @@ from transformers import AutoFeatureExtractor, AutoModelForAudioClassification
8
  import os
9
 
10
  LOCAL_MODEL_DIR = "./local_finetuned_model"
11
- # Prioridade: 1. Pasta Local | 2. Variável de Ambiente | 3. Modelo Base Estável
12
  CUSTOM_MODEL_REPO = os.environ.get("CUSTOM_MODEL_REPO", None)
13
  BASE_MODEL = "HyperMoon/wav2vec2-base-960h-finetuned-deepfake"
14
 
@@ -26,13 +26,17 @@ def get_wav2vec_resources(model_path):
26
  _feature_extractor = AutoFeatureExtractor.from_pretrained(model_path)
27
  model = AutoModelForAudioClassification.from_pretrained(model_path)
28
 
 
 
 
 
 
 
 
29
  _model = model
30
  _model.eval()
31
  _last_model_path = model_path
32
 
33
- # Log para depuração de labels
34
- print(f"Mapeamento de Labels: {model.config.id2label}", file=sys.stderr)
35
-
36
  return _feature_extractor, _model
37
 
38
  def run_inference(audio_path, fallback_model_name=None):
 
8
  import os
9
 
10
  LOCAL_MODEL_DIR = "./local_finetuned_model"
11
+ # Prioridade: 1. Pasta Local (Upload direto) | 2. Repo Customizado (Variável de Ambiente) | 3. Modelo Base
12
  CUSTOM_MODEL_REPO = os.environ.get("CUSTOM_MODEL_REPO", None)
13
  BASE_MODEL = "HyperMoon/wav2vec2-base-960h-finetuned-deepfake"
14
 
 
26
  _feature_extractor = AutoFeatureExtractor.from_pretrained(model_path)
27
  model = AutoModelForAudioClassification.from_pretrained(model_path)
28
 
29
+ # --- OTIMIZAÇÃO: Quantização Dinâmica para CPU ---
30
+ if not torch.cuda.is_available():
31
+ print("Aplicando Quantização Dinâmica (CPU Optimization)...", file=sys.stderr)
32
+ model = torch.quantization.quantize_dynamic(
33
+ model, {torch.nn.Linear}, dtype=torch.qint8
34
+ )
35
+
36
  _model = model
37
  _model.eval()
38
  _last_model_path = model_path
39
 
 
 
 
40
  return _feature_extractor, _model
41
 
42
  def run_inference(audio_path, fallback_model_name=None):