mmpSearch/scripts/classificacao/classificador_mestre.py

275 lines
8.5 KiB
Python

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()