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