att base de dados
This commit is contained in:
1 parent
ceb7908da4
commit
73693e5da9
14 files changed
+3010
-1
No files matched your search
@@ -10,6 +10,5 @@ venv
|
|||||||
.gitignore
|
.gitignore
|
||||||
.git
|
.git
|
||||||
.gitea
|
.gitea
|
||||||
scripts
|
|
||||||
src
|
src
|
||||||
assets/js/creations/server/data
|
assets/js/creations/server/data
|
||||||
@@ -0,0 +1,631 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import multiprocessing
|
||||||
|
import concurrent.futures # NOVO: Necessário para usar o ProcessPoolExecutor
|
||||||
|
import csv
|
||||||
|
import time
|
||||||
|
import glob
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# ================= 1. LIMITADOR DE CPU DINÂMICO =================
|
||||||
|
def configurar_limites_cpu():
|
||||||
|
try:
|
||||||
|
total_cores = multiprocessing.cpu_count()
|
||||||
|
|
||||||
|
print("--- Configuração de Hardware ---")
|
||||||
|
print(f"Núcleos Totais: {total_cores}")
|
||||||
|
print("Núcleos Alocados: 1 thread por processo (Multiprocessing Real)")
|
||||||
|
print("Núcleos Reservados para o Sistema: 1")
|
||||||
|
|
||||||
|
# IMPORTANTE: Em processamento paralelo real (ProcessPoolExecutor),
|
||||||
|
# as libs em C++ devem usar apenas "1" thread para evitar colisão na CPU (oversubscription)
|
||||||
|
os.environ["OMP_NUM_THREADS"] = "1"
|
||||||
|
os.environ["MKL_NUM_THREADS"] = "1"
|
||||||
|
os.environ["TF_NUM_INTRAOP_THREADS"] = "1"
|
||||||
|
os.environ["TF_NUM_INTEROP_THREADS"] = "1"
|
||||||
|
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Aviso: Não foi possível limitar CPU automaticamente: {e}")
|
||||||
|
|
||||||
|
configurar_limites_cpu()
|
||||||
|
|
||||||
|
# ================= IMPORTS =================
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
import logging
|
||||||
|
import gc
|
||||||
|
import numpy as np
|
||||||
|
import psutil
|
||||||
|
import essentia
|
||||||
|
import essentia.standard as es
|
||||||
|
from tqdm import tqdm
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
# Desativa logs internos do Essentia
|
||||||
|
essentia.log.info.active = False
|
||||||
|
essentia.log.warning.active = False
|
||||||
|
|
||||||
|
# ================= CONFIGURAÇÕES =================
|
||||||
|
BASE_DIR = "/var/www/html/trens/src_mmpSearch"
|
||||||
|
PASTA_WAVS = os.path.join(BASE_DIR, "wav")
|
||||||
|
PASTA_LOTES_YAML = os.path.join(BASE_DIR, "lotes_yaml")
|
||||||
|
|
||||||
|
# --- SISTEMA DE DOIS MODELOS ---
|
||||||
|
MODELO_EMBEDDING = "discogs-effnet-bs64-1.pb"
|
||||||
|
MODELO_CLASSIFIER = "genre_discogs400.pb"
|
||||||
|
MODELO_CLASSES = "genre_discogs400.json"
|
||||||
|
|
||||||
|
# Saídas
|
||||||
|
DIR_SAIDA = os.path.join(BASE_DIR, "saida_analises")
|
||||||
|
os.makedirs(DIR_SAIDA, exist_ok=True)
|
||||||
|
ARQUIVO_CHECKPOINT = os.path.join(DIR_SAIDA, "checkpoint_analises.jsonl")
|
||||||
|
ARQUIVO_FINAL_JSON = os.path.join(DIR_SAIDA, "db_final_completo.json")
|
||||||
|
ARQUIVO_AUDITORIA = os.path.join(DIR_SAIDA, "audit_performance.csv")
|
||||||
|
|
||||||
|
# Limites de Segurança
|
||||||
|
LIMITE_RAM_MINIMA_MB = 500
|
||||||
|
MAX_DURACAO_ANALISE = 20 * 60
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
LOG_DIR = os.path.join(BASE_DIR, "logs/classificacao")
|
||||||
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
TIMESTAMP = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=os.path.join(LOG_DIR, f"log_{TIMESTAMP}.log"),
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_YAML = {}
|
||||||
|
|
||||||
|
# Variáveis globais para os Workers conseguirem enxergar os modelos em memória
|
||||||
|
GLOBAL_EMBEDDING = None
|
||||||
|
GLOBAL_CLASSIFIER = None
|
||||||
|
GLOBAL_CLASSES = None
|
||||||
|
|
||||||
|
# ================= CLASSE DE AUDITORIA OTIMIZADA =================
|
||||||
|
class Auditoria:
|
||||||
|
def __init__(self, arquivo_csv):
|
||||||
|
self.arquivo_csv = arquivo_csv
|
||||||
|
self.inicio_global = time.time()
|
||||||
|
self.process = psutil.Process(os.getpid())
|
||||||
|
|
||||||
|
# Mantém o arquivo aberto em modo 'append' na memória (Resolve Gargalo HD)
|
||||||
|
cabecalho = not os.path.exists(self.arquivo_csv)
|
||||||
|
self.file = open(self.arquivo_csv, "a", newline="", encoding="utf-8")
|
||||||
|
self.writer = csv.writer(self.file)
|
||||||
|
|
||||||
|
if cabecalho:
|
||||||
|
self.writer.writerow(
|
||||||
|
[
|
||||||
|
"timestamp",
|
||||||
|
"arquivo",
|
||||||
|
"tamanho_mb",
|
||||||
|
"duracao_audio_s",
|
||||||
|
"tempo_proc_s",
|
||||||
|
"ram_uso_mb",
|
||||||
|
"cpu_percent",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.file.flush()
|
||||||
|
|
||||||
|
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)
|
||||||
|
tamanho_mb = tamanho_bytes / (1024 * 1024)
|
||||||
|
agora = datetime.now().strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
# Escreve na mesma conexão de arquivo aberta
|
||||||
|
self.writer.writerow(
|
||||||
|
[
|
||||||
|
agora,
|
||||||
|
nome_arquivo,
|
||||||
|
round(tamanho_mb, 2),
|
||||||
|
round(duracao_audio, 2),
|
||||||
|
round(tempo_gasto, 4),
|
||||||
|
round(ram_mb, 2),
|
||||||
|
cpu_pct,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.file.flush()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Erro ao auditar arquivo: {e}")
|
||||||
|
|
||||||
|
def verificar_marco(self, contagem):
|
||||||
|
pass # Você pode customizar logs de progresso aqui depois se quiser
|
||||||
|
|
||||||
|
def fechar(self):
|
||||||
|
try:
|
||||||
|
self.file.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ================= FUNÇÕES AUXILIARES =================
|
||||||
|
|
||||||
|
def verificar_memoria_segura(tamanho_arquivo_bytes):
|
||||||
|
mem = psutil.virtual_memory()
|
||||||
|
livre_mb = mem.available / (1024 * 1024)
|
||||||
|
estimativa_uso_ram = tamanho_arquivo_bytes * 15
|
||||||
|
uso_mb = estimativa_uso_ram / (1024 * 1024)
|
||||||
|
|
||||||
|
if livre_mb < LIMITE_RAM_MINIMA_MB:
|
||||||
|
print(f" [!] RAM Crítica ({livre_mb:.1f}MB livres). Forçando limpeza...")
|
||||||
|
gc.collect()
|
||||||
|
time.sleep(2)
|
||||||
|
mem = psutil.virtual_memory()
|
||||||
|
if mem.available / (1024 * 1024) < LIMITE_RAM_MINIMA_MB:
|
||||||
|
return False, "RAM insuficiente no sistema"
|
||||||
|
|
||||||
|
if uso_mb > (livre_mb * 0.8):
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
f"Arquivo muito grande para RAM atual (Est: {uso_mb:.1f}MB, Livre: {livre_mb:.1f}MB)",
|
||||||
|
)
|
||||||
|
|
||||||
|
return True, "OK"
|
||||||
|
|
||||||
|
|
||||||
|
def normalizar_chave(texto):
|
||||||
|
if not texto:
|
||||||
|
return ""
|
||||||
|
s = str(texto)
|
||||||
|
if "/" in s:
|
||||||
|
s = os.path.basename(s)
|
||||||
|
s = os.path.splitext(s)[0]
|
||||||
|
s = unicodedata.normalize("NFKD", s).encode("ASCII", "ignore").decode("ASCII")
|
||||||
|
return s.lower().replace(" ", "").replace("-", "").replace("_", "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def carregar_database_yaml():
|
||||||
|
print(f"--- Carregando Metadados de Lotes YAML em: {PASTA_LOTES_YAML} ---")
|
||||||
|
|
||||||
|
padrao = os.path.join(PASTA_LOTES_YAML, "*.yml")
|
||||||
|
arquivos_lote = sorted(glob.glob(padrao))
|
||||||
|
|
||||||
|
db = {}
|
||||||
|
|
||||||
|
if not arquivos_lote:
|
||||||
|
logging.warning(f"Nenhum lote encontrado em {PASTA_LOTES_YAML}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
total_carregados = 0
|
||||||
|
|
||||||
|
for arquivo in arquivos_lote:
|
||||||
|
try:
|
||||||
|
with open(arquivo, "r", encoding="utf-8") as f:
|
||||||
|
dados_lote = yaml.safe_load(f)
|
||||||
|
|
||||||
|
if not dados_lote:
|
||||||
|
continue
|
||||||
|
|
||||||
|
lista = dados_lote if isinstance(dados_lote, list) else [dados_lote]
|
||||||
|
|
||||||
|
for proj in lista:
|
||||||
|
if not isinstance(proj, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
candidatos = [
|
||||||
|
proj.get("file"),
|
||||||
|
proj.get("original_title"),
|
||||||
|
proj.get("src"),
|
||||||
|
]
|
||||||
|
for k in candidatos:
|
||||||
|
if k:
|
||||||
|
chave = normalizar_chave(k)
|
||||||
|
if chave:
|
||||||
|
db[chave] = proj
|
||||||
|
|
||||||
|
total_carregados += len(lista)
|
||||||
|
print(f"-> Carregado: {os.path.basename(arquivo)} ({len(lista)} projetos)")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
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) ---")
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
def carregar_checkpoint():
|
||||||
|
processados = set()
|
||||||
|
dados_existentes = []
|
||||||
|
if os.path.exists(ARQUIVO_CHECKPOINT):
|
||||||
|
with open(ARQUIVO_CHECKPOINT, "r", encoding="utf-8") as f:
|
||||||
|
for linha in f:
|
||||||
|
try:
|
||||||
|
obj = json.loads(linha)
|
||||||
|
reprocessar = False
|
||||||
|
if "analise_ia" in obj and "erro" in obj["analise_ia"]:
|
||||||
|
reprocessar = True
|
||||||
|
if "analise_tecnica" in obj:
|
||||||
|
est = obj["analise_tecnica"].get("estrutura_segundos", [])
|
||||||
|
if len(est) == 2 and est[1] == 12.0:
|
||||||
|
reprocessar = True
|
||||||
|
if reprocessar:
|
||||||
|
continue
|
||||||
|
processados.add(obj["arquivo"])
|
||||||
|
dados_existentes.append(obj)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return processados, dados_existentes
|
||||||
|
|
||||||
|
|
||||||
|
def salvar_progresso(resultado):
|
||||||
|
with open(ARQUIVO_CHECKPOINT, "a", encoding="utf-8") as f:
|
||||||
|
json.dump(resultado, f, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def calcular_complexidade(projeto_yaml):
|
||||||
|
score = 0
|
||||||
|
W_INST_SYNTH, W_INST_SAMPLE = 3.0, 1.0
|
||||||
|
W_AUTOMATION, W_PATTERN, W_FX = 2.0, 0.5, 1.5
|
||||||
|
|
||||||
|
stats = {"num_automations": 0, "num_patterns": 0, "num_effects": 0, "num_synths": 0}
|
||||||
|
|
||||||
|
tracks = projeto_yaml.get("tracks", [])
|
||||||
|
for track in tracks:
|
||||||
|
instruments = track.get("instruments", [])
|
||||||
|
for inst in instruments:
|
||||||
|
nome_plugin = inst.get("plugin_name", "").lower()
|
||||||
|
nome_inst = inst.get("instrument_name", "").lower()
|
||||||
|
|
||||||
|
if "automation" in nome_inst or "automation" in nome_plugin:
|
||||||
|
score += W_AUTOMATION
|
||||||
|
stats["num_automations"] += 1
|
||||||
|
elif nome_plugin in [
|
||||||
|
"zynaddsubfx",
|
||||||
|
"monstro",
|
||||||
|
"lb302",
|
||||||
|
"watsyn",
|
||||||
|
"opyd",
|
||||||
|
"sfxr",
|
||||||
|
]:
|
||||||
|
score += W_INST_SYNTH
|
||||||
|
stats["num_synths"] += 1
|
||||||
|
else:
|
||||||
|
score += W_INST_SAMPLE
|
||||||
|
|
||||||
|
patterns = inst.get("patterns", [])
|
||||||
|
qtd_patterns = len(patterns)
|
||||||
|
score += qtd_patterns * W_PATTERN
|
||||||
|
stats["num_patterns"] += qtd_patterns
|
||||||
|
|
||||||
|
fxchain = inst.get("fxchain", {})
|
||||||
|
if str(fxchain.get("enabled")) == "1":
|
||||||
|
num_fx = int(fxchain.get("numofeffects", 0))
|
||||||
|
score += num_fx * W_FX
|
||||||
|
stats["num_effects"] += num_fx
|
||||||
|
|
||||||
|
if score <= 15:
|
||||||
|
estrelas = 1
|
||||||
|
elif score <= 40:
|
||||||
|
estrelas = 2
|
||||||
|
elif score <= 80:
|
||||||
|
estrelas = 3
|
||||||
|
elif score <= 120:
|
||||||
|
estrelas = 4
|
||||||
|
else:
|
||||||
|
estrelas = 5
|
||||||
|
|
||||||
|
return {"score_total": round(score, 2), "estrelas": estrelas, "detalhes": stats}
|
||||||
|
|
||||||
|
|
||||||
|
# ================= NÚCLEO DE ANÁLISE =================
|
||||||
|
|
||||||
|
def detectar_estrutura(audio_vec, sample_rate, duration):
|
||||||
|
try:
|
||||||
|
if duration < 30:
|
||||||
|
return [0.0, round(duration, 2)]
|
||||||
|
|
||||||
|
frame_size = 2048
|
||||||
|
hop_size = 512
|
||||||
|
|
||||||
|
rms_algo = es.RMS()
|
||||||
|
flux_algo = es.Flux()
|
||||||
|
w = es.Windowing(type="hann")
|
||||||
|
spec = es.Spectrum()
|
||||||
|
|
||||||
|
rms_curve = []
|
||||||
|
flux_curve = []
|
||||||
|
|
||||||
|
for frame in es.FrameGenerator(
|
||||||
|
audio_vec, frameSize=frame_size, hopSize=hop_size
|
||||||
|
):
|
||||||
|
s = spec(w(frame))
|
||||||
|
rms_curve.append(rms_algo(frame))
|
||||||
|
flux_curve.append(flux_algo(s))
|
||||||
|
|
||||||
|
min_len = min(len(rms_curve), len(flux_curve))
|
||||||
|
rms_curve = np.array(rms_curve[:min_len])
|
||||||
|
flux_curve = np.array(flux_curve[:min_len])
|
||||||
|
|
||||||
|
if np.max(rms_curve) > 0:
|
||||||
|
rms_curve = rms_curve / np.max(rms_curve)
|
||||||
|
if np.max(flux_curve) > 0:
|
||||||
|
flux_curve = flux_curve / np.max(flux_curve)
|
||||||
|
|
||||||
|
novelty = rms_curve * 0.5 + flux_curve * 0.5
|
||||||
|
segundos_por_frame = hop_size / sample_rate
|
||||||
|
|
||||||
|
pontos_mudanca = [0.0]
|
||||||
|
window_frames = int(10 / segundos_por_frame)
|
||||||
|
step_frames = int(5 / segundos_por_frame)
|
||||||
|
total_frames = len(novelty)
|
||||||
|
|
||||||
|
for i in range(0, total_frames - window_frames, step_frames):
|
||||||
|
janela = novelty[i : i + window_frames]
|
||||||
|
if np.std(janela) > 0.1:
|
||||||
|
pico_idx = i + np.argmax(janela)
|
||||||
|
tempo = round(pico_idx * segundos_por_frame, 2)
|
||||||
|
if tempo - pontos_mudanca[-1] > 15:
|
||||||
|
pontos_mudanca.append(tempo)
|
||||||
|
|
||||||
|
pontos_mudanca.append(round(duration, 2))
|
||||||
|
return pontos_mudanca
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Erro estrutura nova: {e}")
|
||||||
|
return [0.0, round(duration, 2)]
|
||||||
|
|
||||||
|
|
||||||
|
def analisar_faixa(caminho_arquivo, embedding_model, classifier_model, classes_raw):
|
||||||
|
# --- AUDITORIA: INÍCIO DO TIMER ---
|
||||||
|
inicio_timer = time.perf_counter()
|
||||||
|
|
||||||
|
tamanho_arquivo = os.path.getsize(caminho_arquivo)
|
||||||
|
pode_processar, motivo = verificar_memoria_segura(tamanho_arquivo)
|
||||||
|
|
||||||
|
if not pode_processar:
|
||||||
|
logging.warning(f"SKIPPED {os.path.basename(caminho_arquivo)}: {motivo}")
|
||||||
|
return None, 0, 0
|
||||||
|
|
||||||
|
nome_arquivo = os.path.basename(caminho_arquivo)
|
||||||
|
chave_busca = normalizar_chave(nome_arquivo)
|
||||||
|
dados_humano = DATABASE_YAML.get(chave_busca)
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
"arquivo": nome_arquivo,
|
||||||
|
"caminho": caminho_arquivo,
|
||||||
|
"dados_projeto": {},
|
||||||
|
"analise_tecnica": {},
|
||||||
|
"analise_ia": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if dados_humano:
|
||||||
|
metadata["dados_projeto"] = {
|
||||||
|
"titulo": dados_humano.get("original_title") or dados_humano.get("file"),
|
||||||
|
"bpm_original": dados_humano.get("bpm"),
|
||||||
|
"tags_yaml": dados_humano.get("tags"),
|
||||||
|
"autor": dados_humano.get("author", "Desconhecido"),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
metadata["analise_tecnica"]["complexidade"] = {
|
||||||
|
"score_total": 0,
|
||||||
|
"estrelas": 0,
|
||||||
|
"detalhes": "YAML não encontrado",
|
||||||
|
}
|
||||||
|
|
||||||
|
duracao_real = 0.0
|
||||||
|
|
||||||
|
# 1. ANÁLISE TÉCNICA E EXTRAÇÃO ÚNICA (HQ 44.1kHz)
|
||||||
|
try:
|
||||||
|
# Lê do HD APENAS UMA VEZ
|
||||||
|
loader_hq = es.MonoLoader(filename=caminho_arquivo, sampleRate=44100)
|
||||||
|
audio_hq = loader_hq()
|
||||||
|
|
||||||
|
if len(audio_hq) > (MAX_DURACAO_ANALISE * 44100):
|
||||||
|
audio_hq = audio_hq[: int(MAX_DURACAO_ANALISE * 44100)]
|
||||||
|
|
||||||
|
duracao_real = len(audio_hq) / 44100.0
|
||||||
|
audio_hq_vec = essentia.array(audio_hq)
|
||||||
|
|
||||||
|
rhythm = es.RhythmExtractor2013(method="multifeature")
|
||||||
|
bpm, _, conf, _, _ = rhythm(audio_hq_vec)
|
||||||
|
|
||||||
|
key_ex = es.KeyExtractor()
|
||||||
|
key, scale, _ = key_ex(audio_hq_vec)
|
||||||
|
|
||||||
|
loudness = es.Loudness()(audio_hq_vec)
|
||||||
|
estrutura = detectar_estrutura(audio_hq_vec, 44100, duracao_real)
|
||||||
|
|
||||||
|
bpm_final = round(bpm, 1)
|
||||||
|
origem = "algoritmo"
|
||||||
|
if dados_humano and dados_humano.get("bpm"):
|
||||||
|
try:
|
||||||
|
bpm_str = str(dados_humano.get("bpm")).replace("'", "").replace('"', "")
|
||||||
|
b = float(bpm_str)
|
||||||
|
if b > 0:
|
||||||
|
bpm_final = b
|
||||||
|
origem = "yaml (humano)"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
metadata["analise_tecnica"].update(
|
||||||
|
{
|
||||||
|
"bpm": bpm_final,
|
||||||
|
"origem_bpm": origem,
|
||||||
|
"tom": key,
|
||||||
|
"escala": scale,
|
||||||
|
"intensidade_db": round(loudness, 2),
|
||||||
|
"estrutura_segundos": estrutura,
|
||||||
|
"duracao_detectada": round(duracao_real, 2),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
metadata["analise_tecnica"]["complexidade"] = (
|
||||||
|
calcular_complexidade(dados_humano) if dados_humano else None
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Erro math {nome_arquivo}: {e}")
|
||||||
|
metadata["analise_tecnica"]["erro"] = str(e)
|
||||||
|
return metadata, 0, 0
|
||||||
|
|
||||||
|
# 2. ANÁLISE IA (LQ 16kHz via Resample da RAM - Resolve Leitura Dupla de HD)
|
||||||
|
if embedding_model and classifier_model and classes_raw:
|
||||||
|
try:
|
||||||
|
resample_algo = es.Resample(inputSampleRate=44100, outputSampleRate=16000)
|
||||||
|
audio_lq_vec = resample_algo(audio_hq_vec)
|
||||||
|
|
||||||
|
embeddings = embedding_model(audio_lq_vec)
|
||||||
|
predictions = classifier_model(embeddings)
|
||||||
|
|
||||||
|
avg_preds = np.mean(predictions, axis=0)
|
||||||
|
top_indices = np.argsort(avg_preds)[::-1][:10]
|
||||||
|
|
||||||
|
tags_detectadas = []
|
||||||
|
genero_pai = "Unknown"
|
||||||
|
|
||||||
|
for i, idx in enumerate(top_indices):
|
||||||
|
score = float(avg_preds[idx])
|
||||||
|
if score < 0.05:
|
||||||
|
continue
|
||||||
|
|
||||||
|
nome_full = classes_raw[idx]
|
||||||
|
partes = nome_full.split("---")
|
||||||
|
tag_limpa = partes[-1]
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
genero_pai = partes[0] if len(partes) > 0 else tag_limpa
|
||||||
|
|
||||||
|
tags_detectadas.append({"tag": tag_limpa, "score": round(score, 3)})
|
||||||
|
|
||||||
|
metadata["analise_ia"] = {
|
||||||
|
"genero_macro": genero_pai,
|
||||||
|
"estilo_principal": tags_detectadas[0]["tag"] if tags_detectadas else "Unknown",
|
||||||
|
"nuvem_tags": tags_detectadas,
|
||||||
|
}
|
||||||
|
|
||||||
|
del audio_lq_vec
|
||||||
|
del embeddings
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
msg = f"Erro IA Pipeline: {str(e)}"
|
||||||
|
logging.error(msg)
|
||||||
|
metadata["analise_ia"] = {"erro": msg}
|
||||||
|
else:
|
||||||
|
metadata["analise_ia"] = {"status": "Modelos nao carregados"}
|
||||||
|
|
||||||
|
# Limpeza Limpa
|
||||||
|
del audio_hq
|
||||||
|
del audio_hq_vec
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
tempo_gasto = time.perf_counter() - inicio_timer
|
||||||
|
return metadata, duracao_real, tempo_gasto
|
||||||
|
|
||||||
|
|
||||||
|
# ================= MAIN (Multiprocessing) =================
|
||||||
|
def worker_analise(caminho):
|
||||||
|
# Processa usando as instâncias dos modelos carregadas na thread mestre
|
||||||
|
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()
|
||||||
|
|
||||||
|
auditor = Auditoria(ARQUIVO_AUDITORIA)
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
total = len(a_fazer)
|
||||||
|
print(f"Total Arquivos: {len(todos)} | Novos a Fazer: {total}")
|
||||||
|
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)
|
||||||
|
|
||||||
|
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:
|
||||||
|
GLOBAL_CLASSES = json.load(f)["classes"]
|
||||||
|
|
||||||
|
print("[1/2] Carregando Extrator de Embeddings...")
|
||||||
|
if hasattr(es, "TensorflowPredictEffnetDiscogs"):
|
||||||
|
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")
|
||||||
|
print(" -> Embeddings carregados com sucesso.")
|
||||||
|
|
||||||
|
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:
|
||||||
|
print(f"ERRO CRÍTICO AO CARREGAR IA: {e}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("Arquivos de modelo incompletos.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if total == 0:
|
||||||
|
print("Tudo atualizado.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
|
||||||
|
# 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"):
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
tempo_total_sessao = time.time() - auditor.inicio_global
|
||||||
|
print("\n--- FIM DO PROCESSAMENTO ---")
|
||||||
|
print(f"Tempo Total: {str(timedelta(seconds=int(tempo_total_sessao)))}")
|
||||||
|
print("Gerando JSON final consolidado...")
|
||||||
|
|
||||||
|
with open(ARQUIVO_FINAL_JSON, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(lista_resultados, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"Dados JSON: {ARQUIVO_FINAL_JSON}")
|
||||||
|
print(f"Auditoria CSV: {ARQUIVO_AUDITORIA}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
import yaml
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import csv
|
||||||
|
import unicodedata
|
||||||
|
import time
|
||||||
|
import psutil
|
||||||
|
import glob # Import necessário para ler múltiplos arquivos
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# --- CONFIGURAÇÕES GERAIS ---
|
||||||
|
BASE_DIR = "/var/www/html/trens/src_mmpSearch"
|
||||||
|
LOG_DIR = os.path.join(BASE_DIR, "logs/classificacao_pedagogica")
|
||||||
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
TIMESTAMP = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
|
|
||||||
|
# Configuração de Log
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - CLASSIFICADOR - %(levelname)s - %(message)s",
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler(os.path.join(LOG_DIR, f"log_pedagogico_{TIMESTAMP}.log")),
|
||||||
|
logging.StreamHandler(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- ARQUIVOS DE ENTRADA/SAÍDA ---
|
||||||
|
def encontrar_caminho(nome_arquivo):
|
||||||
|
"""Procura o arquivo em locais comuns do projeto."""
|
||||||
|
candidatos = [
|
||||||
|
os.path.join(BASE_DIR, "saida_analises", nome_arquivo),
|
||||||
|
os.path.join(BASE_DIR, "data", nome_arquivo),
|
||||||
|
os.path.join(BASE_DIR, nome_arquivo),
|
||||||
|
nome_arquivo,
|
||||||
|
]
|
||||||
|
for c in candidatos:
|
||||||
|
if os.path.exists(c):
|
||||||
|
return c
|
||||||
|
return nome_arquivo
|
||||||
|
|
||||||
|
|
||||||
|
# --- MUDANÇA AQUI: Aponta para a pasta dos lotes ---
|
||||||
|
PASTA_LOTES_YAML = os.path.join(BASE_DIR, "lotes_yaml")
|
||||||
|
# ARQUIVO_YAML = "all.yml" <-- Removido/Comentado
|
||||||
|
|
||||||
|
ARQUIVO_AUDIO_INPUT = encontrar_caminho("db_final_completo.json")
|
||||||
|
ARQUIVO_FINAL_OUTPUT = os.path.join(
|
||||||
|
BASE_DIR, "saida_analises", "db_projetos_classificados.json"
|
||||||
|
)
|
||||||
|
ARQUIVO_AUDITORIA = os.path.join(
|
||||||
|
BASE_DIR, "saida_analises", "audit_classificacao_pedagogica.csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- LISTAS DE REFERÊNCIA ---
|
||||||
|
PLUGINS_NATIVOS = [
|
||||||
|
"tripleoscillator",
|
||||||
|
"zynaddsubfx",
|
||||||
|
"sfxr",
|
||||||
|
"organic",
|
||||||
|
"audiofileprocessor",
|
||||||
|
"lb302",
|
||||||
|
"kicker",
|
||||||
|
"watsyn",
|
||||||
|
"bitinvader",
|
||||||
|
"freeboy",
|
||||||
|
"mallets",
|
||||||
|
"vibed",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# --- CLASSE DE AUDITORIA DE PERFORMANCE ---
|
||||||
|
class AuditoriaPerformance:
|
||||||
|
def __init__(self):
|
||||||
|
self.inicio_global = time.time()
|
||||||
|
self.process = psutil.Process(os.getpid())
|
||||||
|
self.marcos = [1, 10, 100, 500, 1000, 2000, 5000]
|
||||||
|
|
||||||
|
def get_metricas(self):
|
||||||
|
"""Retorna uso atual de RAM (MB) e CPU (%)"""
|
||||||
|
try:
|
||||||
|
ram_mb = self.process.memory_info().rss / (1024 * 1024)
|
||||||
|
cpu_pct = self.process.cpu_percent(interval=None)
|
||||||
|
return round(ram_mb, 2), cpu_pct
|
||||||
|
except:
|
||||||
|
return 0.0, 0.0
|
||||||
|
|
||||||
|
def verificar_marco(self, contagem):
|
||||||
|
if contagem in self.marcos:
|
||||||
|
tempo_decorrido = time.time() - self.inicio_global
|
||||||
|
logging.info(
|
||||||
|
f"--- MARCO: {contagem} projetos processados em {str(timedelta(seconds=int(tempo_decorrido)))} ---"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- FUNÇÕES AUXILIARES ---
|
||||||
|
|
||||||
|
|
||||||
|
def normalizar_chave(texto):
|
||||||
|
if not texto:
|
||||||
|
return ""
|
||||||
|
s = str(texto)
|
||||||
|
if "." in s:
|
||||||
|
s = os.path.splitext(s)[0]
|
||||||
|
s = unicodedata.normalize("NFKD", s).encode("ASCII", "ignore").decode("ASCII")
|
||||||
|
return s.lower().replace(" ", "").replace("-", "").replace("_", "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def calcular_nivel_ponderado(
|
||||||
|
num_tracks, tem_automacao, usa_zyn, qtd_secoes_audio, fx_criativos
|
||||||
|
):
|
||||||
|
pontos = 0
|
||||||
|
|
||||||
|
# 1. Volume
|
||||||
|
if num_tracks >= 8:
|
||||||
|
pontos += 1
|
||||||
|
if num_tracks >= 16:
|
||||||
|
pontos += 2
|
||||||
|
if num_tracks >= 32:
|
||||||
|
pontos += 3
|
||||||
|
if num_tracks >= 64:
|
||||||
|
pontos += 4
|
||||||
|
elif num_tracks >= 128:
|
||||||
|
pontos += 5
|
||||||
|
|
||||||
|
# 2. Técnica
|
||||||
|
if tem_automacao:
|
||||||
|
pontos += 2
|
||||||
|
if usa_zyn:
|
||||||
|
pontos += 1.5
|
||||||
|
if fx_criativos:
|
||||||
|
pontos += 1
|
||||||
|
|
||||||
|
# 3. Estrutura (Áudio)
|
||||||
|
if qtd_secoes_audio >= 3:
|
||||||
|
pontos += 1.5
|
||||||
|
elif qtd_secoes_audio >= 5:
|
||||||
|
pontos += 3
|
||||||
|
elif qtd_secoes_audio >= 10:
|
||||||
|
pontos += 5
|
||||||
|
|
||||||
|
if pontos < 3:
|
||||||
|
return "Iniciante"
|
||||||
|
if pontos < 6:
|
||||||
|
return "Intermediário"
|
||||||
|
return "Avançado"
|
||||||
|
|
||||||
|
|
||||||
|
def analisar_tags_pedagogicas(tracks, bpm, qtd_secoes):
|
||||||
|
tags = []
|
||||||
|
tem_kicker = False
|
||||||
|
tem_zyn = False
|
||||||
|
tem_automacao = False
|
||||||
|
tem_fx_chain = False
|
||||||
|
rolagem_trap = False
|
||||||
|
|
||||||
|
for t in tracks:
|
||||||
|
inst = t.get("instrumenttrack")
|
||||||
|
if not inst:
|
||||||
|
continue
|
||||||
|
|
||||||
|
nome = inst.get("name", "").lower()
|
||||||
|
plugin = inst.get("plugin_name", "").lower()
|
||||||
|
|
||||||
|
if plugin == "kicker":
|
||||||
|
tem_kicker = True
|
||||||
|
if plugin == "zynaddsubfx":
|
||||||
|
tem_zyn = True
|
||||||
|
if "automation" in str(inst).lower():
|
||||||
|
tem_automacao = True
|
||||||
|
|
||||||
|
fx = inst.get("fxchain", {})
|
||||||
|
if fx and int(fx.get("numofeffects", 0)) > 0:
|
||||||
|
tem_fx_chain = True
|
||||||
|
|
||||||
|
patterns = inst.get("patterns", [])
|
||||||
|
if patterns:
|
||||||
|
for pat in patterns:
|
||||||
|
steps = pat.get("steps", [])
|
||||||
|
consecutivos = 0
|
||||||
|
for step in steps:
|
||||||
|
val = 1 if step else 0
|
||||||
|
consecutivos = (consecutivos + 1) if val else 0
|
||||||
|
if consecutivos >= 3 and ("hat" in nome):
|
||||||
|
rolagem_trap = True
|
||||||
|
|
||||||
|
if tem_kicker:
|
||||||
|
tags.append("Síntese de Bateria")
|
||||||
|
if tem_zyn:
|
||||||
|
tags.append("Design de Som Avançado")
|
||||||
|
if tem_automacao:
|
||||||
|
tags.append("Automação")
|
||||||
|
if tem_fx_chain:
|
||||||
|
tags.append("Mixagem com FX")
|
||||||
|
|
||||||
|
if rolagem_trap and bpm > 115:
|
||||||
|
tags.append("Trap Rhythm")
|
||||||
|
if bpm < 100 and not rolagem_trap:
|
||||||
|
tags.append("Boombap Groove")
|
||||||
|
|
||||||
|
if qtd_secoes >= 4:
|
||||||
|
tags.append("Arranjo Completo")
|
||||||
|
elif qtd_secoes <= 1:
|
||||||
|
tags.append("Loop Estático")
|
||||||
|
else:
|
||||||
|
tags.append("Estrutura Simples")
|
||||||
|
|
||||||
|
return tags, tem_automacao, tem_zyn, tem_fx_chain
|
||||||
|
|
||||||
|
|
||||||
|
# --- FUNÇÃO PARA CARREGAR LOTES YAML ---
|
||||||
|
def carregar_todos_yamls(pasta_lotes):
|
||||||
|
"""Lê todos os arquivos YAML da pasta especificada e retorna uma lista única."""
|
||||||
|
logging.info(f"Buscando arquivos YAML em: {pasta_lotes}")
|
||||||
|
|
||||||
|
padrao = os.path.join(pasta_lotes, "*.yml")
|
||||||
|
arquivos = sorted(glob.glob(padrao))
|
||||||
|
|
||||||
|
todos_projetos = []
|
||||||
|
|
||||||
|
if not arquivos:
|
||||||
|
logging.warning("Nenhum arquivo YAML encontrado na pasta de lotes.")
|
||||||
|
# Tenta fallback para o arquivo único antigo se existir
|
||||||
|
caminho_unico = encontrar_caminho("all.yml")
|
||||||
|
if os.path.exists(caminho_unico):
|
||||||
|
logging.info(f"Fallback: Carregando arquivo único {caminho_unico}")
|
||||||
|
with open(caminho_unico, "r", encoding="utf-8") as f:
|
||||||
|
return yaml.safe_load(f)
|
||||||
|
return []
|
||||||
|
|
||||||
|
for arq in arquivos:
|
||||||
|
try:
|
||||||
|
logging.info(f"Carregando lote: {os.path.basename(arq)}")
|
||||||
|
with open(arq, "r", encoding="utf-8") as f:
|
||||||
|
lote = yaml.safe_load(f)
|
||||||
|
if lote:
|
||||||
|
# Garante que seja uma lista
|
||||||
|
if isinstance(lote, list):
|
||||||
|
todos_projetos.extend(lote)
|
||||||
|
elif isinstance(lote, dict):
|
||||||
|
todos_projetos.append(lote)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Erro ao ler arquivo {arq}: {e}")
|
||||||
|
|
||||||
|
logging.info(f"Total de projetos carregados dos lotes: {len(todos_projetos)}")
|
||||||
|
return todos_projetos
|
||||||
|
|
||||||
|
|
||||||
|
# --- MAIN ---
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.info("--- INICIANDO CLASSIFICADOR PEDAGÓGICO (COM AUDITORIA E LOTES) ---")
|
||||||
|
|
||||||
|
auditor = AuditoriaPerformance()
|
||||||
|
|
||||||
|
# 1. Carregar YAML (Adaptado para Lotes)
|
||||||
|
if not os.path.exists(PASTA_LOTES_YAML):
|
||||||
|
logging.warning(f"Pasta de lotes não encontrada: {PASTA_LOTES_YAML}")
|
||||||
|
# Tenta criar se não existir (embora deva existir com arquivos)
|
||||||
|
# os.makedirs(PASTA_LOTES_YAML, exist_ok=True)
|
||||||
|
|
||||||
|
dados_projetos = carregar_todos_yamls(PASTA_LOTES_YAML)
|
||||||
|
|
||||||
|
if not dados_projetos:
|
||||||
|
logging.critical("Nenhum dado de projeto carregado. Encerrando.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. Carregar JSON de Áudio
|
||||||
|
lookup_audio = {}
|
||||||
|
total_audio_carregado = 0
|
||||||
|
|
||||||
|
if os.path.exists(ARQUIVO_AUDIO_INPUT):
|
||||||
|
logging.info(f"Lendo Análise de Áudio: {ARQUIVO_AUDIO_INPUT}")
|
||||||
|
with open(ARQUIVO_AUDIO_INPUT, "r", encoding="utf-8") as f:
|
||||||
|
lista_audio = json.load(f)
|
||||||
|
for item in lista_audio:
|
||||||
|
chave = normalizar_chave(item.get("arquivo", ""))
|
||||||
|
if chave:
|
||||||
|
lookup_audio[chave] = item
|
||||||
|
total_audio_carregado = len(lista_audio)
|
||||||
|
else:
|
||||||
|
logging.warning(f"Arquivo de áudio {ARQUIVO_AUDIO_INPUT} não encontrado.")
|
||||||
|
|
||||||
|
db_final = []
|
||||||
|
estatisticas = {"Iniciante": 0, "Intermediário": 0, "Avançado": 0, "Total": 0}
|
||||||
|
|
||||||
|
# --- PREPARAR CSV DE AUDITORIA ---
|
||||||
|
with open(ARQUIVO_AUDITORIA, "w", newline="", encoding="utf-8") as csvfile:
|
||||||
|
writer = csv.writer(csvfile)
|
||||||
|
# Cabeçalho expandido com métricas de performance
|
||||||
|
writer.writerow(
|
||||||
|
[
|
||||||
|
"Timestamp",
|
||||||
|
"ID_Projeto",
|
||||||
|
"Audio_Encontrado",
|
||||||
|
"Qtd_Secoes",
|
||||||
|
"Nivel",
|
||||||
|
"Genero_Final",
|
||||||
|
"Tempo_Proc_ms",
|
||||||
|
"RAM_Uso_MB",
|
||||||
|
"Tags",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(f"Processando {len(dados_projetos)} projetos...")
|
||||||
|
|
||||||
|
contagem_processados = 0
|
||||||
|
|
||||||
|
for proj in dados_projetos:
|
||||||
|
if not proj or not isinstance(proj, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# --- INÍCIO TIMER INDIVIDUAL ---
|
||||||
|
inicio_item = time.perf_counter()
|
||||||
|
|
||||||
|
nome_arquivo = proj.get("file", "")
|
||||||
|
if not nome_arquivo:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Lógica de Classificação
|
||||||
|
chave_proj = normalizar_chave(nome_arquivo)
|
||||||
|
dados_audio = lookup_audio.get(chave_proj, {})
|
||||||
|
|
||||||
|
analise_tec = dados_audio.get("analise_tecnica", {})
|
||||||
|
analise_ia = dados_audio.get("analise_ia", {})
|
||||||
|
|
||||||
|
raw_bpm = proj.get("bpm", 120)
|
||||||
|
try:
|
||||||
|
bpm_projeto = float(raw_bpm)
|
||||||
|
except:
|
||||||
|
bpm_projeto = 120.0
|
||||||
|
|
||||||
|
est_seg = analise_tec.get("estrutura_segundos", [])
|
||||||
|
qtd_secoes = len(est_seg) if isinstance(est_seg, list) else 1
|
||||||
|
|
||||||
|
tracks = proj.get("tracks", [])
|
||||||
|
tags, tem_auto, tem_zyn, tem_fx = analisar_tags_pedagogicas(
|
||||||
|
tracks, bpm_projeto, qtd_secoes
|
||||||
|
)
|
||||||
|
|
||||||
|
plugins_usados = set()
|
||||||
|
usa_vst_externo = False
|
||||||
|
for t in tracks:
|
||||||
|
inst_track = t.get("instrumenttrack", {})
|
||||||
|
if not inst_track:
|
||||||
|
continue
|
||||||
|
p = inst_track.get("plugin_name", "")
|
||||||
|
if p:
|
||||||
|
p_norm = p.lower()
|
||||||
|
plugins_usados.add(p_norm)
|
||||||
|
if p_norm not in PLUGINS_NATIVOS:
|
||||||
|
usa_vst_externo = True
|
||||||
|
|
||||||
|
nivel = calcular_nivel_ponderado(
|
||||||
|
len(tracks), tem_auto, tem_zyn, qtd_secoes, tem_fx
|
||||||
|
)
|
||||||
|
estatisticas[nivel] += 1
|
||||||
|
estatisticas["Total"] += 1
|
||||||
|
|
||||||
|
genero = "Desconhecido"
|
||||||
|
if analise_ia and "estilo_principal" in analise_ia:
|
||||||
|
genero = analise_ia["estilo_principal"]
|
||||||
|
elif (
|
||||||
|
"genero_macro" in analise_ia
|
||||||
|
and analise_ia["genero_macro"] != "Unknown"
|
||||||
|
):
|
||||||
|
genero = analise_ia["genero_macro"]
|
||||||
|
|
||||||
|
if genero == "Desconhecido" or genero == "Unknown":
|
||||||
|
if "Trap Rhythm" in tags:
|
||||||
|
genero = "Trap (Rule-based)"
|
||||||
|
elif bpm_projeto > 120:
|
||||||
|
genero = "Electronic (Rule-based)"
|
||||||
|
else:
|
||||||
|
genero = "HipHop/Downtempo (Rule-based)"
|
||||||
|
|
||||||
|
# Objeto Final
|
||||||
|
item = {
|
||||||
|
"id": nome_arquivo,
|
||||||
|
"titulo": proj.get("original_title", nome_arquivo),
|
||||||
|
"match_audio": bool(dados_audio),
|
||||||
|
"pedagogia": {
|
||||||
|
"nivel": nivel,
|
||||||
|
"tags_aprendizado": tags,
|
||||||
|
"plugins_principais": list(plugins_usados),
|
||||||
|
"requer_vst_externo": usa_vst_externo,
|
||||||
|
},
|
||||||
|
"tecnica": {
|
||||||
|
"bpm": bpm_projeto,
|
||||||
|
"tom": analise_tec.get("tom", "N/A"),
|
||||||
|
"escala": analise_tec.get("escala", "N/A"),
|
||||||
|
"estrutura_qtd_secoes": qtd_secoes,
|
||||||
|
"intensidade_media": analise_tec.get("intensidade_db", "N/A"),
|
||||||
|
},
|
||||||
|
"classificacao": {
|
||||||
|
"genero": genero,
|
||||||
|
"mood": "Energético"
|
||||||
|
if analise_tec.get("intensidade_db", -20) > -10
|
||||||
|
else "Suave",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
db_final.append(item)
|
||||||
|
|
||||||
|
# --- FIM TIMER INDIVIDUAL ---
|
||||||
|
tempo_ms = (time.perf_counter() - inicio_item) * 1000
|
||||||
|
ram_atual, _ = auditor.get_metricas()
|
||||||
|
ts_agora = datetime.now().strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
# Registra Auditoria com Performance
|
||||||
|
writer.writerow(
|
||||||
|
[
|
||||||
|
ts_agora,
|
||||||
|
nome_arquivo,
|
||||||
|
"SIM" if dados_audio else "NAO",
|
||||||
|
qtd_secoes,
|
||||||
|
nivel,
|
||||||
|
genero,
|
||||||
|
f"{tempo_ms:.3f}", # Tempo em milissegundos com 3 casas
|
||||||
|
f"{ram_atual:.2f}", # RAM em MB
|
||||||
|
"|".join(tags),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
contagem_processados += 1
|
||||||
|
auditor.verificar_marco(contagem_processados)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"Erro ao processar projeto '{proj.get('file', 'unknown')}': {e}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Salvar Resultado JSON
|
||||||
|
logging.info(f"Salvando JSON Final: {ARQUIVO_FINAL_OUTPUT}")
|
||||||
|
with open(ARQUIVO_FINAL_OUTPUT, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(db_final, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
tempo_total = time.time() - auditor.inicio_global
|
||||||
|
|
||||||
|
logging.info("--- RELATÓRIO FINAL ---")
|
||||||
|
logging.info(f"Tempo Total de Execução: {str(timedelta(seconds=int(tempo_total)))}")
|
||||||
|
logging.info(f"Total Processado: {estatisticas['Total']}")
|
||||||
|
logging.info(f"Iniciantes: {estatisticas['Iniciante']}")
|
||||||
|
logging.info(f"Intermediários: {estatisticas['Intermediário']}")
|
||||||
|
logging.info(f"Avançados: {estatisticas['Avançado']}")
|
||||||
|
logging.info(
|
||||||
|
f"Taxa de Casamento com Áudio: {len(lookup_audio)}/{total_audio_carregado}"
|
||||||
|
)
|
||||||
|
logging.info(f"Auditoria salva em: {ARQUIVO_AUDITORIA}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
import yaml
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# --- CONFIGURAÇÕES DE LOG ---
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - CLASSIFICADOR - %(levelname)s - %(message)s",
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler("classificacao_pedagogica.log"),
|
||||||
|
logging.StreamHandler(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- CONFIGURAÇÕES ---
|
||||||
|
ARQUIVO_YAML = "all.yml"
|
||||||
|
ARQUIVO_AUDIO = "analise_audio.json"
|
||||||
|
ARQUIVO_FINAL = "db_projetos_classificados.json"
|
||||||
|
|
||||||
|
PLUGINS_NATIVOS = [
|
||||||
|
"tripleoscillator",
|
||||||
|
"zynaddsubfx",
|
||||||
|
"sfxr",
|
||||||
|
"organic",
|
||||||
|
"audiofileprocessor",
|
||||||
|
"lb302",
|
||||||
|
"kicker",
|
||||||
|
"watsyn",
|
||||||
|
"bitinvader",
|
||||||
|
"freeboy",
|
||||||
|
"mallets",
|
||||||
|
"vibed",
|
||||||
|
]
|
||||||
|
|
||||||
|
# --- LÓGICA PEDAGÓGICA AVANÇADA ---
|
||||||
|
|
||||||
|
|
||||||
|
def calcular_nivel_ponderado(
|
||||||
|
num_tracks, tem_automacao, plugins_complexos, qtd_secoes_audio, fx_criativos
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Calcula nível pedagógico (Iniciante, Intermediário, Avançado)
|
||||||
|
baseado em esforço de engenharia e complexidade musical.
|
||||||
|
"""
|
||||||
|
pontos = 0
|
||||||
|
|
||||||
|
# 1. Volume de Trabalho
|
||||||
|
if num_tracks >= 8:
|
||||||
|
pontos += 1
|
||||||
|
if num_tracks >= 16:
|
||||||
|
pontos += 1
|
||||||
|
|
||||||
|
# 2. Profundidade Técnica
|
||||||
|
if tem_automacao:
|
||||||
|
pontos += 2 # Automação é sinal forte de polimento
|
||||||
|
if plugins_complexos:
|
||||||
|
pontos += 1.5 # Usar ZynAddSubFx requer estudo
|
||||||
|
if fx_criativos:
|
||||||
|
pontos += 1 # Usar efeitos além do básico
|
||||||
|
|
||||||
|
# 3. Estrutura Musical (Vinda do Essentia)
|
||||||
|
# Se o áudio tem > 3 seções detectadas, há arranjo (não é só loop)
|
||||||
|
if qtd_secoes_audio >= 3:
|
||||||
|
pontos += 2
|
||||||
|
elif qtd_secoes_audio >= 5:
|
||||||
|
pontos += 3
|
||||||
|
|
||||||
|
# Classificação
|
||||||
|
if pontos < 3:
|
||||||
|
return "Iniciante"
|
||||||
|
if pontos < 6:
|
||||||
|
return "Intermediário"
|
||||||
|
return "Avançado"
|
||||||
|
|
||||||
|
|
||||||
|
def analisar_tags_pedagogicas(tracks, bpm, qtd_secoes, tom, escala):
|
||||||
|
tags = []
|
||||||
|
|
||||||
|
# Flags de estado técnico
|
||||||
|
tem_zyn = False
|
||||||
|
tem_automacao = False
|
||||||
|
tem_fx_chain = False
|
||||||
|
qtd_nativos = 0
|
||||||
|
qtd_vst_externos = 0
|
||||||
|
tem_audio_processor = False
|
||||||
|
|
||||||
|
for t in tracks:
|
||||||
|
inst = t.get("instrumenttrack")
|
||||||
|
if not inst:
|
||||||
|
continue
|
||||||
|
|
||||||
|
plugin = inst.get("plugin_name", "").lower()
|
||||||
|
|
||||||
|
# Mapeamento de Plugins e Dependências
|
||||||
|
if plugin in PLUGINS_NATIVOS:
|
||||||
|
qtd_nativos += 1
|
||||||
|
elif plugin == "vestige":
|
||||||
|
qtd_vst_externos += 1
|
||||||
|
|
||||||
|
if plugin == "zynaddsubfx":
|
||||||
|
tem_zyn = True
|
||||||
|
|
||||||
|
if plugin == "audiofileprocessor":
|
||||||
|
tem_audio_processor = True
|
||||||
|
|
||||||
|
if "automation" in str(inst).lower():
|
||||||
|
tem_automacao = True
|
||||||
|
|
||||||
|
fx = inst.get("fxchain", {})
|
||||||
|
if fx and int(fx.get("numofeffects", 0)) > 0:
|
||||||
|
tem_fx_chain = True
|
||||||
|
|
||||||
|
# --- 1. Estética da Escassez e Modulação Marginal ---
|
||||||
|
if qtd_nativos > 0 and qtd_vst_externos == 0:
|
||||||
|
tags.append("Estética da Escassez: Síntese Nativa")
|
||||||
|
|
||||||
|
if tem_automacao and qtd_vst_externos == 0:
|
||||||
|
tags.append("Design de Som Marginal (Automação como Timbre)")
|
||||||
|
|
||||||
|
# --- 2. Taxonomia de Bloom (Carga Cognitiva) ---
|
||||||
|
if tem_zyn or tem_fx_chain:
|
||||||
|
tags.append("Bloom: Criação (Sound Design Complexo)")
|
||||||
|
elif tem_audio_processor and not tem_automacao:
|
||||||
|
tags.append("Bloom: Aplicação (Sample Inalterado)")
|
||||||
|
|
||||||
|
# --- 3. Fluência Estrutural (Macroforma Musical) ---
|
||||||
|
if qtd_secoes >= 3:
|
||||||
|
tags.append("Fluência Estrutural: Arranjo Completo (Macroforma)")
|
||||||
|
else:
|
||||||
|
tags.append("Fluência Estrutural: Loop Estático")
|
||||||
|
|
||||||
|
# --- 4. Acessibilidade Ergonômica (Música de Computador) ---
|
||||||
|
if tom == "A" and escala == "minor":
|
||||||
|
tags.append("Ergonomia Autodidata (Teclado QWERTY / Lá Menor)")
|
||||||
|
|
||||||
|
# --- 5. Herança Cultural / Andamento ---
|
||||||
|
if 80 <= bpm <= 95:
|
||||||
|
tags.append("Cadência Clássica (Boom Bap)")
|
||||||
|
elif bpm >= 130:
|
||||||
|
tags.append("Estética Trap/Funk/Eletrônica / Alta Energia")
|
||||||
|
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.info("Iniciando Classificador Mestre...")
|
||||||
|
|
||||||
|
# 1. Carregar YAML
|
||||||
|
if not os.path.exists(ARQUIVO_YAML):
|
||||||
|
logging.critical(f"{ARQUIVO_YAML} não encontrado.")
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(ARQUIVO_YAML, "r", encoding="utf-8") as f:
|
||||||
|
dados_projetos = yaml.safe_load(f)
|
||||||
|
|
||||||
|
# 2. Carregar JSON de Áudio
|
||||||
|
lookup_audio = {}
|
||||||
|
if os.path.exists(ARQUIVO_AUDIO):
|
||||||
|
with open(ARQUIVO_AUDIO, "r", encoding="utf-8") as f:
|
||||||
|
lista_audio = json.load(f)
|
||||||
|
for item in lista_audio:
|
||||||
|
# Normaliza chave de busca (nome do arquivo sem extensão)
|
||||||
|
nome_base = os.path.splitext(item["arquivo"])[0]
|
||||||
|
lookup_audio[nome_base] = item
|
||||||
|
else:
|
||||||
|
logging.warning(
|
||||||
|
"JSON de áudio não encontrado. Classificação será apenas estática."
|
||||||
|
)
|
||||||
|
|
||||||
|
db_final = []
|
||||||
|
|
||||||
|
logging.info(f"Processando {len(dados_projetos)} entradas do YAML...")
|
||||||
|
|
||||||
|
for proj in dados_projetos:
|
||||||
|
if not proj:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
nome_arquivo = proj.get("file", "")
|
||||||
|
if not nome_arquivo:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Tenta casar com dados de áudio
|
||||||
|
# Nota: Seu YAML usa nomes como 'trap-fyrebreak', o arquivo de audio deve ser 'trap-fyrebreak.wav'
|
||||||
|
# O lookup_audio já está sem extensão.
|
||||||
|
dados_audio = lookup_audio.get(nome_arquivo, {})
|
||||||
|
analise_tec = dados_audio.get("analise_tecnica", {})
|
||||||
|
analise_ia = dados_audio.get("analise_ia", {})
|
||||||
|
|
||||||
|
# Dados Técnicos
|
||||||
|
raw_bpm = proj.get("bpm", 120)
|
||||||
|
bpm_projeto = (
|
||||||
|
float(raw_bpm) if str(raw_bpm).replace(".", "", 1).isdigit() else 120.0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Feature nova: Quantidade de seções (Intro, Verso...)
|
||||||
|
qtd_secoes = analise_tec.get("qtd_secoes_detectadas", 1)
|
||||||
|
|
||||||
|
# Análise Estática (Tracks e Plugins)
|
||||||
|
tracks = proj.get("tracks", [])
|
||||||
|
tags, tem_auto, tem_zyn, tem_fx = analisar_tags_pedagogicas(
|
||||||
|
tracks=proj.get("tracks", []),
|
||||||
|
bpm=bpm_projeto,
|
||||||
|
qtd_secoes=qtd_secoes,
|
||||||
|
tom=analise_tec.get("tom", "N/A"),
|
||||||
|
escala=analise_tec.get("escala", "N/A"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verifica plugins externos
|
||||||
|
plugins_usados = set()
|
||||||
|
usa_vst_externo = False
|
||||||
|
for t in tracks:
|
||||||
|
p = t.get("instrumenttrack", {}).get("plugin_name", "")
|
||||||
|
if p:
|
||||||
|
plugins_usados.add(p)
|
||||||
|
if p.lower() not in PLUGINS_NATIVOS:
|
||||||
|
usa_vst_externo = True
|
||||||
|
|
||||||
|
# Cálculo de Nível (Nova Lógica)
|
||||||
|
nivel = calcular_nivel_ponderado(
|
||||||
|
len(tracks), tem_auto, tem_zyn, qtd_secoes, tem_fx
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define Gênero (IA tem prioridade, depois regras manuais)
|
||||||
|
genero = analise_ia.get("genero_predito", "Desconhecido")
|
||||||
|
if genero == "Desconhecido" or analise_ia.get("confianca_genero", 0) < 0.4:
|
||||||
|
# Fallback manual simples
|
||||||
|
if "Trap Rhythm" in tags:
|
||||||
|
genero = "Trap"
|
||||||
|
elif bpm_projeto > 120:
|
||||||
|
genero = "Electronic"
|
||||||
|
else:
|
||||||
|
genero = "HipHop/Downtempo"
|
||||||
|
|
||||||
|
# Montagem do Objeto
|
||||||
|
item = {
|
||||||
|
"id": nome_arquivo,
|
||||||
|
"titulo": proj.get("original_title", nome_arquivo),
|
||||||
|
"pedagogia": {
|
||||||
|
"nivel": nivel,
|
||||||
|
"tags_aprendizado": tags,
|
||||||
|
"plugins_principais": list(plugins_usados),
|
||||||
|
"requer_vst_externo": usa_vst_externo,
|
||||||
|
},
|
||||||
|
"tecnica": {
|
||||||
|
"bpm": bpm_projeto,
|
||||||
|
"tom": analise_tec.get("tom", "N/A"),
|
||||||
|
"escala": analise_tec.get("escala", "N/A"),
|
||||||
|
"estrutura_detectada": f"{qtd_secoes} seções distintas",
|
||||||
|
},
|
||||||
|
"classificacao": {
|
||||||
|
"genero": genero,
|
||||||
|
"mood": "Energético"
|
||||||
|
if analise_tec.get("intensidade", -20) > -10
|
||||||
|
else "Suave",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
db_final.append(item)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Erro ao processar projeto '{proj.get('file')}': {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Salvar Resultado
|
||||||
|
with open(ARQUIVO_FINAL, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(db_final, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
logging.info(f"Sucesso! {len(db_final)} projetos classificados e salvos.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
{
|
||||||
|
"name": "EffnetDiscogs",
|
||||||
|
"type": "Music style classification and embeddings",
|
||||||
|
"link": "https://essentia.upf.edu/models/music-style-classification/discogs-effnet/discogs-effnet-bs64-1.pb",
|
||||||
|
"version": "1",
|
||||||
|
"description": "Prediction of the top-400 music styles in the Discogs-4M dataset (unreleased)",
|
||||||
|
"author": "Pablo Alonso",
|
||||||
|
"email": "pablo.alonso@upf.edu",
|
||||||
|
"release_date": "2022-02-17",
|
||||||
|
"framework": "tensorflow",
|
||||||
|
"framework_version": "2.8.0",
|
||||||
|
"classes": [
|
||||||
|
"Blues---Boogie Woogie",
|
||||||
|
"Blues---Chicago Blues",
|
||||||
|
"Blues---Country Blues",
|
||||||
|
"Blues---Delta Blues",
|
||||||
|
"Blues---Electric Blues",
|
||||||
|
"Blues---Harmonica Blues",
|
||||||
|
"Blues---Jump Blues",
|
||||||
|
"Blues---Louisiana Blues",
|
||||||
|
"Blues---Modern Electric Blues",
|
||||||
|
"Blues---Piano Blues",
|
||||||
|
"Blues---Rhythm & Blues",
|
||||||
|
"Blues---Texas Blues",
|
||||||
|
"Brass & Military---Brass Band",
|
||||||
|
"Brass & Military---Marches",
|
||||||
|
"Brass & Military---Military",
|
||||||
|
"Children's---Educational",
|
||||||
|
"Children's---Nursery Rhymes",
|
||||||
|
"Children's---Story",
|
||||||
|
"Classical---Baroque",
|
||||||
|
"Classical---Choral",
|
||||||
|
"Classical---Classical",
|
||||||
|
"Classical---Contemporary",
|
||||||
|
"Classical---Impressionist",
|
||||||
|
"Classical---Medieval",
|
||||||
|
"Classical---Modern",
|
||||||
|
"Classical---Neo-Classical",
|
||||||
|
"Classical---Neo-Romantic",
|
||||||
|
"Classical---Opera",
|
||||||
|
"Classical---Post-Modern",
|
||||||
|
"Classical---Renaissance",
|
||||||
|
"Classical---Romantic",
|
||||||
|
"Electronic---Abstract",
|
||||||
|
"Electronic---Acid",
|
||||||
|
"Electronic---Acid House",
|
||||||
|
"Electronic---Acid Jazz",
|
||||||
|
"Electronic---Ambient",
|
||||||
|
"Electronic---Bassline",
|
||||||
|
"Electronic---Beatdown",
|
||||||
|
"Electronic---Berlin-School",
|
||||||
|
"Electronic---Big Beat",
|
||||||
|
"Electronic---Bleep",
|
||||||
|
"Electronic---Breakbeat",
|
||||||
|
"Electronic---Breakcore",
|
||||||
|
"Electronic---Breaks",
|
||||||
|
"Electronic---Broken Beat",
|
||||||
|
"Electronic---Chillwave",
|
||||||
|
"Electronic---Chiptune",
|
||||||
|
"Electronic---Dance-pop",
|
||||||
|
"Electronic---Dark Ambient",
|
||||||
|
"Electronic---Darkwave",
|
||||||
|
"Electronic---Deep House",
|
||||||
|
"Electronic---Deep Techno",
|
||||||
|
"Electronic---Disco",
|
||||||
|
"Electronic---Disco Polo",
|
||||||
|
"Electronic---Donk",
|
||||||
|
"Electronic---Downtempo",
|
||||||
|
"Electronic---Drone",
|
||||||
|
"Electronic---Drum n Bass",
|
||||||
|
"Electronic---Dub",
|
||||||
|
"Electronic---Dub Techno",
|
||||||
|
"Electronic---Dubstep",
|
||||||
|
"Electronic---Dungeon Synth",
|
||||||
|
"Electronic---EBM",
|
||||||
|
"Electronic---Electro",
|
||||||
|
"Electronic---Electro House",
|
||||||
|
"Electronic---Electroclash",
|
||||||
|
"Electronic---Euro House",
|
||||||
|
"Electronic---Euro-Disco",
|
||||||
|
"Electronic---Eurobeat",
|
||||||
|
"Electronic---Eurodance",
|
||||||
|
"Electronic---Experimental",
|
||||||
|
"Electronic---Freestyle",
|
||||||
|
"Electronic---Future Jazz",
|
||||||
|
"Electronic---Gabber",
|
||||||
|
"Electronic---Garage House",
|
||||||
|
"Electronic---Ghetto",
|
||||||
|
"Electronic---Ghetto House",
|
||||||
|
"Electronic---Glitch",
|
||||||
|
"Electronic---Goa Trance",
|
||||||
|
"Electronic---Grime",
|
||||||
|
"Electronic---Halftime",
|
||||||
|
"Electronic---Hands Up",
|
||||||
|
"Electronic---Happy Hardcore",
|
||||||
|
"Electronic---Hard House",
|
||||||
|
"Electronic---Hard Techno",
|
||||||
|
"Electronic---Hard Trance",
|
||||||
|
"Electronic---Hardcore",
|
||||||
|
"Electronic---Hardstyle",
|
||||||
|
"Electronic---Hi NRG",
|
||||||
|
"Electronic---Hip Hop",
|
||||||
|
"Electronic---Hip-House",
|
||||||
|
"Electronic---House",
|
||||||
|
"Electronic---IDM",
|
||||||
|
"Electronic---Illbient",
|
||||||
|
"Electronic---Industrial",
|
||||||
|
"Electronic---Italo House",
|
||||||
|
"Electronic---Italo-Disco",
|
||||||
|
"Electronic---Italodance",
|
||||||
|
"Electronic---Jazzdance",
|
||||||
|
"Electronic---Juke",
|
||||||
|
"Electronic---Jumpstyle",
|
||||||
|
"Electronic---Jungle",
|
||||||
|
"Electronic---Latin",
|
||||||
|
"Electronic---Leftfield",
|
||||||
|
"Electronic---Makina",
|
||||||
|
"Electronic---Minimal",
|
||||||
|
"Electronic---Minimal Techno",
|
||||||
|
"Electronic---Modern Classical",
|
||||||
|
"Electronic---Musique Concr\u00e8te",
|
||||||
|
"Electronic---Neofolk",
|
||||||
|
"Electronic---New Age",
|
||||||
|
"Electronic---New Beat",
|
||||||
|
"Electronic---New Wave",
|
||||||
|
"Electronic---Noise",
|
||||||
|
"Electronic---Nu-Disco",
|
||||||
|
"Electronic---Power Electronics",
|
||||||
|
"Electronic---Progressive Breaks",
|
||||||
|
"Electronic---Progressive House",
|
||||||
|
"Electronic---Progressive Trance",
|
||||||
|
"Electronic---Psy-Trance",
|
||||||
|
"Electronic---Rhythmic Noise",
|
||||||
|
"Electronic---Schranz",
|
||||||
|
"Electronic---Sound Collage",
|
||||||
|
"Electronic---Speed Garage",
|
||||||
|
"Electronic---Speedcore",
|
||||||
|
"Electronic---Synth-pop",
|
||||||
|
"Electronic---Synthwave",
|
||||||
|
"Electronic---Tech House",
|
||||||
|
"Electronic---Tech Trance",
|
||||||
|
"Electronic---Techno",
|
||||||
|
"Electronic---Trance",
|
||||||
|
"Electronic---Tribal",
|
||||||
|
"Electronic---Tribal House",
|
||||||
|
"Electronic---Trip Hop",
|
||||||
|
"Electronic---Tropical House",
|
||||||
|
"Electronic---UK Garage",
|
||||||
|
"Electronic---Vaporwave",
|
||||||
|
"Folk, World, & Country---African",
|
||||||
|
"Folk, World, & Country---Bluegrass",
|
||||||
|
"Folk, World, & Country---Cajun",
|
||||||
|
"Folk, World, & Country---Canzone Napoletana",
|
||||||
|
"Folk, World, & Country---Catalan Music",
|
||||||
|
"Folk, World, & Country---Celtic",
|
||||||
|
"Folk, World, & Country---Country",
|
||||||
|
"Folk, World, & Country---Fado",
|
||||||
|
"Folk, World, & Country---Flamenco",
|
||||||
|
"Folk, World, & Country---Folk",
|
||||||
|
"Folk, World, & Country---Gospel",
|
||||||
|
"Folk, World, & Country---Highlife",
|
||||||
|
"Folk, World, & Country---Hillbilly",
|
||||||
|
"Folk, World, & Country---Hindustani",
|
||||||
|
"Folk, World, & Country---Honky Tonk",
|
||||||
|
"Folk, World, & Country---Indian Classical",
|
||||||
|
"Folk, World, & Country---La\u00efk\u00f3",
|
||||||
|
"Folk, World, & Country---Nordic",
|
||||||
|
"Folk, World, & Country---Pacific",
|
||||||
|
"Folk, World, & Country---Polka",
|
||||||
|
"Folk, World, & Country---Ra\u00ef",
|
||||||
|
"Folk, World, & Country---Romani",
|
||||||
|
"Folk, World, & Country---Soukous",
|
||||||
|
"Folk, World, & Country---S\u00e9ga",
|
||||||
|
"Folk, World, & Country---Volksmusik",
|
||||||
|
"Folk, World, & Country---Zouk",
|
||||||
|
"Folk, World, & Country---\u00c9ntekhno",
|
||||||
|
"Funk / Soul---Afrobeat",
|
||||||
|
"Funk / Soul---Boogie",
|
||||||
|
"Funk / Soul---Contemporary R&B",
|
||||||
|
"Funk / Soul---Disco",
|
||||||
|
"Funk / Soul---Free Funk",
|
||||||
|
"Funk / Soul---Funk",
|
||||||
|
"Funk / Soul---Gospel",
|
||||||
|
"Funk / Soul---Neo Soul",
|
||||||
|
"Funk / Soul---New Jack Swing",
|
||||||
|
"Funk / Soul---P.Funk",
|
||||||
|
"Funk / Soul---Psychedelic",
|
||||||
|
"Funk / Soul---Rhythm & Blues",
|
||||||
|
"Funk / Soul---Soul",
|
||||||
|
"Funk / Soul---Swingbeat",
|
||||||
|
"Funk / Soul---UK Street Soul",
|
||||||
|
"Hip Hop---Bass Music",
|
||||||
|
"Hip Hop---Boom Bap",
|
||||||
|
"Hip Hop---Bounce",
|
||||||
|
"Hip Hop---Britcore",
|
||||||
|
"Hip Hop---Cloud Rap",
|
||||||
|
"Hip Hop---Conscious",
|
||||||
|
"Hip Hop---Crunk",
|
||||||
|
"Hip Hop---Cut-up/DJ",
|
||||||
|
"Hip Hop---DJ Battle Tool",
|
||||||
|
"Hip Hop---Electro",
|
||||||
|
"Hip Hop---G-Funk",
|
||||||
|
"Hip Hop---Gangsta",
|
||||||
|
"Hip Hop---Grime",
|
||||||
|
"Hip Hop---Hardcore Hip-Hop",
|
||||||
|
"Hip Hop---Horrorcore",
|
||||||
|
"Hip Hop---Instrumental",
|
||||||
|
"Hip Hop---Jazzy Hip-Hop",
|
||||||
|
"Hip Hop---Miami Bass",
|
||||||
|
"Hip Hop---Pop Rap",
|
||||||
|
"Hip Hop---Ragga HipHop",
|
||||||
|
"Hip Hop---RnB/Swing",
|
||||||
|
"Hip Hop---Screw",
|
||||||
|
"Hip Hop---Thug Rap",
|
||||||
|
"Hip Hop---Trap",
|
||||||
|
"Hip Hop---Trip Hop",
|
||||||
|
"Hip Hop---Turntablism",
|
||||||
|
"Jazz---Afro-Cuban Jazz",
|
||||||
|
"Jazz---Afrobeat",
|
||||||
|
"Jazz---Avant-garde Jazz",
|
||||||
|
"Jazz---Big Band",
|
||||||
|
"Jazz---Bop",
|
||||||
|
"Jazz---Bossa Nova",
|
||||||
|
"Jazz---Contemporary Jazz",
|
||||||
|
"Jazz---Cool Jazz",
|
||||||
|
"Jazz---Dixieland",
|
||||||
|
"Jazz---Easy Listening",
|
||||||
|
"Jazz---Free Improvisation",
|
||||||
|
"Jazz---Free Jazz",
|
||||||
|
"Jazz---Fusion",
|
||||||
|
"Jazz---Gypsy Jazz",
|
||||||
|
"Jazz---Hard Bop",
|
||||||
|
"Jazz---Jazz-Funk",
|
||||||
|
"Jazz---Jazz-Rock",
|
||||||
|
"Jazz---Latin Jazz",
|
||||||
|
"Jazz---Modal",
|
||||||
|
"Jazz---Post Bop",
|
||||||
|
"Jazz---Ragtime",
|
||||||
|
"Jazz---Smooth Jazz",
|
||||||
|
"Jazz---Soul-Jazz",
|
||||||
|
"Jazz---Space-Age",
|
||||||
|
"Jazz---Swing",
|
||||||
|
"Latin---Afro-Cuban",
|
||||||
|
"Latin---Bai\u00e3o",
|
||||||
|
"Latin---Batucada",
|
||||||
|
"Latin---Beguine",
|
||||||
|
"Latin---Bolero",
|
||||||
|
"Latin---Boogaloo",
|
||||||
|
"Latin---Bossanova",
|
||||||
|
"Latin---Cha-Cha",
|
||||||
|
"Latin---Charanga",
|
||||||
|
"Latin---Compas",
|
||||||
|
"Latin---Cubano",
|
||||||
|
"Latin---Cumbia",
|
||||||
|
"Latin---Descarga",
|
||||||
|
"Latin---Forr\u00f3",
|
||||||
|
"Latin---Guaguanc\u00f3",
|
||||||
|
"Latin---Guajira",
|
||||||
|
"Latin---Guaracha",
|
||||||
|
"Latin---MPB",
|
||||||
|
"Latin---Mambo",
|
||||||
|
"Latin---Mariachi",
|
||||||
|
"Latin---Merengue",
|
||||||
|
"Latin---Norte\u00f1o",
|
||||||
|
"Latin---Nueva Cancion",
|
||||||
|
"Latin---Pachanga",
|
||||||
|
"Latin---Porro",
|
||||||
|
"Latin---Ranchera",
|
||||||
|
"Latin---Reggaeton",
|
||||||
|
"Latin---Rumba",
|
||||||
|
"Latin---Salsa",
|
||||||
|
"Latin---Samba",
|
||||||
|
"Latin---Son",
|
||||||
|
"Latin---Son Montuno",
|
||||||
|
"Latin---Tango",
|
||||||
|
"Latin---Tejano",
|
||||||
|
"Latin---Vallenato",
|
||||||
|
"Non-Music---Audiobook",
|
||||||
|
"Non-Music---Comedy",
|
||||||
|
"Non-Music---Dialogue",
|
||||||
|
"Non-Music---Education",
|
||||||
|
"Non-Music---Field Recording",
|
||||||
|
"Non-Music---Interview",
|
||||||
|
"Non-Music---Monolog",
|
||||||
|
"Non-Music---Poetry",
|
||||||
|
"Non-Music---Political",
|
||||||
|
"Non-Music---Promotional",
|
||||||
|
"Non-Music---Radioplay",
|
||||||
|
"Non-Music---Religious",
|
||||||
|
"Non-Music---Spoken Word",
|
||||||
|
"Pop---Ballad",
|
||||||
|
"Pop---Bollywood",
|
||||||
|
"Pop---Bubblegum",
|
||||||
|
"Pop---Chanson",
|
||||||
|
"Pop---City Pop",
|
||||||
|
"Pop---Europop",
|
||||||
|
"Pop---Indie Pop",
|
||||||
|
"Pop---J-pop",
|
||||||
|
"Pop---K-pop",
|
||||||
|
"Pop---Kay\u014dkyoku",
|
||||||
|
"Pop---Light Music",
|
||||||
|
"Pop---Music Hall",
|
||||||
|
"Pop---Novelty",
|
||||||
|
"Pop---Parody",
|
||||||
|
"Pop---Schlager",
|
||||||
|
"Pop---Vocal",
|
||||||
|
"Reggae---Calypso",
|
||||||
|
"Reggae---Dancehall",
|
||||||
|
"Reggae---Dub",
|
||||||
|
"Reggae---Lovers Rock",
|
||||||
|
"Reggae---Ragga",
|
||||||
|
"Reggae---Reggae",
|
||||||
|
"Reggae---Reggae-Pop",
|
||||||
|
"Reggae---Rocksteady",
|
||||||
|
"Reggae---Roots Reggae",
|
||||||
|
"Reggae---Ska",
|
||||||
|
"Reggae---Soca",
|
||||||
|
"Rock---AOR",
|
||||||
|
"Rock---Acid Rock",
|
||||||
|
"Rock---Acoustic",
|
||||||
|
"Rock---Alternative Rock",
|
||||||
|
"Rock---Arena Rock",
|
||||||
|
"Rock---Art Rock",
|
||||||
|
"Rock---Atmospheric Black Metal",
|
||||||
|
"Rock---Avantgarde",
|
||||||
|
"Rock---Beat",
|
||||||
|
"Rock---Black Metal",
|
||||||
|
"Rock---Blues Rock",
|
||||||
|
"Rock---Brit Pop",
|
||||||
|
"Rock---Classic Rock",
|
||||||
|
"Rock---Coldwave",
|
||||||
|
"Rock---Country Rock",
|
||||||
|
"Rock---Crust",
|
||||||
|
"Rock---Death Metal",
|
||||||
|
"Rock---Deathcore",
|
||||||
|
"Rock---Deathrock",
|
||||||
|
"Rock---Depressive Black Metal",
|
||||||
|
"Rock---Doo Wop",
|
||||||
|
"Rock---Doom Metal",
|
||||||
|
"Rock---Dream Pop",
|
||||||
|
"Rock---Emo",
|
||||||
|
"Rock---Ethereal",
|
||||||
|
"Rock---Experimental",
|
||||||
|
"Rock---Folk Metal",
|
||||||
|
"Rock---Folk Rock",
|
||||||
|
"Rock---Funeral Doom Metal",
|
||||||
|
"Rock---Funk Metal",
|
||||||
|
"Rock---Garage Rock",
|
||||||
|
"Rock---Glam",
|
||||||
|
"Rock---Goregrind",
|
||||||
|
"Rock---Goth Rock",
|
||||||
|
"Rock---Gothic Metal",
|
||||||
|
"Rock---Grindcore",
|
||||||
|
"Rock---Grunge",
|
||||||
|
"Rock---Hard Rock",
|
||||||
|
"Rock---Hardcore",
|
||||||
|
"Rock---Heavy Metal",
|
||||||
|
"Rock---Indie Rock",
|
||||||
|
"Rock---Industrial",
|
||||||
|
"Rock---Krautrock",
|
||||||
|
"Rock---Lo-Fi",
|
||||||
|
"Rock---Lounge",
|
||||||
|
"Rock---Math Rock",
|
||||||
|
"Rock---Melodic Death Metal",
|
||||||
|
"Rock---Melodic Hardcore",
|
||||||
|
"Rock---Metalcore",
|
||||||
|
"Rock---Mod",
|
||||||
|
"Rock---Neofolk",
|
||||||
|
"Rock---New Wave",
|
||||||
|
"Rock---No Wave",
|
||||||
|
"Rock---Noise",
|
||||||
|
"Rock---Noisecore",
|
||||||
|
"Rock---Nu Metal",
|
||||||
|
"Rock---Oi",
|
||||||
|
"Rock---Parody",
|
||||||
|
"Rock---Pop Punk",
|
||||||
|
"Rock---Pop Rock",
|
||||||
|
"Rock---Pornogrind",
|
||||||
|
"Rock---Post Rock",
|
||||||
|
"Rock---Post-Hardcore",
|
||||||
|
"Rock---Post-Metal",
|
||||||
|
"Rock---Post-Punk",
|
||||||
|
"Rock---Power Metal",
|
||||||
|
"Rock---Power Pop",
|
||||||
|
"Rock---Power Violence",
|
||||||
|
"Rock---Prog Rock",
|
||||||
|
"Rock---Progressive Metal",
|
||||||
|
"Rock---Psychedelic Rock",
|
||||||
|
"Rock---Psychobilly",
|
||||||
|
"Rock---Pub Rock",
|
||||||
|
"Rock---Punk",
|
||||||
|
"Rock---Rock & Roll",
|
||||||
|
"Rock---Rockabilly",
|
||||||
|
"Rock---Shoegaze",
|
||||||
|
"Rock---Ska",
|
||||||
|
"Rock---Sludge Metal",
|
||||||
|
"Rock---Soft Rock",
|
||||||
|
"Rock---Southern Rock",
|
||||||
|
"Rock---Space Rock",
|
||||||
|
"Rock---Speed Metal",
|
||||||
|
"Rock---Stoner Rock",
|
||||||
|
"Rock---Surf",
|
||||||
|
"Rock---Symphonic Rock",
|
||||||
|
"Rock---Technical Death Metal",
|
||||||
|
"Rock---Thrash",
|
||||||
|
"Rock---Twist",
|
||||||
|
"Rock---Viking Metal",
|
||||||
|
"Rock---Y\u00e9-Y\u00e9",
|
||||||
|
"Stage & Screen---Musical",
|
||||||
|
"Stage & Screen---Score",
|
||||||
|
"Stage & Screen---Soundtrack",
|
||||||
|
"Stage & Screen---Theme"
|
||||||
|
],
|
||||||
|
"model_types": [
|
||||||
|
"frozen_model",
|
||||||
|
"SavedModel",
|
||||||
|
"onnx"
|
||||||
|
],
|
||||||
|
"dataset": {
|
||||||
|
"name": "Discogs-4M (unreleased)",
|
||||||
|
"citation": "In-house dataset",
|
||||||
|
"size": "4M full tracks (3.3M used)",
|
||||||
|
"metrics": {
|
||||||
|
"ROC-AUC": 0.95417,
|
||||||
|
"PR-AUC": 0.20629
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "serving_default_melspectrogram",
|
||||||
|
"type": "float",
|
||||||
|
"shape": [
|
||||||
|
64,
|
||||||
|
128,
|
||||||
|
96
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "PartitionedCall:0",
|
||||||
|
"type": "float",
|
||||||
|
"shape": [
|
||||||
|
64,
|
||||||
|
400
|
||||||
|
],
|
||||||
|
"op": "Sigmoid",
|
||||||
|
"output_purpose": "predictions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PartitionedCall:1",
|
||||||
|
"type": "float",
|
||||||
|
"shape": [
|
||||||
|
64,
|
||||||
|
1280
|
||||||
|
],
|
||||||
|
"op": "Flatten",
|
||||||
|
"output_purpose": "embeddings"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"citation": "@inproceedings{alonso2022music,\n title={Music Representation Learning Based on Editorial Metadata from Discogs},\n author={Alonso-Jim{\\'e}nez, Pablo and Serra, Xavier and Bogdanov, Dmitry},\n booktitle={Conference of the International Society for Music Information Retrieval (ISMIR)},\n year={2022}\n}",
|
||||||
|
"inference": {
|
||||||
|
"sample_rate": 16000,
|
||||||
|
"algorithm": "TensorflowPredictEffnetDiscogs"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,452 @@
|
|||||||
|
{
|
||||||
|
"name": "Genre Discogs400",
|
||||||
|
"type": "Music genre classification",
|
||||||
|
"link": "https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.pb",
|
||||||
|
"version": "1",
|
||||||
|
"description": "Prediction of 400 music styles in the from the Discogs taxonomy",
|
||||||
|
"author": "Pablo Alonso",
|
||||||
|
"email": "pablo.alonso@upf.edu",
|
||||||
|
"release_date": "2023-05-04",
|
||||||
|
"framework": "tensorflow",
|
||||||
|
"framework_version": "2.8.0",
|
||||||
|
"classes": [
|
||||||
|
"Blues---Boogie Woogie",
|
||||||
|
"Blues---Chicago Blues",
|
||||||
|
"Blues---Country Blues",
|
||||||
|
"Blues---Delta Blues",
|
||||||
|
"Blues---Electric Blues",
|
||||||
|
"Blues---Harmonica Blues",
|
||||||
|
"Blues---Jump Blues",
|
||||||
|
"Blues---Louisiana Blues",
|
||||||
|
"Blues---Modern Electric Blues",
|
||||||
|
"Blues---Piano Blues",
|
||||||
|
"Blues---Rhythm & Blues",
|
||||||
|
"Blues---Texas Blues",
|
||||||
|
"Brass & Military---Brass Band",
|
||||||
|
"Brass & Military---Marches",
|
||||||
|
"Brass & Military---Military",
|
||||||
|
"Children's---Educational",
|
||||||
|
"Children's---Nursery Rhymes",
|
||||||
|
"Children's---Story",
|
||||||
|
"Classical---Baroque",
|
||||||
|
"Classical---Choral",
|
||||||
|
"Classical---Classical",
|
||||||
|
"Classical---Contemporary",
|
||||||
|
"Classical---Impressionist",
|
||||||
|
"Classical---Medieval",
|
||||||
|
"Classical---Modern",
|
||||||
|
"Classical---Neo-Classical",
|
||||||
|
"Classical---Neo-Romantic",
|
||||||
|
"Classical---Opera",
|
||||||
|
"Classical---Post-Modern",
|
||||||
|
"Classical---Renaissance",
|
||||||
|
"Classical---Romantic",
|
||||||
|
"Electronic---Abstract",
|
||||||
|
"Electronic---Acid",
|
||||||
|
"Electronic---Acid House",
|
||||||
|
"Electronic---Acid Jazz",
|
||||||
|
"Electronic---Ambient",
|
||||||
|
"Electronic---Bassline",
|
||||||
|
"Electronic---Beatdown",
|
||||||
|
"Electronic---Berlin-School",
|
||||||
|
"Electronic---Big Beat",
|
||||||
|
"Electronic---Bleep",
|
||||||
|
"Electronic---Breakbeat",
|
||||||
|
"Electronic---Breakcore",
|
||||||
|
"Electronic---Breaks",
|
||||||
|
"Electronic---Broken Beat",
|
||||||
|
"Electronic---Chillwave",
|
||||||
|
"Electronic---Chiptune",
|
||||||
|
"Electronic---Dance-pop",
|
||||||
|
"Electronic---Dark Ambient",
|
||||||
|
"Electronic---Darkwave",
|
||||||
|
"Electronic---Deep House",
|
||||||
|
"Electronic---Deep Techno",
|
||||||
|
"Electronic---Disco",
|
||||||
|
"Electronic---Disco Polo",
|
||||||
|
"Electronic---Donk",
|
||||||
|
"Electronic---Downtempo",
|
||||||
|
"Electronic---Drone",
|
||||||
|
"Electronic---Drum n Bass",
|
||||||
|
"Electronic---Dub",
|
||||||
|
"Electronic---Dub Techno",
|
||||||
|
"Electronic---Dubstep",
|
||||||
|
"Electronic---Dungeon Synth",
|
||||||
|
"Electronic---EBM",
|
||||||
|
"Electronic---Electro",
|
||||||
|
"Electronic---Electro House",
|
||||||
|
"Electronic---Electroclash",
|
||||||
|
"Electronic---Euro House",
|
||||||
|
"Electronic---Euro-Disco",
|
||||||
|
"Electronic---Eurobeat",
|
||||||
|
"Electronic---Eurodance",
|
||||||
|
"Electronic---Experimental",
|
||||||
|
"Electronic---Freestyle",
|
||||||
|
"Electronic---Future Jazz",
|
||||||
|
"Electronic---Gabber",
|
||||||
|
"Electronic---Garage House",
|
||||||
|
"Electronic---Ghetto",
|
||||||
|
"Electronic---Ghetto House",
|
||||||
|
"Electronic---Glitch",
|
||||||
|
"Electronic---Goa Trance",
|
||||||
|
"Electronic---Grime",
|
||||||
|
"Electronic---Halftime",
|
||||||
|
"Electronic---Hands Up",
|
||||||
|
"Electronic---Happy Hardcore",
|
||||||
|
"Electronic---Hard House",
|
||||||
|
"Electronic---Hard Techno",
|
||||||
|
"Electronic---Hard Trance",
|
||||||
|
"Electronic---Hardcore",
|
||||||
|
"Electronic---Hardstyle",
|
||||||
|
"Electronic---Hi NRG",
|
||||||
|
"Electronic---Hip Hop",
|
||||||
|
"Electronic---Hip-House",
|
||||||
|
"Electronic---House",
|
||||||
|
"Electronic---IDM",
|
||||||
|
"Electronic---Illbient",
|
||||||
|
"Electronic---Industrial",
|
||||||
|
"Electronic---Italo House",
|
||||||
|
"Electronic---Italo-Disco",
|
||||||
|
"Electronic---Italodance",
|
||||||
|
"Electronic---Jazzdance",
|
||||||
|
"Electronic---Juke",
|
||||||
|
"Electronic---Jumpstyle",
|
||||||
|
"Electronic---Jungle",
|
||||||
|
"Electronic---Latin",
|
||||||
|
"Electronic---Leftfield",
|
||||||
|
"Electronic---Makina",
|
||||||
|
"Electronic---Minimal",
|
||||||
|
"Electronic---Minimal Techno",
|
||||||
|
"Electronic---Modern Classical",
|
||||||
|
"Electronic---Musique Concr\u00e8te",
|
||||||
|
"Electronic---Neofolk",
|
||||||
|
"Electronic---New Age",
|
||||||
|
"Electronic---New Beat",
|
||||||
|
"Electronic---New Wave",
|
||||||
|
"Electronic---Noise",
|
||||||
|
"Electronic---Nu-Disco",
|
||||||
|
"Electronic---Power Electronics",
|
||||||
|
"Electronic---Progressive Breaks",
|
||||||
|
"Electronic---Progressive House",
|
||||||
|
"Electronic---Progressive Trance",
|
||||||
|
"Electronic---Psy-Trance",
|
||||||
|
"Electronic---Rhythmic Noise",
|
||||||
|
"Electronic---Schranz",
|
||||||
|
"Electronic---Sound Collage",
|
||||||
|
"Electronic---Speed Garage",
|
||||||
|
"Electronic---Speedcore",
|
||||||
|
"Electronic---Synth-pop",
|
||||||
|
"Electronic---Synthwave",
|
||||||
|
"Electronic---Tech House",
|
||||||
|
"Electronic---Tech Trance",
|
||||||
|
"Electronic---Techno",
|
||||||
|
"Electronic---Trance",
|
||||||
|
"Electronic---Tribal",
|
||||||
|
"Electronic---Tribal House",
|
||||||
|
"Electronic---Trip Hop",
|
||||||
|
"Electronic---Tropical House",
|
||||||
|
"Electronic---UK Garage",
|
||||||
|
"Electronic---Vaporwave",
|
||||||
|
"Folk, World, & Country---African",
|
||||||
|
"Folk, World, & Country---Bluegrass",
|
||||||
|
"Folk, World, & Country---Cajun",
|
||||||
|
"Folk, World, & Country---Canzone Napoletana",
|
||||||
|
"Folk, World, & Country---Catalan Music",
|
||||||
|
"Folk, World, & Country---Celtic",
|
||||||
|
"Folk, World, & Country---Country",
|
||||||
|
"Folk, World, & Country---Fado",
|
||||||
|
"Folk, World, & Country---Flamenco",
|
||||||
|
"Folk, World, & Country---Folk",
|
||||||
|
"Folk, World, & Country---Gospel",
|
||||||
|
"Folk, World, & Country---Highlife",
|
||||||
|
"Folk, World, & Country---Hillbilly",
|
||||||
|
"Folk, World, & Country---Hindustani",
|
||||||
|
"Folk, World, & Country---Honky Tonk",
|
||||||
|
"Folk, World, & Country---Indian Classical",
|
||||||
|
"Folk, World, & Country---La\u00efk\u00f3",
|
||||||
|
"Folk, World, & Country---Nordic",
|
||||||
|
"Folk, World, & Country---Pacific",
|
||||||
|
"Folk, World, & Country---Polka",
|
||||||
|
"Folk, World, & Country---Ra\u00ef",
|
||||||
|
"Folk, World, & Country---Romani",
|
||||||
|
"Folk, World, & Country---Soukous",
|
||||||
|
"Folk, World, & Country---S\u00e9ga",
|
||||||
|
"Folk, World, & Country---Volksmusik",
|
||||||
|
"Folk, World, & Country---Zouk",
|
||||||
|
"Folk, World, & Country---\u00c9ntekhno",
|
||||||
|
"Funk / Soul---Afrobeat",
|
||||||
|
"Funk / Soul---Boogie",
|
||||||
|
"Funk / Soul---Contemporary R&B",
|
||||||
|
"Funk / Soul---Disco",
|
||||||
|
"Funk / Soul---Free Funk",
|
||||||
|
"Funk / Soul---Funk",
|
||||||
|
"Funk / Soul---Gospel",
|
||||||
|
"Funk / Soul---Neo Soul",
|
||||||
|
"Funk / Soul---New Jack Swing",
|
||||||
|
"Funk / Soul---P.Funk",
|
||||||
|
"Funk / Soul---Psychedelic",
|
||||||
|
"Funk / Soul---Rhythm & Blues",
|
||||||
|
"Funk / Soul---Soul",
|
||||||
|
"Funk / Soul---Swingbeat",
|
||||||
|
"Funk / Soul---UK Street Soul",
|
||||||
|
"Hip Hop---Bass Music",
|
||||||
|
"Hip Hop---Boom Bap",
|
||||||
|
"Hip Hop---Bounce",
|
||||||
|
"Hip Hop---Britcore",
|
||||||
|
"Hip Hop---Cloud Rap",
|
||||||
|
"Hip Hop---Conscious",
|
||||||
|
"Hip Hop---Crunk",
|
||||||
|
"Hip Hop---Cut-up/DJ",
|
||||||
|
"Hip Hop---DJ Battle Tool",
|
||||||
|
"Hip Hop---Electro",
|
||||||
|
"Hip Hop---G-Funk",
|
||||||
|
"Hip Hop---Gangsta",
|
||||||
|
"Hip Hop---Grime",
|
||||||
|
"Hip Hop---Hardcore Hip-Hop",
|
||||||
|
"Hip Hop---Horrorcore",
|
||||||
|
"Hip Hop---Instrumental",
|
||||||
|
"Hip Hop---Jazzy Hip-Hop",
|
||||||
|
"Hip Hop---Miami Bass",
|
||||||
|
"Hip Hop---Pop Rap",
|
||||||
|
"Hip Hop---Ragga HipHop",
|
||||||
|
"Hip Hop---RnB/Swing",
|
||||||
|
"Hip Hop---Screw",
|
||||||
|
"Hip Hop---Thug Rap",
|
||||||
|
"Hip Hop---Trap",
|
||||||
|
"Hip Hop---Trip Hop",
|
||||||
|
"Hip Hop---Turntablism",
|
||||||
|
"Jazz---Afro-Cuban Jazz",
|
||||||
|
"Jazz---Afrobeat",
|
||||||
|
"Jazz---Avant-garde Jazz",
|
||||||
|
"Jazz---Big Band",
|
||||||
|
"Jazz---Bop",
|
||||||
|
"Jazz---Bossa Nova",
|
||||||
|
"Jazz---Contemporary Jazz",
|
||||||
|
"Jazz---Cool Jazz",
|
||||||
|
"Jazz---Dixieland",
|
||||||
|
"Jazz---Easy Listening",
|
||||||
|
"Jazz---Free Improvisation",
|
||||||
|
"Jazz---Free Jazz",
|
||||||
|
"Jazz---Fusion",
|
||||||
|
"Jazz---Gypsy Jazz",
|
||||||
|
"Jazz---Hard Bop",
|
||||||
|
"Jazz---Jazz-Funk",
|
||||||
|
"Jazz---Jazz-Rock",
|
||||||
|
"Jazz---Latin Jazz",
|
||||||
|
"Jazz---Modal",
|
||||||
|
"Jazz---Post Bop",
|
||||||
|
"Jazz---Ragtime",
|
||||||
|
"Jazz---Smooth Jazz",
|
||||||
|
"Jazz---Soul-Jazz",
|
||||||
|
"Jazz---Space-Age",
|
||||||
|
"Jazz---Swing",
|
||||||
|
"Latin---Afro-Cuban",
|
||||||
|
"Latin---Bai\u00e3o",
|
||||||
|
"Latin---Batucada",
|
||||||
|
"Latin---Beguine",
|
||||||
|
"Latin---Bolero",
|
||||||
|
"Latin---Boogaloo",
|
||||||
|
"Latin---Bossanova",
|
||||||
|
"Latin---Cha-Cha",
|
||||||
|
"Latin---Charanga",
|
||||||
|
"Latin---Compas",
|
||||||
|
"Latin---Cubano",
|
||||||
|
"Latin---Cumbia",
|
||||||
|
"Latin---Descarga",
|
||||||
|
"Latin---Forr\u00f3",
|
||||||
|
"Latin---Guaguanc\u00f3",
|
||||||
|
"Latin---Guajira",
|
||||||
|
"Latin---Guaracha",
|
||||||
|
"Latin---MPB",
|
||||||
|
"Latin---Mambo",
|
||||||
|
"Latin---Mariachi",
|
||||||
|
"Latin---Merengue",
|
||||||
|
"Latin---Norte\u00f1o",
|
||||||
|
"Latin---Nueva Cancion",
|
||||||
|
"Latin---Pachanga",
|
||||||
|
"Latin---Porro",
|
||||||
|
"Latin---Ranchera",
|
||||||
|
"Latin---Reggaeton",
|
||||||
|
"Latin---Rumba",
|
||||||
|
"Latin---Salsa",
|
||||||
|
"Latin---Samba",
|
||||||
|
"Latin---Son",
|
||||||
|
"Latin---Son Montuno",
|
||||||
|
"Latin---Tango",
|
||||||
|
"Latin---Tejano",
|
||||||
|
"Latin---Vallenato",
|
||||||
|
"Non-Music---Audiobook",
|
||||||
|
"Non-Music---Comedy",
|
||||||
|
"Non-Music---Dialogue",
|
||||||
|
"Non-Music---Education",
|
||||||
|
"Non-Music---Field Recording",
|
||||||
|
"Non-Music---Interview",
|
||||||
|
"Non-Music---Monolog",
|
||||||
|
"Non-Music---Poetry",
|
||||||
|
"Non-Music---Political",
|
||||||
|
"Non-Music---Promotional",
|
||||||
|
"Non-Music---Radioplay",
|
||||||
|
"Non-Music---Religious",
|
||||||
|
"Non-Music---Spoken Word",
|
||||||
|
"Pop---Ballad",
|
||||||
|
"Pop---Bollywood",
|
||||||
|
"Pop---Bubblegum",
|
||||||
|
"Pop---Chanson",
|
||||||
|
"Pop---City Pop",
|
||||||
|
"Pop---Europop",
|
||||||
|
"Pop---Indie Pop",
|
||||||
|
"Pop---J-pop",
|
||||||
|
"Pop---K-pop",
|
||||||
|
"Pop---Kay\u014dkyoku",
|
||||||
|
"Pop---Light Music",
|
||||||
|
"Pop---Music Hall",
|
||||||
|
"Pop---Novelty",
|
||||||
|
"Pop---Parody",
|
||||||
|
"Pop---Schlager",
|
||||||
|
"Pop---Vocal",
|
||||||
|
"Reggae---Calypso",
|
||||||
|
"Reggae---Dancehall",
|
||||||
|
"Reggae---Dub",
|
||||||
|
"Reggae---Lovers Rock",
|
||||||
|
"Reggae---Ragga",
|
||||||
|
"Reggae---Reggae",
|
||||||
|
"Reggae---Reggae-Pop",
|
||||||
|
"Reggae---Rocksteady",
|
||||||
|
"Reggae---Roots Reggae",
|
||||||
|
"Reggae---Ska",
|
||||||
|
"Reggae---Soca",
|
||||||
|
"Rock---AOR",
|
||||||
|
"Rock---Acid Rock",
|
||||||
|
"Rock---Acoustic",
|
||||||
|
"Rock---Alternative Rock",
|
||||||
|
"Rock---Arena Rock",
|
||||||
|
"Rock---Art Rock",
|
||||||
|
"Rock---Atmospheric Black Metal",
|
||||||
|
"Rock---Avantgarde",
|
||||||
|
"Rock---Beat",
|
||||||
|
"Rock---Black Metal",
|
||||||
|
"Rock---Blues Rock",
|
||||||
|
"Rock---Brit Pop",
|
||||||
|
"Rock---Classic Rock",
|
||||||
|
"Rock---Coldwave",
|
||||||
|
"Rock---Country Rock",
|
||||||
|
"Rock---Crust",
|
||||||
|
"Rock---Death Metal",
|
||||||
|
"Rock---Deathcore",
|
||||||
|
"Rock---Deathrock",
|
||||||
|
"Rock---Depressive Black Metal",
|
||||||
|
"Rock---Doo Wop",
|
||||||
|
"Rock---Doom Metal",
|
||||||
|
"Rock---Dream Pop",
|
||||||
|
"Rock---Emo",
|
||||||
|
"Rock---Ethereal",
|
||||||
|
"Rock---Experimental",
|
||||||
|
"Rock---Folk Metal",
|
||||||
|
"Rock---Folk Rock",
|
||||||
|
"Rock---Funeral Doom Metal",
|
||||||
|
"Rock---Funk Metal",
|
||||||
|
"Rock---Garage Rock",
|
||||||
|
"Rock---Glam",
|
||||||
|
"Rock---Goregrind",
|
||||||
|
"Rock---Goth Rock",
|
||||||
|
"Rock---Gothic Metal",
|
||||||
|
"Rock---Grindcore",
|
||||||
|
"Rock---Grunge",
|
||||||
|
"Rock---Hard Rock",
|
||||||
|
"Rock---Hardcore",
|
||||||
|
"Rock---Heavy Metal",
|
||||||
|
"Rock---Indie Rock",
|
||||||
|
"Rock---Industrial",
|
||||||
|
"Rock---Krautrock",
|
||||||
|
"Rock---Lo-Fi",
|
||||||
|
"Rock---Lounge",
|
||||||
|
"Rock---Math Rock",
|
||||||
|
"Rock---Melodic Death Metal",
|
||||||
|
"Rock---Melodic Hardcore",
|
||||||
|
"Rock---Metalcore",
|
||||||
|
"Rock---Mod",
|
||||||
|
"Rock---Neofolk",
|
||||||
|
"Rock---New Wave",
|
||||||
|
"Rock---No Wave",
|
||||||
|
"Rock---Noise",
|
||||||
|
"Rock---Noisecore",
|
||||||
|
"Rock---Nu Metal",
|
||||||
|
"Rock---Oi",
|
||||||
|
"Rock---Parody",
|
||||||
|
"Rock---Pop Punk",
|
||||||
|
"Rock---Pop Rock",
|
||||||
|
"Rock---Pornogrind",
|
||||||
|
"Rock---Post Rock",
|
||||||
|
"Rock---Post-Hardcore",
|
||||||
|
"Rock---Post-Metal",
|
||||||
|
"Rock---Post-Punk",
|
||||||
|
"Rock---Power Metal",
|
||||||
|
"Rock---Power Pop",
|
||||||
|
"Rock---Power Violence",
|
||||||
|
"Rock---Prog Rock",
|
||||||
|
"Rock---Progressive Metal",
|
||||||
|
"Rock---Psychedelic Rock",
|
||||||
|
"Rock---Psychobilly",
|
||||||
|
"Rock---Pub Rock",
|
||||||
|
"Rock---Punk",
|
||||||
|
"Rock---Rock & Roll",
|
||||||
|
"Rock---Rockabilly",
|
||||||
|
"Rock---Shoegaze",
|
||||||
|
"Rock---Ska",
|
||||||
|
"Rock---Sludge Metal",
|
||||||
|
"Rock---Soft Rock",
|
||||||
|
"Rock---Southern Rock",
|
||||||
|
"Rock---Space Rock",
|
||||||
|
"Rock---Speed Metal",
|
||||||
|
"Rock---Stoner Rock",
|
||||||
|
"Rock---Surf",
|
||||||
|
"Rock---Symphonic Rock",
|
||||||
|
"Rock---Technical Death Metal",
|
||||||
|
"Rock---Thrash",
|
||||||
|
"Rock---Twist",
|
||||||
|
"Rock---Viking Metal",
|
||||||
|
"Rock---Y\u00e9-Y\u00e9",
|
||||||
|
"Stage & Screen---Musical",
|
||||||
|
"Stage & Screen---Score",
|
||||||
|
"Stage & Screen---Soundtrack",
|
||||||
|
"Stage & Screen---Theme"
|
||||||
|
],
|
||||||
|
"model_types": ["frozen_model", "SavedModel", "onnx"],
|
||||||
|
"dataset": {
|
||||||
|
"name": "Discogs-4M (unreleased)",
|
||||||
|
"citation": "In-house dataset",
|
||||||
|
"size": "4M full tracks (3.3M used)",
|
||||||
|
"metrics": {
|
||||||
|
"ROC-AUC": 0.95417,
|
||||||
|
"PR-AUC": 0.20629
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "serving_default_model_Placeholder",
|
||||||
|
"type": "float",
|
||||||
|
"shape": ["batch_size", 1280]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "PartitionedCall:0",
|
||||||
|
"type": "float",
|
||||||
|
"shape": ["batch_size", 400],
|
||||||
|
"op": "Sigmoid",
|
||||||
|
"output_purpose": "predictions"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"citation": "@inproceedings{alonso2022music,\n title={Music Representation Learning Based on Editorial Metadata from Discogs},\n author={Alonso-Jim{\\'e}nez, Pablo and Serra, Xavier and Bogdanov, Dmitry},\n booktitle={Conference of the International Society for Music Information Retrieval (ISMIR)},\n year={2022}\n}",
|
||||||
|
"inference": {
|
||||||
|
"sample_rate": 16000,
|
||||||
|
"algorithm": "TensorflowPredict2D",
|
||||||
|
"embedding_model": {
|
||||||
|
"algorithm": "TensorflowPredictEffnetDiscogs",
|
||||||
|
"model_name": "discogs-effnet-bs64-1",
|
||||||
|
"link": "https://essentia.upf.edu/models/music-style-classification/discogs-effnet/discogs-effnet-bs64-1.pb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,339 @@
|
|||||||
|
# benchmark.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import multiprocessing
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
import csv
|
||||||
|
import psutil
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# Importando apenas o necessário (sem sobrescrever as pastas do utils nativo no processamento global)
|
||||||
|
from file_parser import parse_mmp_file
|
||||||
|
from file_saver import save_to_json, save_to_yaml
|
||||||
|
from utils import SRC_MMPSEARCH, MMP_FOLDER
|
||||||
|
|
||||||
|
# === ISOLAMENTO DE DIRETÓRIOS PARA O BENCHMARK ===
|
||||||
|
# Tudo será gerado dentro de "benchmark_output" para não afetar o Jekyll
|
||||||
|
BENCHMARK_OUT = os.path.join(SRC_MMPSEARCH, "benchmark_output")
|
||||||
|
DATA_FOLDER = os.path.join(BENCHMARK_OUT, "data")
|
||||||
|
METADATA_FOLDER = os.path.join(BENCHMARK_OUT, "metadata")
|
||||||
|
WAV_FOLDER = os.path.join(BENCHMARK_OUT, "wav")
|
||||||
|
MMPZ_FOLDER = os.path.join(BENCHMARK_OUT, "mmpz")
|
||||||
|
LOG_FOLDER = os.path.join(BENCHMARK_OUT, "logs")
|
||||||
|
SAIDA_ANALISES = os.path.join(BENCHMARK_OUT, "analises")
|
||||||
|
LOTES_FOLDER = os.path.join(BENCHMARK_OUT, "lotes_yaml")
|
||||||
|
|
||||||
|
# === CONFIGURAÇÕES GERAIS ===
|
||||||
|
TIMEOUT_RENDER_SECONDS = 300
|
||||||
|
MIN_RAM_FREE_MB = 500
|
||||||
|
|
||||||
|
global_counter = None
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(value):
|
||||||
|
value = str(value)
|
||||||
|
value = (
|
||||||
|
unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
|
||||||
|
)
|
||||||
|
value = value.lower()
|
||||||
|
value = re.sub(r"[^\w\s-]", "", value)
|
||||||
|
value = re.sub(r"[-\s_]+", "-", value)
|
||||||
|
return value.strip("-_")
|
||||||
|
|
||||||
|
|
||||||
|
def get_cpu_safe_count():
|
||||||
|
try:
|
||||||
|
count = multiprocessing.cpu_count()
|
||||||
|
return max(1, count - 1)
|
||||||
|
except:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def init_worker(counter):
|
||||||
|
global global_counter
|
||||||
|
global_counter = counter
|
||||||
|
|
||||||
|
|
||||||
|
# === FUNÇÃO ORIGINAL DO SEU CÓDIGO (Com leve ajuste de segurança de arquivos) ===
|
||||||
|
def process_single_file(args):
|
||||||
|
file_name, clean_slug, total_files = args
|
||||||
|
file_path = os.path.join(MMP_FOLDER, file_name)
|
||||||
|
pid = os.getpid()
|
||||||
|
|
||||||
|
global global_counter
|
||||||
|
current_idx = 0
|
||||||
|
if global_counter:
|
||||||
|
with global_counter.get_lock():
|
||||||
|
global_counter.value += 1
|
||||||
|
current_idx = global_counter.value
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
result = {
|
||||||
|
"success": False,
|
||||||
|
"file": file_name,
|
||||||
|
"slug": clean_slug,
|
||||||
|
"data": None,
|
||||||
|
"error": None,
|
||||||
|
"duration_s": 0.0,
|
||||||
|
"ram_mb": 0.0,
|
||||||
|
"file_size_mb": 0.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
result["file_size_mb"] = os.path.getsize(file_path) / (1024 * 1024)
|
||||||
|
|
||||||
|
mem = psutil.virtual_memory()
|
||||||
|
if (mem.available / (1024 * 1024)) < MIN_RAM_FREE_MB:
|
||||||
|
result["error"] = "RAM Insuficiente"
|
||||||
|
return result
|
||||||
|
|
||||||
|
original_base_name = os.path.splitext(file_name)[0]
|
||||||
|
ogg_name = clean_slug + ".ogg"
|
||||||
|
json_name = clean_slug + ".json"
|
||||||
|
yml_name = clean_slug + ".yml"
|
||||||
|
target_mmp_path = ""
|
||||||
|
|
||||||
|
if file_name.endswith(".mmpz"):
|
||||||
|
destination_path = os.path.join(MMPZ_FOLDER, file_name)
|
||||||
|
# ALERTA DE BENCHMARK: Trocado move por copy2 para não destruir o dataset de testes!
|
||||||
|
if not os.path.exists(destination_path):
|
||||||
|
shutil.copy2(file_path, destination_path)
|
||||||
|
|
||||||
|
mmp_temp_name = clean_slug + ".mmp"
|
||||||
|
output_mmp_path = os.path.join(MMP_FOLDER, mmp_temp_name)
|
||||||
|
abs_dest = os.path.abspath(destination_path)
|
||||||
|
abs_mmp_out = os.path.abspath(output_mmp_path)
|
||||||
|
|
||||||
|
with open(abs_mmp_out, "w") as outfile:
|
||||||
|
subprocess.run(
|
||||||
|
["lmms", "--dump", abs_dest],
|
||||||
|
stdout=outfile,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
check=True,
|
||||||
|
timeout=60,
|
||||||
|
env={"QT_QPA_PLATFORM": "offscreen", **os.environ},
|
||||||
|
)
|
||||||
|
target_mmp_path = output_mmp_path
|
||||||
|
elif file_name.endswith(".mmp"):
|
||||||
|
target_mmp_path = file_path
|
||||||
|
|
||||||
|
# Renderização do Áudio
|
||||||
|
if os.path.exists(target_mmp_path):
|
||||||
|
abs_target_mmp = os.path.abspath(target_mmp_path)
|
||||||
|
abs_ogg_out = os.path.abspath(os.path.join(WAV_FOLDER, ogg_name))
|
||||||
|
|
||||||
|
# Aqui estava o errinho do "wav_cmd" que arrumamos na revisão!
|
||||||
|
ogg_cmd = ["lmms", "-r", abs_target_mmp, "-o", abs_ogg_out, "-f", "ogg"]
|
||||||
|
render_process = subprocess.run(
|
||||||
|
ogg_cmd,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=TIMEOUT_RENDER_SECONDS,
|
||||||
|
env={"QT_QPA_PLATFORM": "offscreen", **os.environ},
|
||||||
|
)
|
||||||
|
if render_process.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(
|
||||||
|
render_process.returncode, "lmms render", stderr="Erro LMMS"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parsing do XML
|
||||||
|
if os.path.exists(target_mmp_path):
|
||||||
|
mmp_data = parse_mmp_file(target_mmp_path)
|
||||||
|
if mmp_data:
|
||||||
|
mmp_data["file"] = clean_slug
|
||||||
|
mmp_data["original_title"] = original_base_name
|
||||||
|
save_to_json(mmp_data, os.path.join(METADATA_FOLDER, json_name))
|
||||||
|
save_to_yaml(mmp_data, os.path.join(DATA_FOLDER, yml_name))
|
||||||
|
result["success"] = True
|
||||||
|
result["data"] = mmp_data
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = psutil.Process(pid)
|
||||||
|
# rss = Resident Set Size (RAM real usada pelo processo)
|
||||||
|
result["ram_mb"] = process.memory_info().rss / (1024 * 1024)
|
||||||
|
except:
|
||||||
|
result["ram_mb"] = 0.0
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result["error"] = str(e)
|
||||||
|
|
||||||
|
result["duration_s"] = time.time() - start_time
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# === MOTOR DE BENCHMARK ===
|
||||||
|
def execute_batch(limit_files, mode):
|
||||||
|
"""
|
||||||
|
Executa um lote de arquivos.
|
||||||
|
Se mode == 'sequencial', usa apenas 1 núcleo.
|
||||||
|
Se mode == 'paralelo', usa o safe count de núcleos.
|
||||||
|
"""
|
||||||
|
start_time_global = time.time()
|
||||||
|
|
||||||
|
# Prepara pastas limpas para o teste
|
||||||
|
for folder in [
|
||||||
|
MMPZ_FOLDER,
|
||||||
|
WAV_FOLDER,
|
||||||
|
METADATA_FOLDER,
|
||||||
|
DATA_FOLDER,
|
||||||
|
LOTES_FOLDER,
|
||||||
|
SAIDA_ANALISES,
|
||||||
|
]:
|
||||||
|
os.makedirs(folder, exist_ok=True)
|
||||||
|
|
||||||
|
all_files_raw = [f for f in os.listdir(MMP_FOLDER) if f.endswith((".mmp", ".mmpz"))]
|
||||||
|
if limit_files > 0:
|
||||||
|
all_files_raw = all_files_raw[:limit_files]
|
||||||
|
|
||||||
|
total_files = len(all_files_raw)
|
||||||
|
if total_files == 0:
|
||||||
|
return 0, 0, 0, 0, 0
|
||||||
|
|
||||||
|
shared_counter = multiprocessing.Value("i", 0)
|
||||||
|
tasks = []
|
||||||
|
|
||||||
|
for idx, file_name in enumerate(all_files_raw):
|
||||||
|
slug = slugify(os.path.splitext(file_name)[0])
|
||||||
|
if not slug:
|
||||||
|
slug = f"proj-{idx}"
|
||||||
|
tasks.append((file_name, slug, total_files))
|
||||||
|
|
||||||
|
num_cores = 1 if mode == "sequencial" else get_cpu_safe_count()
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"\n[{mode.upper()}] Processando {total_files} arquivos com {num_cores} núcleo(s)..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execução
|
||||||
|
with multiprocessing.Pool(
|
||||||
|
processes=num_cores, initializer=init_worker, initargs=(shared_counter,)
|
||||||
|
) as pool:
|
||||||
|
results = pool.map(process_single_file, tasks)
|
||||||
|
|
||||||
|
# --- CÁLCULO DE MÉDIAS E SUCESSOS ---
|
||||||
|
sucessos = [r for r in results if r["success"]]
|
||||||
|
qtd_sucessos = len(sucessos)
|
||||||
|
|
||||||
|
avg_time = (
|
||||||
|
sum(r["duration_s"] for r in sucessos) / qtd_sucessos if qtd_sucessos > 0 else 0
|
||||||
|
)
|
||||||
|
avg_size = (
|
||||||
|
sum(r["file_size_mb"] for r in sucessos) / qtd_sucessos
|
||||||
|
if qtd_sucessos > 0
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
avg_ram = (
|
||||||
|
sum(r["ram_mb"] for r in sucessos) / qtd_sucessos if qtd_sucessos > 0 else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
duration_total = time.time() - start_time_global
|
||||||
|
|
||||||
|
# --- SALVAR RELATÓRIO DETALHADO POR LOTE ---
|
||||||
|
detalhado_csv = os.path.join(
|
||||||
|
BENCHMARK_OUT, f"auditoria_detalhada_{mode}_{limit_files}.csv"
|
||||||
|
)
|
||||||
|
with open(detalhado_csv, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(
|
||||||
|
["Arquivo", "Tamanho_MB", "Tempo_s", "RAM_MB", "Status", "Erro"]
|
||||||
|
)
|
||||||
|
for r in results:
|
||||||
|
status_str = "SUCESSO" if r["success"] else "FALHA"
|
||||||
|
writer.writerow(
|
||||||
|
[
|
||||||
|
r["file"],
|
||||||
|
f"{r['file_size_mb']:.2f}",
|
||||||
|
f"{r['duration_s']:.2f}",
|
||||||
|
f"{r['ram_mb']:.2f}",
|
||||||
|
status_str,
|
||||||
|
r["error"] or "",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"[{mode.upper()}] Concluído em {duration_total:.2f}s (Sucessos: {qtd_sucessos}/{total_files})"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"[{mode.upper()}] Médias -> Tempo: {avg_time:.2f}s | Tamanho: {avg_size:.2f}MB | RAM: {avg_ram:.2f}MB"
|
||||||
|
)
|
||||||
|
|
||||||
|
return duration_total, qtd_sucessos, avg_time, avg_size, avg_ram
|
||||||
|
|
||||||
|
|
||||||
|
def run_benchmark():
|
||||||
|
print("==================================================")
|
||||||
|
print(" INICIANDO TESTE DE SPEEDUP (SEQUENCIAL VS PARALELO)")
|
||||||
|
print("==================================================")
|
||||||
|
|
||||||
|
test_sizes = [1, 10, 100, 1000]
|
||||||
|
modes = ["sequencial", "paralelo"]
|
||||||
|
|
||||||
|
results_table = []
|
||||||
|
|
||||||
|
for size in test_sizes:
|
||||||
|
print(f"\n>>> INICIANDO BATERIA PARA {size} ARQUIVO(S) <<<")
|
||||||
|
row = {"Tamanho": size}
|
||||||
|
|
||||||
|
for mode in modes:
|
||||||
|
duration, success_count, avg_time, avg_size, avg_ram = execute_batch(
|
||||||
|
size, mode
|
||||||
|
)
|
||||||
|
row[f"Tempo_Total_{mode}_(s)"] = round(duration, 2)
|
||||||
|
row[f"Tempo_Medio_Proj_{mode}_(s)"] = round(avg_time, 2)
|
||||||
|
row[f"Tamanho_Medio_Proj_(MB)"] = round(
|
||||||
|
avg_size, 2
|
||||||
|
) # O tamanho é igual para ambos, mas guardamos
|
||||||
|
row[f"RAM_Media_{mode}_(MB)"] = round(avg_ram, 2)
|
||||||
|
row[f"Sucesso_{mode}"] = success_count
|
||||||
|
|
||||||
|
# Calcula o Speedup (Tempo Total Sequencial / Tempo Total Paralelo)
|
||||||
|
speedup = (
|
||||||
|
row["Tempo_Total_sequencial_(s)"] / row["Tempo_Total_paralelo_(s)"]
|
||||||
|
if row["Tempo_Total_paralelo_(s)"] > 0
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
row["Speedup"] = round(speedup, 2)
|
||||||
|
results_table.append(row)
|
||||||
|
|
||||||
|
print(f"--- Fim da bateria de {size}. Speedup Alcançado: {speedup:.2f}x ---")
|
||||||
|
|
||||||
|
# Salva o resultado final num CSV consolidado
|
||||||
|
os.makedirs(BENCHMARK_OUT, exist_ok=True)
|
||||||
|
csv_file = os.path.join(BENCHMARK_OUT, "resultado_grafico_speedup.csv")
|
||||||
|
|
||||||
|
# Lista de colunas para o CSV final
|
||||||
|
colunas = [
|
||||||
|
"Tamanho",
|
||||||
|
"Speedup",
|
||||||
|
"Tempo_Total_sequencial_(s)",
|
||||||
|
"Tempo_Total_paralelo_(s)",
|
||||||
|
"Tempo_Medio_Proj_sequencial_(s)",
|
||||||
|
"Tempo_Medio_Proj_paralelo_(s)",
|
||||||
|
"RAM_Media_sequencial_(MB)",
|
||||||
|
"RAM_Media_paralelo_(MB)",
|
||||||
|
"Tamanho_Medio_Proj_(MB)",
|
||||||
|
"Sucesso_sequencial",
|
||||||
|
"Sucesso_paralelo",
|
||||||
|
]
|
||||||
|
|
||||||
|
with open(csv_file, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=colunas)
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(results_table)
|
||||||
|
|
||||||
|
print("\n==================================================")
|
||||||
|
print(f"BENCHMARK FINALIZADO! Resultados salvos em:\n{BENCHMARK_OUT}")
|
||||||
|
print("==================================================")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_benchmark()
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
nohup: ignoring input
|
||||||
|
==================================================
|
||||||
|
INICIANDO TESTE DE SPEEDUP (SEQUENCIAL VS PARALELO)
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
>>> INICIANDO BATERIA PARA 1 ARQUIVO(S) <<<
|
||||||
|
|
||||||
|
[SEQUENCIAL] Processando 1 arquivos com 1 núcleo(s)...
|
||||||
|
[SEQUENCIAL] Concluído em 43.78s (Sucessos: 1/1)
|
||||||
|
[SEQUENCIAL] Médias -> Tempo: 43.69s | Tamanho: 0.54MB | RAM: 27.22MB
|
||||||
|
|
||||||
|
[PARALELO] Processando 1 arquivos com 11 núcleo(s)...
|
||||||
|
[PARALELO] Concluído em 46.51s (Sucessos: 1/1)
|
||||||
|
[PARALELO] Médias -> Tempo: 46.48s | Tamanho: 0.54MB | RAM: 27.58MB
|
||||||
|
--- Fim da bateria de 1. Speedup Alcançado: 0.94x ---
|
||||||
|
|
||||||
|
>>> INICIANDO BATERIA PARA 10 ARQUIVO(S) <<<
|
||||||
|
|
||||||
|
[SEQUENCIAL] Processando 10 arquivos com 1 núcleo(s)...
|
||||||
|
Process ForkPoolWorker-13:
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/pool.py", line 131, in worker
|
||||||
|
put((job, i, result))
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/queues.py", line 399, in put
|
||||||
|
self._writer.send_bytes(obj)
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/connection.py", line 200, in send_bytes
|
||||||
|
self._send_bytes(m[offset:offset + size])
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/connection.py", line 420, in _send_bytes
|
||||||
|
self._send(header)
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/connection.py", line 384, in _send
|
||||||
|
n = write(self._handle, buf)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
BrokenPipeError: [Errno 32] Broken pipe
|
||||||
|
|
||||||
|
During handling of the above exception, another exception occurred:
|
||||||
|
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
|
||||||
|
self.run()
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/process.py", line 108, in run
|
||||||
|
self._target(*self._args, **self._kwargs)
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/pool.py", line 136, in worker
|
||||||
|
put((job, i, (False, wrapped)))
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/queues.py", line 399, in put
|
||||||
|
self._writer.send_bytes(obj)
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/connection.py", line 200, in send_bytes
|
||||||
|
self._send_bytes(m[offset:offset + size])
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/connection.py", line 420, in _send_bytes
|
||||||
|
self._send(header)
|
||||||
|
File "/usr/lib/python3.12/multiprocessing/connection.py", line 384, in _send
|
||||||
|
n = write(self._handle, buf)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
BrokenPipeError: [Errno 32] Broken pipe
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
import csv
|
||||||
|
import psutil
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from urllib.parse import urljoin, urlparse, parse_qs
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
from utils import (
|
||||||
|
SRC_MMPSEARCH,
|
||||||
|
MMP_FOLDER,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ================= CONFIGURAÇÕES PADRONIZADAS =================
|
||||||
|
BASE_DIR = SRC_MMPSEARCH
|
||||||
|
PASTA_DESTINO = MMP_FOLDER # Pasta de entrada do pipeline
|
||||||
|
LOG_DIR = os.path.join(BASE_DIR, "logs/crawler")
|
||||||
|
ARQUIVO_AUDITORIA = os.path.join(LOG_DIR, "audit_crawler.csv")
|
||||||
|
|
||||||
|
# Segurança de Hardware
|
||||||
|
MIN_RAM_FREE_MB = 500 # Pausa se tiver menos que 500MB livre
|
||||||
|
MAX_WORKERS_SAFE = 4 # I/O Bound não precisa de tantos workers quanto CPU, mas 4 é seguro
|
||||||
|
|
||||||
|
HEADERS = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Compatible; MMPResearchBot/1.0; +http://seu-site.com)'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cria pastas
|
||||||
|
for p in [PASTA_DESTINO, LOG_DIR]:
|
||||||
|
os.makedirs(p, exist_ok=True)
|
||||||
|
|
||||||
|
# ================= FUNÇÕES AUXILIARES =================
|
||||||
|
|
||||||
|
def slugify(value):
|
||||||
|
"""Sanitização robusta de nomes de arquivo (Padrão do Pipeline)"""
|
||||||
|
value = str(value)
|
||||||
|
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
|
||||||
|
value = value.lower()
|
||||||
|
value = re.sub(r'[^\w\s-]', '', value)
|
||||||
|
value = re.sub(r'[-\s_]+', '-', value)
|
||||||
|
return value.strip('-_')
|
||||||
|
|
||||||
|
def verificar_memoria_segura():
|
||||||
|
"""Retorna True se é seguro continuar"""
|
||||||
|
try:
|
||||||
|
mem = psutil.virtual_memory()
|
||||||
|
return (mem.available / (1024 * 1024)) > MIN_RAM_FREE_MB
|
||||||
|
except:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ================= CLASSE DE AUDITORIA =================
|
||||||
|
class AuditoriaCrawler:
|
||||||
|
def __init__(self, arquivo_csv):
|
||||||
|
self.arquivo_csv = arquivo_csv
|
||||||
|
self.inicio_global = time.time()
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
# Cria CSV se não existir
|
||||||
|
if not os.path.exists(self.arquivo_csv):
|
||||||
|
with open(self.arquivo_csv, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow([
|
||||||
|
"Timestamp", "Nome_Original", "Slug_Final",
|
||||||
|
"Tamanho_MB", "Tempo_DL_s", "Velocidade_KBps",
|
||||||
|
"Status", "URL_Origem"
|
||||||
|
])
|
||||||
|
|
||||||
|
def registrar_download(self, nome_orig, slug, tamanho_bytes, tempo_s, url, status="SUCESSO"):
|
||||||
|
with self.lock:
|
||||||
|
try:
|
||||||
|
tamanho_mb = tamanho_bytes / (1024 * 1024)
|
||||||
|
velocidade = (tamanho_bytes / 1024) / tempo_s if tempo_s > 0 else 0
|
||||||
|
ts = datetime.now().strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
with open(self.arquivo_csv, 'a', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow([
|
||||||
|
ts, nome_orig, slug,
|
||||||
|
f"{tamanho_mb:.2f}", f"{tempo_s:.2f}",
|
||||||
|
f"{velocidade:.2f}", status, url
|
||||||
|
])
|
||||||
|
|
||||||
|
# Log Console Simplificado
|
||||||
|
print(f"[{ts}] {status}: {slug} ({tamanho_mb:.2f}MB @ {velocidade:.0f}KB/s)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao auditar: {e}")
|
||||||
|
|
||||||
|
# ================= WORKER =================
|
||||||
|
|
||||||
|
def baixar_projeto_worker(args):
|
||||||
|
url_detalhes, auditor = args
|
||||||
|
|
||||||
|
# Check de Segurança (Throttle)
|
||||||
|
if not verificar_memoria_segura():
|
||||||
|
time.sleep(5) # Espera RAM liberar
|
||||||
|
if not verificar_memoria_segura():
|
||||||
|
return (False, "RAM Crítica - Download Abortado")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Pega página de detalhes
|
||||||
|
resp = requests.get(url_detalhes, headers=HEADERS, timeout=15)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
auditor.registrar_download("N/A", "N/A", 0, 0, url_detalhes, f"HTTP_{resp.status_code}")
|
||||||
|
return (False, f"HTTP {resp.status_code}")
|
||||||
|
|
||||||
|
soup = BeautifulSoup(resp.text, 'html.parser')
|
||||||
|
|
||||||
|
# Lógica de encontrar link de download (Manteve a sua, que é boa)
|
||||||
|
link_tag = soup.find('a', href=lambda h: h and 'download_file.php' in h)
|
||||||
|
if not link_tag:
|
||||||
|
link_tag = soup.find('a', string='Download')
|
||||||
|
|
||||||
|
if link_tag:
|
||||||
|
url_download = urljoin(url_detalhes, link_tag['href'])
|
||||||
|
|
||||||
|
inicio_dl = time.time()
|
||||||
|
|
||||||
|
# Request do Arquivo
|
||||||
|
with requests.get(url_download, headers=HEADERS, stream=True, timeout=60) as r_file:
|
||||||
|
r_file.raise_for_status()
|
||||||
|
|
||||||
|
# --- DESCIFRAR NOME (COM SANITIZAÇÃO) ---
|
||||||
|
nome_bruto = ""
|
||||||
|
if "Content-Disposition" in r_file.headers:
|
||||||
|
cd = r_file.headers["Content-Disposition"]
|
||||||
|
if "filename=" in cd:
|
||||||
|
nome_bruto = cd.split("filename=")[1].strip('"')
|
||||||
|
|
||||||
|
if not nome_bruto:
|
||||||
|
query = parse_qs(urlparse(url_download).query)
|
||||||
|
nome_bruto = query['name'][0] if 'name' in query else f"proj_{int(time.time())}.mmpz"
|
||||||
|
|
||||||
|
# Aplica Slugify (Padrão do Pipeline)
|
||||||
|
nome_base, ext = os.path.splitext(nome_bruto)
|
||||||
|
if not ext: ext = ".mmpz" # Default seguro
|
||||||
|
slug = slugify(nome_base)
|
||||||
|
nome_final = f"{slug}{ext}"
|
||||||
|
|
||||||
|
caminho_final = os.path.join(PASTA_DESTINO, nome_final)
|
||||||
|
|
||||||
|
# Skip se já existe
|
||||||
|
if os.path.exists(caminho_final):
|
||||||
|
auditor.registrar_download(nome_bruto, slug, os.path.getsize(caminho_final), 0, url_detalhes, "SKIPPED_EXISTS")
|
||||||
|
return (True, "Já existe")
|
||||||
|
|
||||||
|
# Gravação em chunks (Seguro para RAM)
|
||||||
|
bytes_downloaded = 0
|
||||||
|
with open(caminho_final, 'wb') as f:
|
||||||
|
for chunk in r_file.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
bytes_downloaded += len(chunk)
|
||||||
|
|
||||||
|
tempo_total = time.time() - inicio_dl
|
||||||
|
|
||||||
|
# Auditoria de Sucesso
|
||||||
|
auditor.registrar_download(nome_bruto, slug, bytes_downloaded, tempo_total, url_detalhes, "SUCESSO")
|
||||||
|
return (True, "OK")
|
||||||
|
|
||||||
|
else:
|
||||||
|
auditor.registrar_download("N/A", "N/A", 0, 0, url_detalhes, "LINK_NOT_FOUND")
|
||||||
|
return (False, "Link não encontrado")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
auditor.registrar_download("Erro", "Erro", 0, 0, url_detalhes, f"EXCEPT_{str(e)[:20]}")
|
||||||
|
return (False, str(e))
|
||||||
|
|
||||||
|
# ================= MAIN =================
|
||||||
|
|
||||||
|
def crawler_manager(url_base, max_paginas=2):
|
||||||
|
print(f"\n=== INICIANDO CRAWLER PADRONIZADO ===")
|
||||||
|
print(f"Destino: {PASTA_DESTINO}")
|
||||||
|
print(f"Auditoria: {ARQUIVO_AUDITORIA}")
|
||||||
|
|
||||||
|
auditor = AuditoriaCrawler(ARQUIVO_AUDITORIA)
|
||||||
|
pagina_atual = 1
|
||||||
|
total_sucesso = 0
|
||||||
|
|
||||||
|
# Executor seguro
|
||||||
|
with ThreadPoolExecutor(max_workers=MAX_WORKERS_SAFE) as executor:
|
||||||
|
while pagina_atual <= max_paginas:
|
||||||
|
print(f"\n--- Varrendo Página {pagina_atual} ---")
|
||||||
|
|
||||||
|
operador = '&' if '?' in url_base else '?'
|
||||||
|
url_pag = f"{url_base}{operador}page={pagina_atual}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Pega a lista de projetos da página
|
||||||
|
r = requests.get(url_pag, headers=HEADERS)
|
||||||
|
soup = BeautifulSoup(r.text, 'html.parser')
|
||||||
|
# Filtro específico do LMMS Sharing Platform
|
||||||
|
links = soup.find_all('a', href=lambda h: h and '?action=show&file=' in h)
|
||||||
|
urls_unicas = sorted(list(set([l['href'] for l in links])))
|
||||||
|
|
||||||
|
if not urls_unicas:
|
||||||
|
print("Nenhum projeto encontrado nesta página. Encerrando.")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Submete tarefas
|
||||||
|
futures = []
|
||||||
|
for u in urls_unicas:
|
||||||
|
url_completa = urljoin(url_base, u)
|
||||||
|
futures.append(executor.submit(baixar_projeto_worker, (url_completa, auditor)))
|
||||||
|
|
||||||
|
# Processa resultados conforme chegam
|
||||||
|
for future in as_completed(futures):
|
||||||
|
ok, msg = future.result()
|
||||||
|
if ok: total_sucesso += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro na paginação: {e}")
|
||||||
|
|
||||||
|
pagina_atual += 1
|
||||||
|
time.sleep(1) # Respeito ao servidor
|
||||||
|
|
||||||
|
tempo_total = time.time() - auditor.inicio_global
|
||||||
|
print(f"\n=== CRAWLER FINALIZADO ===")
|
||||||
|
print(f"Tempo: {timedelta(seconds=int(tempo_total))}")
|
||||||
|
print(f"Downloads com Sucesso: {total_sucesso}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# URL de exemplo (Projetos ordenados por data)
|
||||||
|
URL_ALVO = 'https://lmms.io/lsp/?action=browse&category=Projects&sort=date'
|
||||||
|
|
||||||
|
crawler_manager(URL_ALVO, max_paginas=499)
|
||||||
Binary file not shown.
Executable
+72
@@ -0,0 +1,72 @@
|
|||||||
|
from flask import Flask, request, jsonify, session
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SECRET_KEY'] = '25de5592bf94c2ca18e27baa0ae2d4ee22a63012f32e1be719d31f530c215a387b9ec0c9d96be38e80a7ccdd859e04408facefff8fd9119e7f5a2d987d85abb7' # Troque isso!
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users_mmpSearch.db' # O arquivo do banco
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
bcrypt = Bcrypt(app)
|
||||||
|
login_manager = LoginManager(app)
|
||||||
|
|
||||||
|
# --- Modelo do Banco de Dados ---
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||||
|
password = db.Column(db.String(150), nullable=False)
|
||||||
|
|
||||||
|
# Cria o banco na primeira execução
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return User.query.get(int(user_id))
|
||||||
|
|
||||||
|
# --- Rotas ---
|
||||||
|
|
||||||
|
@app.route('/api/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
data = request.json
|
||||||
|
hashed_password = bcrypt.generate_password_hash(data['password']).decode('utf-8')
|
||||||
|
new_user = User(username=data['username'], password=hashed_password)
|
||||||
|
try:
|
||||||
|
db.session.add(new_user)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({"message": "Usuário criado com sucesso!"}), 201
|
||||||
|
except:
|
||||||
|
return jsonify({"message": "Usuário já existe"}), 400
|
||||||
|
|
||||||
|
@app.route('/api/login', methods=['POST'])
|
||||||
|
def login():
|
||||||
|
data = request.json
|
||||||
|
user = User.query.filter_by(username=data['username']).first()
|
||||||
|
if user and bcrypt.check_password_hash(user.password, data['password']):
|
||||||
|
login_user(user)
|
||||||
|
return jsonify({"message": "Login realizado", "user": user.username}), 200
|
||||||
|
return jsonify({"message": "Credenciais inválidas"}), 401
|
||||||
|
|
||||||
|
@app.route('/api/logout', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return jsonify({"message": "Logout realizado"}), 200
|
||||||
|
|
||||||
|
@app.route('/api/check_auth', methods=['GET'])
|
||||||
|
def check_auth():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return jsonify({"logged_in": True, "user": current_user.username})
|
||||||
|
return jsonify({"logged_in": False})
|
||||||
|
|
||||||
|
# Rota para seus uploads (exemplo de como proteger)
|
||||||
|
@app.route('/api/upload_seguro', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def upload_seguro():
|
||||||
|
# Aqui entraria sua lógica de salvar o arquivo
|
||||||
|
# Como você já tem a lógica pronta, você pode chamar ela aqui
|
||||||
|
# ou mover o código para cá.
|
||||||
|
return jsonify({"message": f"Upload recebido de {current_user.username}"})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(port=33005)
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Interrompe a execução imediatamente se qualquer comando falhar
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Define o caminho absoluto para o seu Python do ambiente virtual
|
||||||
|
PYTHON_BIN="/nethome/jotachina/projetos/mmpSearch/venv/bin/python3"
|
||||||
|
|
||||||
|
# Define o diretório raiz do projeto para garantir que os caminhos relativos funcionem
|
||||||
|
BASE_DIR="/nethome/jotachina/projetos/mmpSearch/scripts"
|
||||||
|
|
||||||
|
echo "================================================================="
|
||||||
|
echo "Iniciando Pipeline Completo do MMPSearch"
|
||||||
|
echo "================================================================="
|
||||||
|
|
||||||
|
# Navega até a raiz do projeto para que os caminhos dos scripts funcionem corretamente
|
||||||
|
cd "$BASE_DIR"
|
||||||
|
|
||||||
|
echo "[1/3] Executando: handler/main.py..."
|
||||||
|
"$PYTHON_BIN" handler/main.py
|
||||||
|
echo "handler/main.py concluído!"
|
||||||
|
echo "-----------------------------------------------------------------"
|
||||||
|
|
||||||
|
echo "[2/3] Executando: classificacao/analise_audio.py..."
|
||||||
|
"$PYTHON_BIN" classificacao/analise_audio.py
|
||||||
|
echo "classificacao/analise_audio.py concluído!"
|
||||||
|
echo "-----------------------------------------------------------------"
|
||||||
|
|
||||||
|
echo "[3/3] Executando: classificacao/classificacao_mestre.py..."
|
||||||
|
"$PYTHON_BIN" classificacao/classificacao_mestre.py
|
||||||
|
echo "classificacao/classificacao_mestre.py concluído!"
|
||||||
|
echo "================================================================="
|
||||||
|
echo "Pipeline finalizado com sucesso!"
|
||||||
|
echo "================================================================="
|
||||||
Reference in new issue
Block a user