corrigindo upload
This commit is contained in:
1 parent
73693e5da9
commit
d2a68fa744
9 files changed
+4295
-74
No files matched your search
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import sys
|
||||
import multiprocessing
|
||||
import concurrent.futures # NOVO: Necessário para usar o ProcessPoolExecutor
|
||||
import csv
|
||||
@@ -7,6 +6,7 @@ import time
|
||||
import glob
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
# ================= 1. LIMITADOR DE CPU DINÂMICO =================
|
||||
def configurar_limites_cpu():
|
||||
try:
|
||||
@@ -28,6 +28,7 @@ def configurar_limites_cpu():
|
||||
except Exception as e:
|
||||
print(f"Aviso: Não foi possível limitar CPU automaticamente: {e}")
|
||||
|
||||
|
||||
configurar_limites_cpu()
|
||||
|
||||
# ================= IMPORTS =================
|
||||
@@ -84,6 +85,7 @@ GLOBAL_EMBEDDING = None
|
||||
GLOBAL_CLASSIFIER = None
|
||||
GLOBAL_CLASSES = None
|
||||
|
||||
|
||||
# ================= CLASSE DE AUDITORIA OTIMIZADA =================
|
||||
class Auditoria:
|
||||
def __init__(self, arquivo_csv):
|
||||
@@ -110,7 +112,9 @@ class Auditoria:
|
||||
)
|
||||
self.file.flush()
|
||||
|
||||
def registrar_processamento(self, nome_arquivo, tamanho_bytes, duracao_audio, tempo_gasto):
|
||||
def registrar_processamento(
|
||||
self, nome_arquivo, tamanho_bytes, duracao_audio, tempo_gasto
|
||||
):
|
||||
try:
|
||||
ram_mb = self.process.memory_info().rss / (1024 * 1024)
|
||||
cpu_pct = self.process.cpu_percent(interval=None)
|
||||
@@ -134,7 +138,7 @@ class Auditoria:
|
||||
logging.error(f"Erro ao auditar arquivo: {e}")
|
||||
|
||||
def verificar_marco(self, contagem):
|
||||
pass # Você pode customizar logs de progresso aqui depois se quiser
|
||||
pass # Você pode customizar logs de progresso aqui depois se quiser
|
||||
|
||||
def fechar(self):
|
||||
try:
|
||||
@@ -145,6 +149,7 @@ class Auditoria:
|
||||
|
||||
# ================= FUNÇÕES AUXILIARES =================
|
||||
|
||||
|
||||
def verificar_memoria_segura(tamanho_arquivo_bytes):
|
||||
mem = psutil.virtual_memory()
|
||||
livre_mb = mem.available / (1024 * 1024)
|
||||
@@ -225,7 +230,9 @@ def carregar_database_yaml():
|
||||
logging.error(f"Erro ao ler lote {arquivo}: {e}")
|
||||
print(f"X Erro ao ler {os.path.basename(arquivo)}")
|
||||
|
||||
print(f"--- Total de Projetos no DB (Memória): {len(db)} (de {total_carregados} lidos) ---")
|
||||
print(
|
||||
f"--- Total de Projetos no DB (Memória): {len(db)} (de {total_carregados} lidos) ---"
|
||||
)
|
||||
return db
|
||||
|
||||
|
||||
@@ -300,14 +307,14 @@ def calcular_complexidade(projeto_yaml):
|
||||
score += num_fx * W_FX
|
||||
stats["num_effects"] += num_fx
|
||||
|
||||
if score <= 15:
|
||||
estrelas = 1
|
||||
elif score <= 40:
|
||||
estrelas = 2
|
||||
if score <= 120:
|
||||
estrelas = 4
|
||||
elif score <= 80:
|
||||
estrelas = 3
|
||||
elif score <= 120:
|
||||
estrelas = 4
|
||||
elif score <= 40:
|
||||
estrelas = 2
|
||||
elif score <= 15:
|
||||
estrelas = 1
|
||||
else:
|
||||
estrelas = 5
|
||||
|
||||
@@ -316,6 +323,7 @@ def calcular_complexidade(projeto_yaml):
|
||||
|
||||
# ================= NÚCLEO DE ANÁLISE =================
|
||||
|
||||
|
||||
def detectar_estrutura(audio_vec, sample_rate, duration):
|
||||
try:
|
||||
if duration < 30:
|
||||
@@ -496,7 +504,9 @@ def analisar_faixa(caminho_arquivo, embedding_model, classifier_model, classes_r
|
||||
|
||||
metadata["analise_ia"] = {
|
||||
"genero_macro": genero_pai,
|
||||
"estilo_principal": tags_detectadas[0]["tag"] if tags_detectadas else "Unknown",
|
||||
"estilo_principal": tags_detectadas[0]["tag"]
|
||||
if tags_detectadas
|
||||
else "Unknown",
|
||||
"nuvem_tags": tags_detectadas,
|
||||
}
|
||||
|
||||
@@ -509,7 +519,7 @@ def analisar_faixa(caminho_arquivo, embedding_model, classifier_model, classes_r
|
||||
metadata["analise_ia"] = {"erro": msg}
|
||||
else:
|
||||
metadata["analise_ia"] = {"status": "Modelos nao carregados"}
|
||||
|
||||
|
||||
# Limpeza Limpa
|
||||
del audio_hq
|
||||
del audio_hq_vec
|
||||
@@ -525,12 +535,13 @@ def worker_analise(caminho):
|
||||
global GLOBAL_EMBEDDING, GLOBAL_CLASSIFIER, GLOBAL_CLASSES
|
||||
return analisar_faixa(caminho, GLOBAL_EMBEDDING, GLOBAL_CLASSIFIER, GLOBAL_CLASSES)
|
||||
|
||||
|
||||
def main():
|
||||
print("\n--- INICIANDO PROCESSADOR + AUDITORIA ---")
|
||||
|
||||
# Referencia variávies globais do script para alimentar os childs process do pool
|
||||
global DATABASE_YAML, GLOBAL_EMBEDDING, GLOBAL_CLASSIFIER, GLOBAL_CLASSES
|
||||
|
||||
|
||||
DATABASE_YAML = carregar_database_yaml()
|
||||
|
||||
ja_processados, lista_resultados = carregar_checkpoint()
|
||||
@@ -540,7 +551,7 @@ def main():
|
||||
if not os.path.exists(PASTA_WAVS):
|
||||
print("CRÍTICO: Pasta WAV não encontrada!")
|
||||
return
|
||||
|
||||
|
||||
todos = [f for f in os.listdir(PASTA_WAVS) if f.lower().endswith((".wav", ".ogg"))]
|
||||
a_fazer = [os.path.join(PASTA_WAVS, f) for f in todos if f not in ja_processados]
|
||||
|
||||
@@ -549,11 +560,27 @@ def main():
|
||||
print(f"Auditoria será salva em: {ARQUIVO_AUDITORIA}")
|
||||
|
||||
# === CARREGAMENTO MODELOS GLOBAIS ===
|
||||
path_embed = MODELO_EMBEDDING if os.path.exists(MODELO_EMBEDDING) else os.path.join(BASE_DIR, MODELO_EMBEDDING)
|
||||
path_class = MODELO_CLASSIFIER if os.path.exists(MODELO_CLASSIFIER) else os.path.join(BASE_DIR, MODELO_CLASSIFIER)
|
||||
path_json = MODELO_CLASSES if os.path.exists(MODELO_CLASSES) else os.path.join(BASE_DIR, MODELO_CLASSES)
|
||||
path_embed = (
|
||||
MODELO_EMBEDDING
|
||||
if os.path.exists(MODELO_EMBEDDING)
|
||||
else os.path.join(BASE_DIR, MODELO_EMBEDDING)
|
||||
)
|
||||
path_class = (
|
||||
MODELO_CLASSIFIER
|
||||
if os.path.exists(MODELO_CLASSIFIER)
|
||||
else os.path.join(BASE_DIR, MODELO_CLASSIFIER)
|
||||
)
|
||||
path_json = (
|
||||
MODELO_CLASSES
|
||||
if os.path.exists(MODELO_CLASSES)
|
||||
else os.path.join(BASE_DIR, MODELO_CLASSES)
|
||||
)
|
||||
|
||||
if os.path.exists(path_embed) and os.path.exists(path_class) and os.path.exists(path_json):
|
||||
if (
|
||||
os.path.exists(path_embed)
|
||||
and os.path.exists(path_class)
|
||||
and os.path.exists(path_json)
|
||||
):
|
||||
print("--- Carregando Modelos de IA ---")
|
||||
try:
|
||||
with open(path_json, "r") as f:
|
||||
@@ -561,13 +588,23 @@ def main():
|
||||
|
||||
print("[1/2] Carregando Extrator de Embeddings...")
|
||||
if hasattr(es, "TensorflowPredictEffnetDiscogs"):
|
||||
GLOBAL_EMBEDDING = es.TensorflowPredictEffnetDiscogs(graphFilename=path_embed, output="PartitionedCall:1")
|
||||
GLOBAL_EMBEDDING = es.TensorflowPredictEffnetDiscogs(
|
||||
graphFilename=path_embed, output="PartitionedCall:1"
|
||||
)
|
||||
else:
|
||||
print(" -> Usando TensorflowPredict genérico.")
|
||||
GLOBAL_EMBEDDING = es.TensorflowPredict(graphFilename=path_embed, input="serving_default_model_Placeholder", output="PartitionedCall:1")
|
||||
GLOBAL_EMBEDDING = es.TensorflowPredict(
|
||||
graphFilename=path_embed,
|
||||
input="serving_default_model_Placeholder",
|
||||
output="PartitionedCall:1",
|
||||
)
|
||||
print(" -> Embeddings carregados com sucesso.")
|
||||
|
||||
GLOBAL_CLASSIFIER = es.TensorflowPredict2D(graphFilename=path_class, input="serving_default_model_Placeholder", output="PartitionedCall:0")
|
||||
GLOBAL_CLASSIFIER = es.TensorflowPredict2D(
|
||||
graphFilename=path_class,
|
||||
input="serving_default_model_Placeholder",
|
||||
output="PartitionedCall:0",
|
||||
)
|
||||
print("[2/2] Classificador de Gênero carregado.")
|
||||
|
||||
except Exception as e:
|
||||
@@ -580,40 +617,50 @@ def main():
|
||||
if total == 0:
|
||||
print("Tudo atualizado.")
|
||||
return
|
||||
|
||||
# ProcessPoolExecutor lidará com N filas ao mesmo tempo
|
||||
|
||||
# ProcessPoolExecutor lidará com N filas ao mesmo tempo
|
||||
max_workers = max(1, multiprocessing.cpu_count() - 1)
|
||||
print(f"-> Iniciando processamento paralelo com {max_workers} workers.")
|
||||
contagem_sessao = 0
|
||||
|
||||
try:
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
resultados_futuros = {executor.submit(worker_analise, caminho): caminho for caminho in a_fazer}
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(
|
||||
max_workers=max_workers
|
||||
) as executor:
|
||||
resultados_futuros = {
|
||||
executor.submit(worker_analise, caminho): caminho for caminho in a_fazer
|
||||
}
|
||||
|
||||
# O as_completed processa os que terminam mais rápido em vez de travar na ordem
|
||||
for future in tqdm(concurrent.futures.as_completed(resultados_futuros), total=len(a_fazer), unit="track"):
|
||||
for future in tqdm(
|
||||
concurrent.futures.as_completed(resultados_futuros),
|
||||
total=len(a_fazer),
|
||||
unit="track",
|
||||
):
|
||||
try:
|
||||
res, duracao, tempo_gasto = future.result()
|
||||
caminho = resultados_futuros[future]
|
||||
|
||||
|
||||
if res:
|
||||
salvar_progresso(res)
|
||||
lista_resultados.append(res)
|
||||
|
||||
|
||||
tamanho = os.path.getsize(caminho)
|
||||
auditor.registrar_processamento(res['arquivo'], tamanho, duracao, tempo_gasto)
|
||||
|
||||
auditor.registrar_processamento(
|
||||
res["arquivo"], tamanho, duracao, tempo_gasto
|
||||
)
|
||||
|
||||
contagem_sessao += 1
|
||||
auditor.verificar_marco(contagem_sessao)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Erro ao processar um dos arquivos no worker: {e}")
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nParando paralelismo graciosamente...")
|
||||
|
||||
finally:
|
||||
auditor.fechar() # Fecha o CSV com segurança
|
||||
auditor.fechar() # Fecha o CSV com segurança
|
||||
|
||||
tempo_total_sessao = time.time() - auditor.inicio_global
|
||||
print("\n--- FIM DO PROCESSAMENTO ---")
|
||||
@@ -628,4 +675,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
@@ -120,7 +120,7 @@ def calcular_nivel_ponderado(
|
||||
if num_tracks >= 32:
|
||||
pontos += 3
|
||||
if num_tracks >= 64:
|
||||
pontos += 4
|
||||
pontos += 4
|
||||
elif num_tracks >= 128:
|
||||
pontos += 5
|
||||
|
||||
@@ -133,12 +133,12 @@ def calcular_nivel_ponderado(
|
||||
pontos += 1
|
||||
|
||||
# 3. Estrutura (Áudio)
|
||||
if qtd_secoes_audio >= 3:
|
||||
pontos += 1.5
|
||||
if qtd_secoes_audio >= 10:
|
||||
pontos += 5
|
||||
elif qtd_secoes_audio >= 5:
|
||||
pontos += 3
|
||||
elif qtd_secoes_audio >= 10:
|
||||
pontos += 5
|
||||
elif qtd_secoes_audio >= 3:
|
||||
pontos += 1.5
|
||||
|
||||
if pontos < 3:
|
||||
return "Iniciante"
|
||||
|
||||
@@ -7,6 +7,7 @@ import shutil
|
||||
import time
|
||||
import io
|
||||
import threading
|
||||
from threading import Lock
|
||||
|
||||
from flask import Flask, request, jsonify, send_file, redirect
|
||||
from flask_cors import CORS
|
||||
@@ -41,6 +42,15 @@ from utils import (
|
||||
SAMPLE_MANIFEST,
|
||||
)
|
||||
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO
|
||||
) # obriga o servidor a mostrar os sucessos também
|
||||
|
||||
# Trava do build do Jekyll
|
||||
JEKYLL_LOCK = Lock()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# --- CONFIGURAÇÃO DE SEGURANÇA E BANCO ---
|
||||
@@ -164,17 +174,13 @@ def allowed_sample(filename):
|
||||
and filename.rsplit(".", 1)[1].lower() in ALLOWED_SAMPLE_EXTENSIONS
|
||||
)
|
||||
|
||||
|
||||
def run_jekyll_build():
|
||||
RUBY_BIN_PATH = "/usr/bin/ruby3.2"
|
||||
BUNDLE_PATH = (
|
||||
"/nethome/jotachina/projetos/mmpSearch/vendor/bundle/ruby/3.2.0/bin/bundle"
|
||||
)
|
||||
# Prepara o ambiente para o subprocesso
|
||||
BUNDLE_PATH = "/nethome/jotachina/projetos/mmpSearch/vendor/bundle/ruby/3.2.0/bin/bundle"
|
||||
|
||||
env_vars = os.environ.copy()
|
||||
# Adiciona o caminho do Ruby ao PATH do usuário www-data temporariamente
|
||||
env_vars["PATH"] = f"{RUBY_BIN_PATH}:{env_vars.get('PATH', '')}"
|
||||
print("Iniciando build do Jekyll...")
|
||||
|
||||
command = [
|
||||
BUNDLE_PATH,
|
||||
"exec",
|
||||
@@ -183,18 +189,24 @@ def run_jekyll_build():
|
||||
"--destination",
|
||||
"/var/www/html/trens/mmpSearch/",
|
||||
]
|
||||
try:
|
||||
# Redirecionamos a saída para DEVNULL para não encher o buffer e travar
|
||||
subprocess.Popen(
|
||||
command,
|
||||
cwd=BASE_DATA,
|
||||
env=env_vars,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
print("Jekyll Build iniciado em segundo plano (background).")
|
||||
except Exception as e:
|
||||
print(f"Erro ao iniciar Jekyll: {e}")
|
||||
|
||||
logging.info("⏳ Aguardando liberação para iniciar build do Jekyll...")
|
||||
with JEKYLL_LOCK:
|
||||
logging.info("🔨 Iniciando build do Jekyll (Travando a fila)...")
|
||||
try:
|
||||
resultado = subprocess.run(
|
||||
command,
|
||||
cwd=BASE_DATA,
|
||||
env=env_vars,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
logging.info("✅ Jekyll Build concluído com sucesso!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error(f"❌ ERRO FATAL no Jekyll Build (Code {e.returncode}):\n{e.stderr}")
|
||||
except Exception as e:
|
||||
logging.error(f"❌ Erro inesperado ao chamar Jekyll: {e}")
|
||||
|
||||
|
||||
def load_manifest_keys():
|
||||
@@ -321,31 +333,27 @@ def update_xml_paths_exact(mmp_filename, replacements):
|
||||
|
||||
|
||||
def run_heavy_tasks_in_background():
|
||||
"""Esta função roda isolada sem travar o usuário"""
|
||||
print("--- [BACKGROUND] Iniciando reconstrução de índices ---")
|
||||
logging.info("--- [BACKGROUND] Iniciando tarefas assíncronas ---")
|
||||
try:
|
||||
|
||||
# 2. Isso gera os manifestos (Python puro)
|
||||
generate_manifests(SRC_MMPSEARCH)
|
||||
|
||||
# 3. Isso chama o subprocesso do Jekyll (Externo)
|
||||
# Mantém sua função original que usa subprocess
|
||||
run_jekyll_build()
|
||||
|
||||
print("--- [BACKGROUND] Tarefas concluídas com sucesso ---")
|
||||
logging.info("--- [BACKGROUND] Todas as tarefas concluídas ---")
|
||||
except Exception as e:
|
||||
print(f"--- [BACKGROUND] Erro: {e} ---")
|
||||
|
||||
logging.error(f"--- [BACKGROUND] Erro: {e} ---")
|
||||
|
||||
def process_and_build(filename):
|
||||
"""Função chamada pela rota de upload"""
|
||||
# Processamento inicial do arquivo (rápido)
|
||||
result = process_single_file(filename)
|
||||
|
||||
# 1. Preparamos os 3 dados que o main.py exige: (file_name, clean_slug, total_files)
|
||||
name_without_ext = os.path.splitext(filename)[0]
|
||||
clean_slug = slugify(name_without_ext)
|
||||
args_tuple = (filename, clean_slug, 1) # O 1 representa que é só 1 arquivo no total
|
||||
|
||||
# 2. Passamos a tupla!
|
||||
result = process_single_file(args_tuple)
|
||||
|
||||
if result["success"]:
|
||||
# Em vez de chamar rebuild_indexes() direto, criamos a Thread
|
||||
# O Flask vai responder o return abaixo imediatamente,
|
||||
# enquanto a thread continua rodando no servidor.
|
||||
task_thread = threading.Thread(target=run_heavy_tasks_in_background)
|
||||
task_thread.start()
|
||||
|
||||
|
||||
Reference in new issue
Block a user