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
- demo-aesthetescence.wav
- dr-dre.wav
- drake.wav
- dreamhop-animal-l-bonus-r0und-ep.wav
- drifting-rev-d-csammis-track-1.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": {},
"drumsynth": {
"misc_synth": {

View File

@ -169,42 +169,42 @@ permalink: /envie_seu_projeto/
<div id="resolve-modal" class="modal">
<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">
<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>
</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">
<ul id="missing-files-list" style="list-style: disc; margin-left: 20px; color: #d63031;">
</ul>
<section class="modal-card-body">
<div class="content is-small mb-4">
<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>
<form id="resolve-form">
<input type="hidden" name="project_file" id="hidden-project-name">
<div class="field">
<label class="label">Envie os arquivos listados acima:</label>
<div class="file has-name is-fullwidth is-warning">
<label class="file-label">
<input class="file-input" type="file" name="sample_files" multiple required>
<span class="file-cta">
<span class="file-icon"><i class="fa-solid fa-upload"></i></span>
<span class="file-label">Selecionar Samples...</span>
</span>
<span class="file-name" id="resolve-file-name">Nenhum selecionado</span>
</label>
</div>
<div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>Arquivo Faltante (XML)</th>
<th style="width: 50px;">Status</th>
<th style="width: 120px;">Ação</th>
</tr>
</thead>
<tbody id="missing-files-table-body">
</tbody>
</table>
</div>
<button type="submit" class="button is-warning is-fullwidth mt-4" id="resolve-btn">
Enviar Arquivos e Finalizar
<div class="notification is-danger is-light is-hidden" id="resolve-error-msg"></div>
<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>
</form>
</section>
@ -396,24 +396,95 @@ document.addEventListener('DOMContentLoaded', () => {
document.documentElement.classList.add('is-clipped');
}
// --- FUNÇÃO PARA GERAR A TABELA DE UPLOAD ---
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;
const tbody = document.getElementById('missing-files-table-body');
tbody.innerHTML = ''; // Limpa tabela
// 2. Preenche a lista visual de arquivos faltando
const list = document.getElementById('missing-files-list');
list.innerHTML = ''; // Limpa lista anterior
const resolveBtn = document.getElementById('resolve-btn');
resolveBtn.disabled = true; // Começa desabilitado até preencher tudo
if (data.missing_files && data.missing_files.length > 0) {
data.missing_files.forEach(file => {
const li = document.createElement('li');
li.textContent = file;
list.appendChild(li);
// Controle de validação
let totalFiles = data.missing_files.length;
let filledFiles = 0;
// 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();
});
// Evento: Quando o usuário seleciona um arquivo
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++;
}
// 3. Abre o modal
resolveModal.classList.add('is-active');
// 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');
}

View File

@ -13,19 +13,11 @@ from werkzeug.utils import secure_filename
# Importa suas funções e configurações existentes
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__)
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):
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
@ -43,10 +35,10 @@ def run_jekyll_build():
def load_manifest_keys():
"""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"]
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)
return list(data.keys())
except Exception:
@ -173,44 +165,54 @@ def analyze_mmp_dependencies(mmp_filename):
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)
try:
tree = ET.parse(filepath)
root = tree.getroot()
factory_folders = load_manifest_keys()
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"):
src = audio_node.get('src', '')
if not src: continue
is_factory = any(src.startswith(folder + "/") for folder in factory_folders)
is_already_imported = src.startswith(XML_IMPORTED_PATH_PREFIX)
# Pega o nome do arquivo que está atualmente no XML
current_basename = os.path.basename(src)
current_basename_lower = current_basename.lower()
if not is_factory and not is_already_imported:
base_name = os.path.basename(src)
# Verifica se esse nome está na lista de substituições
if current_basename_lower in replacements_lower:
new_filename = replacements_lower[current_basename_lower]
# Verifica se temos esse arquivo mapeado (upload do usuário)
if base_name in file_map:
safe_name = file_map[base_name]
new_src = f"{XML_IMPORTED_PATH_PREFIX}/{safe_name}"
# Constrói o novo caminho completo
new_src = f"{XML_IMPORTED_PATH_PREFIX}/{new_filename}"
# Aplica a alteração
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
print(f"[XML FIX] Substituído: {current_basename} -> {new_src}")
changes_made = True
print(f"Path corrigido: {base_name} -> {new_src}")
if changes_made:
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
except Exception as e:
print(f"Erro ao atualizar XML: {e}")
print(f"Erro crítico ao editar XML: {e}")
return False
# --- ROTAS ---
@ -289,43 +291,59 @@ def upload_file():
return jsonify({"error": "Tipo de arquivo não permitido"}), 400
@app.route("/api/upload/resolve", methods=["POST"])
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
uploaded_files = request.files.getlist("sample_files")
project_filename = request.form.get('project_file') # Nome do .mmp
if not project_filename or not uploaded_files:
return jsonify({"error": "Dados incompletos"}), 400
if not project_filename:
return jsonify({"error": "Nome do projeto não informado"}), 400
# Garante pasta
os.makedirs(PHYSICAL_IMPORTED_FOLDER, exist_ok=True)
os.chmod(PHYSICAL_IMPORTED_FOLDER, 0o775)
# Cria pasta imported
os.makedirs(XML_IMPORTED_PATH_PREFIX, exist_ok=True)
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
for file in uploaded_files:
if file and allowed_sample(file.filename):
clean_sample_name = secure_filename(file.filename)
save_path = os.path.join(PHYSICAL_IMPORTED_FOLDER, clean_sample_name)
# request.files é um dicionário imutável, iteramos sobre ele
for original_name, file_storage in request.files.items():
if file_storage and allowed_sample(file_storage.filename):
file.save(save_path)
# 1. Salva o arquivo fisicamente
# Usamos secure_filename no arquivo NOVO para evitar problemas no disco
safe_new_name = secure_filename(file_storage.filename)
# Se secure_filename deixou vazio (ex: "???"), gera um nome
if not safe_new_name:
safe_new_name = f"sample_{int(time.time())}.wav"
save_path = os.path.join(XML_IMPORTED_PATH_PREFIX, safe_new_name)
file_storage.save(save_path)
try:
os.chmod(save_path, 0o664)
except: pass
# Mapeia nome original -> nome salvo
file_map[file.filename] = clean_sample_name
file_map[clean_sample_name] = clean_sample_name # Fallback
# 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
# 2. Atualiza o XML (.mmp)
if update_xml_paths(project_filename, file_map):
# 3. Processa e Builda
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)
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):
"""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")
MMP_MANIFEST = os.path.join(BASE_PATH, "src_mmpSearch", "metadata", "mmp-manifest.json")
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")
LOG_FOLDER = os.path.join(SRC_MMPSEARCH, "logs", "handler_logs")
CERT_PATH = "/etc/letsencrypt/live/alice.ufsj.edu.br/fullchain.pem"
@ -96,6 +97,9 @@ SUPPORTED_PLUGINS = [
"zynaddsubfx",
]
ALLOWED_EXTENSIONS = {"mmp", "mmpz"}
ALLOWED_SAMPLE_EXTENSIONS = {'wav', 'mp3', 'ogg', 'flac', 'ds'}
tags = {}
tags["TAG"] = []
tags["plugin"] = []