resolvendo dependencias dos projetos
Deploy / Deploy (push) Successful in 1m48s Details

This commit is contained in:
JotaChina 2025-12-08 17:47:53 -03:00
parent 7c2b1897ba
commit be62cb7c11
7 changed files with 2578 additions and 98 deletions

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,7 @@
- deep-house-vespertine-feat-georg-no-days-off.wav - deep-house-vespertine-feat-georg-no-days-off.wav
- demo-aesthetescence.wav - demo-aesthetescence.wav
- dr-dre.wav - dr-dre.wav
- drake.wav
- dreamhop-animal-l-bonus-r0und-ep.wav - dreamhop-animal-l-bonus-r0und-ep.wav
- drifting-rev-d-csammis-track-1.wav - drifting-rev-d-csammis-track-1.wav
- dubstep-fyrebreak-saichania-original-mix.wav - dubstep-fyrebreak-saichania-original-mix.wav

1191
_data/drake.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,9 @@
{ {
"imported": {}, "imported": {
"bassdrum_acoustic01_-_Copia.ogg": {
"_isFile": true
}
},
"samples": {}, "samples": {},
"drumsynth": { "drumsynth": {
"misc_synth": { "misc_synth": {

View File

@ -169,42 +169,42 @@ permalink: /envie_seu_projeto/
<div id="resolve-modal" class="modal"> <div id="resolve-modal" class="modal">
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card" style="width: 600px; max-width: 95%;">
<header class="modal-card-head has-background-warning-light"> <header class="modal-card-head has-background-warning-light">
<p class="modal-card-title has-text-warning-dark">Atenção: Arquivos Faltando</p> <p class="modal-card-title has-text-warning-dark">
<span class="icon mr-2"><i class="fa-solid fa-link-slash"></i></span>
Arquivos Externos Detectados
</p>
<button class="delete" aria-label="close"></button> <button class="delete" aria-label="close"></button>
</header> </header>
<section class="modal-card-body p-5">
<div class="notification is-warning is-light">
<span class="icon"><i class="fa-solid fa-triangle-exclamation"></i></span>
<strong>Seu projeto usa samples externos!</strong>
<p class="mt-2">Para que o projeto funcione para todos, precisamos dos seguintes arquivos:</p>
</div>
<div class="box"> <section class="modal-card-body">
<ul id="missing-files-list" style="list-style: disc; margin-left: 20px; color: #d63031;"> <div class="content is-small mb-4">
</ul> <p>Este projeto utiliza samples que não são nativos do LMMS. Para que o som saia correto, precisamos que você envie os arquivos correspondentes.</p>
</div> </div>
<form id="resolve-form"> <form id="resolve-form">
<input type="hidden" name="project_file" id="hidden-project-name"> <input type="hidden" name="project_file" id="hidden-project-name">
<div class="field"> <div class="table-container">
<label class="label">Envie os arquivos listados acima:</label> <table class="table is-fullwidth is-striped is-hoverable">
<div class="file has-name is-fullwidth is-warning"> <thead>
<label class="file-label"> <tr>
<input class="file-input" type="file" name="sample_files" multiple required> <th>Arquivo Faltante (XML)</th>
<span class="file-cta"> <th style="width: 50px;">Status</th>
<span class="file-icon"><i class="fa-solid fa-upload"></i></span> <th style="width: 120px;">Ação</th>
<span class="file-label">Selecionar Samples...</span> </tr>
</span> </thead>
<span class="file-name" id="resolve-file-name">Nenhum selecionado</span> <tbody id="missing-files-table-body">
</label> </tbody>
</div> </table>
</div> </div>
<button type="submit" class="button is-warning is-fullwidth mt-4" id="resolve-btn"> <div class="notification is-danger is-light is-hidden" id="resolve-error-msg"></div>
Enviar Arquivos e Finalizar
<button type="submit" class="button is-success is-fullwidth mt-4" id="resolve-btn" disabled>
<span class="icon"><i class="fa-solid fa-check"></i></span>
<span>Enviar Todos e Processar</span>
</button> </button>
</form> </form>
</section> </section>
@ -396,24 +396,95 @@ document.addEventListener('DOMContentLoaded', () => {
document.documentElement.classList.add('is-clipped'); document.documentElement.classList.add('is-clipped');
} }
// --- FUNÇÃO PARA GERAR A TABELA DE UPLOAD ---
function mostrarResolveModal(data) { function mostrarResolveModal(data) {
// 1. Preenche o input hidden com o nome do projeto (retornado pelo backend) // 1. Configurações iniciais
document.getElementById('hidden-project-name').value = data.project_file; document.getElementById('hidden-project-name').value = data.project_file;
const tbody = document.getElementById('missing-files-table-body');
tbody.innerHTML = ''; // Limpa tabela
// 2. Preenche a lista visual de arquivos faltando const resolveBtn = document.getElementById('resolve-btn');
const list = document.getElementById('missing-files-list'); resolveBtn.disabled = true; // Começa desabilitado até preencher tudo
list.innerHTML = ''; // Limpa lista anterior
if (data.missing_files && data.missing_files.length > 0) { // Controle de validação
data.missing_files.forEach(file => { let totalFiles = data.missing_files.length;
const li = document.createElement('li'); let filledFiles = 0;
li.textContent = file;
list.appendChild(li); // 2. Gera uma linha para cada arquivo faltante
data.missing_files.forEach((originalName, index) => {
const tr = document.createElement('tr');
// Coluna 1: Nome do arquivo
const tdName = document.createElement('td');
tdName.style.verticalAlign = "middle";
tdName.innerHTML = `<span class="has-text-grey-dark is-family-code">${originalName}</span>`;
// Coluna 2: Ícone de Status
const tdStatus = document.createElement('td');
tdStatus.style.verticalAlign = "middle";
tdStatus.style.textAlign = "center";
tdStatus.innerHTML = `<span class="icon has-text-grey-light status-icon"><i class="fa-solid fa-circle-xmark"></i></span>`;
// Coluna 3: Botão de Upload
const tdAction = document.createElement('td');
tdAction.style.textAlign = "right";
// Cria o input file invisível
// O 'name' do input é o NOME ORIGINAL DO ARQUIVO. O Backend usará isso para mapear.
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.name = originalName; // <--- O PULO DO GATO
fileInput.style.display = 'none';
fileInput.accept = ".wav, .mp3, .ogg, .flac, .ds";
// Botão bonito que aciona o input
const uploadLabel = document.createElement('label');
uploadLabel.className = 'button is-small is-info is-outlined';
uploadLabel.innerHTML = `<span class="icon"><i class="fa-solid fa-upload"></i></span>`;
// Evento: Ao clicar no botão, abre o seletor de arquivo
uploadLabel.addEventListener('click', (e) => {
e.preventDefault(); // Evita submit
fileInput.click();
}); });
}
// 3. Abre o modal // Evento: Quando o usuário seleciona um arquivo
resolveModal.classList.add('is-active'); fileInput.addEventListener('change', () => {
if (fileInput.files.length > 0) {
// Muda visual para sucesso
uploadLabel.className = 'button is-small is-success';
uploadLabel.innerHTML = `<span class="icon"><i class="fa-solid fa-rotate"></i></span>`; // Ícone de trocar
const icon = tdStatus.querySelector('.status-icon');
icon.innerHTML = `<i class="fa-solid fa-circle-check"></i>`;
icon.classList.remove('has-text-grey-light');
icon.classList.add('has-text-success');
// Lógica de validação (simples)
if (!fileInput.dataset.filled) {
fileInput.dataset.filled = "true";
filledFiles++;
}
// Se todos foram preenchidos, habilita o botão final
if (filledFiles >= totalFiles) {
resolveBtn.disabled = false;
resolveBtn.classList.remove('is-outlined');
}
}
});
tdAction.appendChild(fileInput);
tdAction.appendChild(uploadLabel);
tr.appendChild(tdName);
tr.appendChild(tdStatus);
tr.appendChild(tdAction);
tbody.appendChild(tr);
});
// 3. Exibe o modal
document.getElementById('resolve-modal').classList.add('is-active');
document.documentElement.classList.add('is-clipped'); document.documentElement.classList.add('is-clipped');
} }

View File

@ -13,19 +13,11 @@ from werkzeug.utils import secure_filename
# Importa suas funções e configurações existentes # Importa suas funções e configurações existentes
from main import process_single_file, rebuild_indexes, generate_manifests, slugify from main import process_single_file, rebuild_indexes, generate_manifests, slugify
from utils import MMP_FOLDER, MMPZ_FOLDER, CERT_PATH, KEY_PATH, BASE_DATA, SRC_MMPSEARCH, SAMPLE_SRC, METADATA_FOLDER from utils import ALLOWED_EXTENSIONS, ALLOWED_SAMPLE_EXTENSIONS, MMP_FOLDER, MMPZ_FOLDER, CERT_PATH, KEY_PATH, BASE_DATA, SRC_MMPSEARCH, SAMPLE_SRC, METADATA_FOLDER, XML_IMPORTED_PATH_PREFIX, SAMPLE_MANIFEST
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
ALLOWED_EXTENSIONS = {"mmp", "mmpz"}
ALLOWED_SAMPLE_EXTENSIONS = {'wav', 'mp3', 'ogg', 'flac', 'ds'}
# --- CONFIGURAÇÕES ---
MANIFEST_PATH = os.path.join(METADATA_FOLDER, "samples-manifest.json")
XML_IMPORTED_PATH_PREFIX = "src_mmpSearch/samples/imported"
PHYSICAL_IMPORTED_FOLDER = os.path.join(SAMPLE_SRC, "imported")
def allowed_file(filename): def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
@ -43,10 +35,10 @@ def run_jekyll_build():
def load_manifest_keys(): def load_manifest_keys():
"""Carrega as pastas de fábrica (keys do manifesto).""" """Carrega as pastas de fábrica (keys do manifesto)."""
if not os.path.exists(MANIFEST_PATH): if not os.path.exists(SAMPLE_MANIFEST):
return ["drums", "instruments", "effects", "presets", "samples"] return ["drums", "instruments", "effects", "presets", "samples"]
try: try:
with open(MANIFEST_PATH, 'r', encoding='utf-8') as f: with open(SAMPLE_MANIFEST, 'r', encoding='utf-8') as f:
data = json.load(f) data = json.load(f)
return list(data.keys()) return list(data.keys())
except Exception: except Exception:
@ -173,44 +165,54 @@ def analyze_mmp_dependencies(mmp_filename):
return len(missing_samples) == 0, list(missing_samples) return len(missing_samples) == 0, list(missing_samples)
def update_xml_paths(mmp_filename, file_map): def update_xml_paths_exact(mmp_filename, replacements):
""" """
Atualiza os caminhos no XML para apontar para a pasta imported. Substitui caminhos no XML baseando-se no mapeamento exato:
replacements = { 'nome_original_no_xml.wav': 'novo_nome_seguro.wav' }
""" """
filepath = os.path.join(MMP_FOLDER, mmp_filename) filepath = os.path.join(MMP_FOLDER, mmp_filename)
try: try:
tree = ET.parse(filepath) tree = ET.parse(filepath)
root = tree.getroot() root = tree.getroot()
factory_folders = load_manifest_keys()
changes_made = False changes_made = False
# Normaliza as chaves para minúsculo para garantir o match
# Ex: {'Kick.wav': 'kick.wav'} -> {'kick.wav': 'kick.wav'}
replacements_lower = {k.lower(): v for k, v in replacements.items()}
for audio_node in root.findall(".//audiofileprocessor"): for audio_node in root.findall(".//audiofileprocessor"):
src = audio_node.get('src', '') src = audio_node.get('src', '')
if not src: continue if not src: continue
is_factory = any(src.startswith(folder + "/") for folder in factory_folders) # Pega o nome do arquivo que está atualmente no XML
is_already_imported = src.startswith(XML_IMPORTED_PATH_PREFIX) current_basename = os.path.basename(src)
current_basename_lower = current_basename.lower()
if not is_factory and not is_already_imported: # Verifica se esse nome está na lista de substituições
base_name = os.path.basename(src) if current_basename_lower in replacements_lower:
new_filename = replacements_lower[current_basename_lower]
# Verifica se temos esse arquivo mapeado (upload do usuário) # Constrói o novo caminho completo
if base_name in file_map: new_src = f"{XML_IMPORTED_PATH_PREFIX}/{new_filename}"
safe_name = file_map[base_name]
new_src = f"{XML_IMPORTED_PATH_PREFIX}/{safe_name}" # Aplica a alteração
audio_node.set('src', new_src) audio_node.set('src', new_src)
audio_node.set('vol', audio_node.get('vol', '1')) audio_node.set('vol', audio_node.get('vol', '1')) # Garante volume
changes_made = True
print(f"Path corrigido: {base_name} -> {new_src}") print(f"[XML FIX] Substituído: {current_basename} -> {new_src}")
changes_made = True
if changes_made: if changes_made:
tree.write(filepath, encoding='UTF-8', xml_declaration=True) tree.write(filepath, encoding='UTF-8', xml_declaration=True)
print(f"Projeto {mmp_filename} atualizado com novos caminhos.") print(f"Projeto {mmp_filename} salvo com novos caminhos.")
return True
else:
print("Aviso: Nenhum caminho foi alterado no XML (match não encontrado).")
return True # Retorna True para não travar o processo, mas avisa no log
return True
except Exception as e: except Exception as e:
print(f"Erro ao atualizar XML: {e}") print(f"Erro crítico ao editar XML: {e}")
return False return False
# --- ROTAS --- # --- ROTAS ---
@ -289,43 +291,59 @@ def upload_file():
return jsonify({"error": "Tipo de arquivo não permitido"}), 400 return jsonify({"error": "Tipo de arquivo não permitido"}), 400
@app.route("/api/upload/resolve", methods=["POST"]) @app.route("/api/upload/resolve", methods=["POST"])
def resolve_samples(): def resolve_samples():
""" """
Recebe o nome do arquivo .mmp ( existente no servidor) e os samples. Recebe inputs onde:
name="nome_original.wav" (chave) -> value=Arquivo (conteúdo)
""" """
project_filename = request.form.get('project_file') # Espera o nome .mmp project_filename = request.form.get('project_file') # Nome do .mmp
uploaded_files = request.files.getlist("sample_files")
if not project_filename or not uploaded_files: if not project_filename:
return jsonify({"error": "Dados incompletos"}), 400 return jsonify({"error": "Nome do projeto não informado"}), 400
# Garante pasta # Cria pasta imported
os.makedirs(PHYSICAL_IMPORTED_FOLDER, exist_ok=True) os.makedirs(XML_IMPORTED_PATH_PREFIX, exist_ok=True)
os.chmod(PHYSICAL_IMPORTED_FOLDER, 0o775) try:
os.chmod(XML_IMPORTED_PATH_PREFIX, 0o775)
except: pass
file_map = {} # Dicionário de substituição: { 'Original.wav': 'Novo_Seguro.wav' }
replacements = {}
# 1. Salva os samples # request.files é um dicionário imutável, iteramos sobre ele
for file in uploaded_files: for original_name, file_storage in request.files.items():
if file and allowed_sample(file.filename): if file_storage and allowed_sample(file_storage.filename):
clean_sample_name = secure_filename(file.filename)
save_path = os.path.join(PHYSICAL_IMPORTED_FOLDER, clean_sample_name)
file.save(save_path) # 1. Salva o arquivo fisicamente
os.chmod(save_path, 0o664) # Usamos secure_filename no arquivo NOVO para evitar problemas no disco
safe_new_name = secure_filename(file_storage.filename)
# Mapeia nome original -> nome salvo # Se secure_filename deixou vazio (ex: "???"), gera um nome
file_map[file.filename] = clean_sample_name if not safe_new_name:
file_map[clean_sample_name] = clean_sample_name # Fallback safe_new_name = f"sample_{int(time.time())}.wav"
# 2. Atualiza o XML (.mmp) save_path = os.path.join(XML_IMPORTED_PATH_PREFIX, safe_new_name)
if update_xml_paths(project_filename, file_map): file_storage.save(save_path)
# 3. Processa e Builda
try:
os.chmod(save_path, 0o664)
except: pass
# 2. Mapeia para substituição no XML
# A chave 'original_name' vem do `name` do input HTML, que definimos como o nome original
replacements[original_name] = safe_new_name
if not replacements:
return jsonify({"error": "Nenhum arquivo válido enviado."}), 400
# 3. Atualiza o XML
if update_xml_paths_exact(project_filename, replacements):
# 4. Processa (Gera WAV na pasta correta e JSON)
return process_and_build(project_filename) return process_and_build(project_filename)
else: else:
return jsonify({"error": "Falha ao atualizar referências no projeto."}), 500 return jsonify({"error": "Falha ao atualizar o arquivo de projeto."}), 500
def process_and_build(filename): def process_and_build(filename):
"""Encapsula a chamada do main.py""" """Encapsula a chamada do main.py"""

View File

@ -15,6 +15,7 @@ SAMPLE_MANIFEST = os.path.join(BASE_PATH, "src_mmpSearch", "metadata", "samples-
SAMPLE_SRC = os.path.join(SRC_MMPSEARCH, "samples") SAMPLE_SRC = os.path.join(SRC_MMPSEARCH, "samples")
MMP_MANIFEST = os.path.join(BASE_PATH, "src_mmpSearch", "metadata", "mmp-manifest.json") MMP_MANIFEST = os.path.join(BASE_PATH, "src_mmpSearch", "metadata", "mmp-manifest.json")
DATA_FOLDER = os.path.join(BASE_DATA, "_data") DATA_FOLDER = os.path.join(BASE_DATA, "_data")
XML_IMPORTED_PATH_PREFIX = os.path.join(SAMPLE_SRC, "imported")
SAMPLE_MANIFEST_DATA = os.path.join(DATA_FOLDER, "samples-manifest.json") SAMPLE_MANIFEST_DATA = os.path.join(DATA_FOLDER, "samples-manifest.json")
LOG_FOLDER = os.path.join(SRC_MMPSEARCH, "logs", "handler_logs") LOG_FOLDER = os.path.join(SRC_MMPSEARCH, "logs", "handler_logs")
CERT_PATH = "/etc/letsencrypt/live/alice.ufsj.edu.br/fullchain.pem" CERT_PATH = "/etc/letsencrypt/live/alice.ufsj.edu.br/fullchain.pem"
@ -96,6 +97,9 @@ SUPPORTED_PLUGINS = [
"zynaddsubfx", "zynaddsubfx",
] ]
ALLOWED_EXTENSIONS = {"mmp", "mmpz"}
ALLOWED_SAMPLE_EXTENSIONS = {'wav', 'mp3', 'ogg', 'flac', 'ds'}
tags = {} tags = {}
tags["TAG"] = [] tags["TAG"] = []
tags["plugin"] = [] tags["plugin"] = []