diff --git a/creation.html b/creation.html
index 343ee390..a5282dc2 100755
--- a/creation.html
+++ b/creation.html
@@ -804,6 +804,76 @@
});
}
});
+
+ document.addEventListener("DOMContentLoaded", () => {
+ // --- LÓGICA DE UPLOAD DE SAMPLE AVULSO ---
+ const uploadSampleBtn = document.getElementById("upload-sample-btn");
+ const sampleInput = document.getElementById("sample-file-input");
+
+ if (uploadSampleBtn && sampleInput) {
+
+ // 1. Botão clica no input invisível
+ uploadSampleBtn.addEventListener("click", () => {
+ sampleInput.click();
+ });
+
+ // 2. Quando o arquivo é selecionado
+ sampleInput.addEventListener("change", async () => {
+ if (sampleInput.files.length === 0) return;
+
+ const file = sampleInput.files[0];
+
+ // Pergunta a categoria para organizar no servidor (opcional)
+ // O backend usa isso para criar pastas: samples/drums, samples/vocals, etc.
+ const category = prompt(
+ "Em qual categoria este sample se encaixa? (Ex: drums, effects, vocals)",
+ "imported"
+ );
+
+ if (category === null) {
+ // Usuário cancelou
+ sampleInput.value = "";
+ return;
+ }
+
+ // Prepara o formulário
+ const formData = new FormData();
+ formData.append("sample_file", file); // Deve bater com 'sample_file' no Python
+ formData.append("subfolder", category); // Deve bater com 'subfolder' no Python
+
+ // Feedback Visual (ícone girando ou ficando transparente)
+ const originalIcon = uploadSampleBtn.className;
+ uploadSampleBtn.className = "fa-solid fa-spinner fa-spin"; // Ícone de loading
+ uploadSampleBtn.style.pointerEvents = "none";
+
+ try {
+ const response = await fetch("https://alice.ufsj.edu.br:33002/api/upload/sample", {
+ method: "POST",
+ body: formData
+ });
+
+ const result = await response.json();
+
+ if (response.ok) {
+ alert("Sucesso! " + result.message);
+ // Opcional: Recarregar a lista de samples lateral se você tiver uma função para isso
+ // reloadBrowser();
+ } else {
+ alert("Erro ao enviar: " + (result.error || "Desconhecido"));
+ }
+
+ } catch (error) {
+ console.error("Erro no upload:", error);
+ alert("Erro de conexão com o servidor.");
+ } finally {
+ // Restaura o botão e limpa o input
+ uploadSampleBtn.className = originalIcon;
+ uploadSampleBtn.style.pointerEvents = "auto";
+ sampleInput.value = "";
+ }
+ });
+ }
+ });
diff --git a/scripts/handler/upload_server.py b/scripts/handler/upload_server.py
index 026d5d2d..fd1513dc 100755
--- a/scripts/handler/upload_server.py
+++ b/scripts/handler/upload_server.py
@@ -220,17 +220,16 @@ def update_xml_paths_exact(mmp_filename, replacements):
@app.route("/api/download/", methods=["GET"])
def download_project_package(project_name):
"""
- Gera um ZIP contendo o arquivo .mmp e apenas os samples importados necessários.
- Estrutura do ZIP:
- - projeto.mmp
- - src_mmpSearch/
- - samples/
- - imported/
- - sample1.wav
- - sample2.wav
+ Gera um ZIP onde:
+ 1. O .mmp é modificado NA VOLÁTIL (sem salvar no disco) para ter caminhos curtos.
+ 2. A estrutura do ZIP fica limpa:
+ - projeto.mmp
+ - imported/
+ - sample1.wav
+ - sample2.wav
"""
- # Garante que estamos pegando o .mmp (mesmo que peçam sem extensão)
- if not project_name.endswith('.mmp'):
+ # Garante extensão .mmp
+ if not project_name.lower().endswith('.mmp'):
project_name += '.mmp'
clean_name = secure_filename(project_name)
@@ -239,56 +238,65 @@ def download_project_package(project_name):
if not os.path.exists(mmp_path):
return jsonify({"error": "Projeto não encontrado"}), 404
- # Prepara o buffer do ZIP na memória (não salva no disco para economizar I/O)
+ # Prepara o buffer do ZIP na memória
memory_file = io.BytesIO()
try:
+ # 1. Parseia o XML original do disco
+ tree = ET.parse(mmp_path)
+ root = tree.getroot()
+
+ # Conjunto para rastrear quais arquivos físicos precisamos colocar no ZIP
+ samples_to_pack = set()
+
+ # 2. Modifica o XML na MEMÓRIA (não afeta o arquivo original no servidor)
+ for audio_node in root.findall(".//audiofileprocessor"):
+ src = audio_node.get('src', '')
+
+ # Verifica se é um sample importado do nosso sistema
+ if src.startswith(XML_IMPORTED_PATH_PREFIX):
+ # Pega apenas o nome do arquivo (ex: 'kick_deep.wav')
+ filename = os.path.basename(src)
+
+ # Define o NOVO caminho curto relativo para dentro do ZIP
+ # Antes: src_mmpSearch/samples/imported/kick_deep.wav
+ # Agora: imported/kick_deep.wav
+ short_path = f"imported/{filename}"
+
+ # Atualiza o XML na memória
+ audio_node.set('src', short_path)
+
+ # Adiciona à lista de arquivos para copiar
+ samples_to_pack.add(filename)
+
+ # 3. Cria o ZIP
with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
- # 1. Adiciona o arquivo .mmp na RAIZ do ZIP
- # arcname é o nome que o arquivo terá dentro do zip
- zf.write(mmp_path, arcname=clean_name)
+ # A) Escreve o .mmp MODIFICADO no ZIP
+ # Convertemos a árvore XML modificada para string binária
+ xml_str = ET.tostring(root, encoding='utf-8', method='xml')
+ zf.writestr(clean_name, xml_str)
- # 2. Lê o XML para descobrir quais samples incluir
- tree = ET.parse(mmp_path)
- root = tree.getroot()
-
- # Conjunto para evitar adicionar o mesmo sample 2x
- samples_to_pack = set()
-
- for audio_node in root.findall(".//audiofileprocessor"):
- src = audio_node.get('src', '')
-
- # Verifica se é um sample importado (nosso padrão)
- # O src no XML já deve estar como: src_mmpSearch/samples/imported/nome.wav
- if src.startswith(XML_IMPORTED_PATH_PREFIX):
- # Extrai apenas o nome do arquivo (ex: kick.wav)
- sample_filename = os.path.basename(src)
- samples_to_pack.add(sample_filename)
-
- # 3. Adiciona os samples encontrados ao ZIP mantendo a estrutura de pasta
+ # B) Adiciona os samples físicos na pasta 'imported/' do ZIP
for sample_name in samples_to_pack:
- # Caminho físico no servidor (onde o arquivo realmente está)
physical_path = os.path.join(XML_IMPORTED_PATH_PREFIX, sample_name)
- # Caminho DENTRO do ZIP (para o LMMS ler relativo ao mmp)
- # Deve ser: src_mmpSearch/samples/imported/sample.wav
- zip_internal_path = f"{XML_IMPORTED_PATH_PREFIX}/{sample_name}"
+ # Caminho curto dentro do ZIP
+ zip_entry_name = f"imported/{sample_name}"
if os.path.exists(physical_path):
- zf.write(physical_path, arcname=zip_internal_path)
- print(f"[ZIP] Adicionado: {sample_name}")
+ zf.write(physical_path, arcname=zip_entry_name)
+ print(f"[ZIP CLEAN] Adicionado: {zip_entry_name}")
else:
- print(f"[ZIP] AVISO: Sample listado no XML mas não encontrado no disco: {sample_name}")
+ print(f"[ZIP CLEAN] AVISO: Arquivo faltante no disco: {sample_name}")
- # Finaliza o ponteiro do arquivo
+ # Finaliza e envia
memory_file.seek(0)
-
return send_file(
memory_file,
mimetype='application/zip',
as_attachment=True,
- download_name=f"{os.path.splitext(clean_name)[0]}_pack.zip"
+ download_name=f"{os.path.splitext(clean_name)[0]}.zip"
)
except Exception as e: