upload de samples
Deploy / Deploy (push) Successful in 1m29s Details

This commit is contained in:
JotaChina 2025-12-08 12:23:43 -03:00
parent 229f09883d
commit 20dff13a0b
5 changed files with 347726 additions and 340435 deletions

3548
_data/2019Winter_Song.yml Normal file

File diff suppressed because it is too large Load Diff

684416
_data/all.yml

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
- (lo-fi beat) bubblegum.wav - (lo-fi beat) bubblegum.wav
- )screams(.wav - )screams(.wav
- '- 7 is the answer (06.02.24).wav' - '- 7 is the answer (06.02.24).wav'
- 2019Winter_Song.wav
- 43yu.wav - 43yu.wav
- 4r3st.wav - 4r3st.wav
- '@prod.plue_Trap_Beat.wav' - '@prod.plue_Trap_Beat.wav'

View File

@ -28,6 +28,10 @@ permalink: /samples/
<button id="btn-home" class="button is-small is-info is-light mr-3" title="Voltar ao início"> <button id="btn-home" class="button is-small is-info is-light mr-3" title="Voltar ao início">
<i class="fa-solid fa-house"></i> <i class="fa-solid fa-house"></i>
</button> </button>
<button id="btn-open-upload" class="button is-small is-success is-light mr-3" title="Enviar novo Sample">
<span class="icon"><i class="fa-solid fa-cloud-arrow-up"></i></span>
<span>Enviar Sample</span>
</button>
<nav class="breadcrumb is-small mb-0" aria-label="breadcrumbs"> <nav class="breadcrumb is-small mb-0" aria-label="breadcrumbs">
<ul id="breadcrumb-list"> <ul id="breadcrumb-list">
<li class="is-active"><a href="#">Raiz</a></li> <li class="is-active"><a href="#">Raiz</a></li>
@ -164,6 +168,56 @@ permalink: /samples/
</div> </div>
</div> </div>
<div id="upload-sample-modal" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Enviar Sample</p>
<button class="delete" aria-label="close" id="close-upload-modal"></button>
</header>
<section class="modal-card-body">
<form id="sample-upload-form">
<div class="field">
<label class="label">Arquivo de Áudio</label>
<div class="control">
<div class="file has-name is-fullwidth">
<label class="file-label">
<input class="file-input" type="file" name="sample_file" accept=".wav,.mp3,.ogg,.flac" required>
<span class="file-cta">
<span class="file-icon"><i class="fa-solid fa-upload"></i></span>
<span class="file-label">Escolher arquivo...</span>
</span>
<span class="file-name" id="upload-filename-display">Nenhum arquivo selecionado</span>
</label>
</div>
</div>
</div>
<div class="field">
<label class="label">Salvar na Pasta</label>
<div class="control has-icons-left">
<input class="input" type="text" name="subfolder" placeholder="Ex: Drums/Kicks (Vazio = Raiz)" id="upload-subfolder">
<span class="icon is-small is-left"><i class="fa-solid fa-folder-open"></i></span>
</div>
<p class="help">Você pode criar pastas usando barras, ex: <code>Percussion/Hats</code></p>
</div>
<progress id="upload-progress" class="progress is-small is-primary is-hidden" max="100"></progress>
<p id="upload-status" class="help is-hidden"></p>
</form>
</section>
<footer class="modal-card-foot" style="justify-content: flex-end;">
<button class="button" id="cancel-upload">Cancelar</button>
<button class="button is-success" id="confirm-upload-btn">
<span class="icon"><i class="fa-solid fa-check"></i></span>
<span>Enviar</span>
</button>
</footer>
</div>
</div>
<style> <style>
.browser-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 15px; } .browser-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 15px; }
.browser-item { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 15px; border: 1px solid transparent; border-radius: 8px; cursor: pointer; transition: all 0.2s; } .browser-item { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 15px; border: 1px solid transparent; border-radius: 8px; cursor: pointer; transition: all 0.2s; }
@ -440,5 +494,89 @@ document.addEventListener('DOMContentLoaded', () => {
} catch(e){} }); } catch(e){} });
closeButtons.forEach(el => el.addEventListener('click', closeModal)); closeButtons.forEach(el => el.addEventListener('click', closeModal));
document.addEventListener('keydown', (e) => { if (e.key === "Escape") closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === "Escape") closeModal(); });
// === LÓGICA DE UPLOAD ===
const uploadModal = document.getElementById('upload-sample-modal');
const btnOpenUpload = document.getElementById('btn-open-upload');
const btnCloseUpload = document.getElementById('close-upload-modal');
const btnCancelUpload = document.getElementById('cancel-upload');
const fileInput = document.querySelector('#sample-upload-form input[type="file"]');
const fileNameDisplay = document.getElementById('upload-filename-display');
const subfolderInput = document.getElementById('upload-subfolder');
const confirmUploadBtn = document.getElementById('confirm-upload-btn');
const uploadProgress = document.getElementById('upload-progress');
const uploadStatus = document.getElementById('upload-status');
// Abrir/Fechar Modal
function toggleUploadModal(show) {
if(show) {
uploadModal.classList.add('is-active');
// Tenta preencher a pasta atual automaticamente baseado na navegação
const currentPath = currentPathStack.join('/');
subfolderInput.value = currentPath;
} else {
uploadModal.classList.remove('is-active');
uploadStatus.classList.add('is-hidden');
uploadProgress.classList.add('is-hidden');
}
}
if(btnOpenUpload) btnOpenUpload.onclick = () => toggleUploadModal(true);
if(btnCloseUpload) btnCloseUpload.onclick = () => toggleUploadModal(false);
if(btnCancelUpload) btnCancelUpload.onclick = () => toggleUploadModal(false);
// Atualizar nome do arquivo no input
fileInput.onchange = () => {
if (fileInput.files.length > 0) {
fileNameDisplay.textContent = fileInput.files[0].name;
}
};
// Enviar Formulário
confirmUploadBtn.onclick = async (e) => {
e.preventDefault();
if (fileInput.files.length === 0) {
alert("Selecione um arquivo primeiro.");
return;
}
const formData = new FormData(document.getElementById('sample-upload-form'));
// UI de Carregamento
confirmUploadBtn.classList.add('is-loading');
uploadProgress.classList.remove('is-hidden');
uploadStatus.classList.remove('is-hidden');
uploadStatus.textContent = "Enviando e atualizando biblioteca (pode demorar)...";
uploadStatus.className = "help has-text-info";
try {
// URL do seu servidor Python (ajuste a porta/domínio se necessário)
// Se estiver rodando localmente no navegador, use a URL pública
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) {
uploadStatus.textContent = "Sucesso! Recarregando...";
uploadStatus.className = "help has-text-success";
// Aguarda um pouco e recarrega a página para pegar o novo manifesto
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
throw new Error(result.error || "Erro desconhecido");
}
} catch (error) {
console.error(error);
uploadStatus.textContent = "Erro: " + error.message;
uploadStatus.className = "help has-text-danger";
confirmUploadBtn.classList.remove('is-loading');
}
};
}); });
</script> </script>

View File

@ -8,7 +8,7 @@ from werkzeug.utils import secure_filename
from main import process_single_file, rebuild_indexes, generate_manifests from main import process_single_file, rebuild_indexes, generate_manifests
# 2. ADICIONE BASE_PATH ABAIXO (Assume que utils tem o caminho raiz do projeto) # 2. ADICIONE BASE_PATH ABAIXO (Assume que utils tem o caminho raiz do projeto)
from utils import MMP_FOLDER, MMPZ_FOLDER, CERT_PATH, KEY_PATH, BASE_DATA, SRC_MMPSEARCH from utils import MMP_FOLDER, MMPZ_FOLDER, CERT_PATH, KEY_PATH, BASE_DATA, SRC_MMPSEARCH, SAMPLE_SRC
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
@ -101,6 +101,62 @@ def upload_file():
return jsonify({"error": "Tipo de arquivo não permitido"}), 400 return jsonify({"error": "Tipo de arquivo não permitido"}), 400
ALLOWED_SAMPLE_EXTENSIONS = {'wav', 'mp3', 'ogg', 'flac'}
def allowed_sample(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_SAMPLE_EXTENSIONS
@app.route('/api/upload/sample', methods=['POST'])
def upload_sample():
if 'sample_file' not in request.files:
return jsonify({'error': 'Nenhum arquivo enviado'}), 400
file = request.files['sample_file']
# Pega a subpasta (opcional), ex: "Drums/Kicks"
subfolder = request.form.get('subfolder', '').strip()
if file.filename == '':
return jsonify({'error': 'Nome do arquivo vazio'}), 400
if file and allowed_sample(file.filename):
filename = secure_filename(file.filename)
# Define o caminho final (Raiz de samples + Subpasta)
# Evita "directory traversal" removendo ..
safe_subfolder = subfolder.replace('..', '').strip('/')
target_dir = os.path.join(SAMPLE_SRC, safe_subfolder)
# Cria a pasta se não existir
if not os.path.exists(target_dir):
try:
os.makedirs(target_dir, exist_ok=True)
# Garante permissão para o grupo www-data
os.system(f"chmod -R 775 {target_dir}")
except Exception as e:
return jsonify({'error': f"Erro ao criar pasta: {str(e)}"}), 500
save_path = os.path.join(target_dir, filename)
try:
file.save(save_path)
# Garante permissão do arquivo
os.chmod(save_path, 0o664)
print(f"Sample salvo em: {save_path}")
# 1. Regenerar o manifesto (samples-manifest.json)
# Passamos SRC_MMPSEARCH pois o generate_manifests espera a raiz do projeto
generate_manifests(SRC_MMPSEARCH)
# 2. Reconstruir o site (para o Jekyll ler o novo JSON)
run_jekyll_build()
return jsonify({'message': 'Sample enviado e biblioteca atualizada!'}), 200
except Exception as e:
return jsonify({'error': f"Falha ao processar: {str(e)}"}), 500
return jsonify({'error': 'Tipo de arquivo não permitido (apenas wav, mp3, ogg, flac)'}), 400
if __name__ == "__main__": if __name__ == "__main__":
print("Iniciando Servidor de Upload MMP...") print("Iniciando Servidor de Upload MMP...")