Espace Collaboratif BandHub
Gérez le répertoire de votre groupe, préparez vos concerts, défilez vos accords en direct et partagez vos répétitions enregistrées directement avec Google Drive.
Chants au Répertoire
0
Format ChordPro prêt
Setlists de Concert
0
Mode Téléprompteur
Enregistrements Audio
0
Audios de Répétition
Rôles Vocaux Configurés
0
Palette Couleur Active
Dernières Setlists
Dernières Répétitions
Sélectionnez un chant à gauche ou créez-en un nouveau pour l'éditer.
---
---
Setlists de Concert & Répétition
Organisez vos séquences de concert et lancez le téléprompteur de scène.
Sélectionnez une setlist pour la gérer ou la lancer en direct.
---
Aucune lecture
Sélectionnez une piste audio ci-dessous
Fichiers Audio Orphelins Détectés sur Drive (0)
Ces fichiers sont présents dans le dossier /Records mais n'ont pas de fiche métadonnées.Éditeur de Rôles Vocaux
Définissez les clés et couleurs d'affichage pour les balises de chanteurs.
Aide-Mémoire ChordPro & Rôles
Exemples de balises à intégrer dans vos éditeurs de chants.
Google Apps Script - Code Backend (`Code.gs`)
Copiez ce code dans votre projet Google Apps Script lié au dossier partagé Drive.
// =================================================================
// BANDHUB - GOOGLE APPS SCRIPT BACKEND (Code.gs)
// =================================================================
const PARENT_FOLDER_ID = "VOTRE_ID_DOSSIER_GOOGLE_DRIVE_ICI";
function doGet(e) {
return handleRequest(e);
}
function doPost(e) {
return handleRequest(e);
}
function handleRequest(e) {
const lock = LockService.getScriptLock();
lock.tryLock(10000);
try {
let action = e.parameter ? e.parameter.action : null;
let postData = null;
if (e.postData && e.postData.contents) {
try {
postData = JSON.parse(e.postData.contents);
if (postData.action) action = postData.action;
} catch (err) {}
}
const parentFolder = DriveApp.getFolderById(PARENT_FOLDER_ID);
const folders = ensureSubFolders(parentFolder);
let responseData = { status: "success" };
if (action === "verify_access") {
responseData.message = "Accès autorisé à Google Drive";
responseData.connected = true;
} else if (action === "load_all") {
responseData.songs = loadJsonFilesFromFolder(folders.songs);
responseData.setlists = loadJsonFilesFromFolder(folders.lists);
responseData.records = loadJsonFilesFromFolder(folders.records);
responseData.config = loadSingleJsonFile(folders.parameter, "config.json") || { roles: [] };
responseData.unmappedAudios = findUnmappedAudioFiles(folders.records, responseData.records);
} else if (action === "save_song") {
saveJsonFile(folders.songs, postData.data.id + ".json", postData.data);
responseData.message = "Chant enregistré";
} else if (action === "delete_song") {
deleteFileInFolder(folders.songs, (postData.id || e.parameter.id) + ".json");
responseData.message = "Chant supprimé";
} else if (action === "save_setlist") {
saveJsonFile(folders.lists, postData.data.id + ".json", postData.data);
responseData.message = "Setlist enregistrée";
} else if (action === "delete_setlist") {
deleteFileInFolder(folders.lists, (postData.id || e.parameter.id) + ".json");
responseData.message = "Setlist supprimée";
} else if (action === "save_audio") {
const audioBlob = Utilities.newBlob(
Utilities.base64Decode(postData.audioData64),
postData.mimeType || "audio/mp3",
postData.fileName
);
const audioFile = folders.records.createFile(audioBlob);
const metadata = postData.metadata;
metadata.audioFileId = audioFile.getId();
metadata.audioDownloadUrl = audioFile.getDownloadUrl();
saveJsonFile(folders.records, metadata.id + ".json", metadata);
responseData.metadata = metadata;
} else if (action === "save_audio_metadata") {
saveJsonFile(folders.records, postData.data.id + ".json", postData.data);
responseData.message = "Métadonnées audio enregistrées";
} else if (action === "delete_audio") {
const recId = postData.id || e.parameter.id;
const audioFileId = postData.audioFileId || e.parameter.audioFileId;
deleteFileInFolder(folders.records, recId + ".json");
if (audioFileId) {
try { DriveApp.getFileById(audioFileId).setTrashed(true); } catch(e) {}
}
responseData.message = "Audio supprimé";
} else if (action === "get_audio_base64") {
const fileId = e.parameter.fileId;
if (fileId) {
const file = DriveApp.getFileById(fileId);
const bytes = file.getBlob().getBytes();
const base64 = Utilities.base64Encode(bytes);
responseData.base64 = "data:" + file.getMimeType() + ";base64," + base64;
}
} else if (action === "save_parameters") {
saveJsonFile(folders.parameter, "config.json", postData.data);
responseData.message = "Paramètres enregistrés";
}
return ContentService.createTextOutput(JSON.stringify(responseData))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService.createTextOutput(JSON.stringify({ status: "error", message: error.toString() }))
.setMimeType(ContentService.MimeType.JSON);
} finally {
lock.releaseLock();
}
}
function ensureSubFolders(parent) {
return {
songs: getOrCreateSubFolder(parent, "songs"),
lists: getOrCreateSubFolder(parent, "Lists"),
records: getOrCreateSubFolder(parent, "Records"),
parameter: getOrCreateSubFolder(parent, "parameter")
};
}
function getOrCreateSubFolder(parent, name) {
const folders = parent.getFoldersByName(name);
if (folders.hasNext()) return folders.next();
return parent.createFolder(name);
}
function saveJsonFile(folder, fileName, data) {
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
const files = folder.getFilesByName(fileName);
if (files.hasNext()) {
const file = files.next();
file.setContent(content);
while (files.hasNext()) { files.next().setTrashed(true); }
} else {
folder.createFile(fileName, content, MimeType.PLAIN_TEXT);
}
}
function loadJsonFilesFromFolder(folder) {
const itemsMap = {};
const files = folder.getFiles();
while (files.hasNext()) {
const file = files.next();
if (file.getName().endsWith(".json")) {
try {
const item = JSON.parse(file.getContentText());
if (item && item.id) { itemsMap[item.id] = item; }
} catch(e) {}
}
}
return Object.values(itemsMap);
}
function loadSingleJsonFile(folder, fileName) {
const files = folder.getFilesByName(fileName);
if (files.hasNext()) {
try {
return JSON.parse(files.next().getContentText());
} catch(e) {}
}
return null;
}
function deleteFileInFolder(folder, fileName) {
const files = folder.getFilesByName(fileName);
while (files.hasNext()) {
files.next().setTrashed(true);
}
}
function findUnmappedAudioFiles(folder, existingRecords) {
const mappedFileIds = new Set(existingRecords.map(r => r.audioFileId).filter(Boolean));
const unmapped = [];
const files = folder.getFiles();
while (files.hasNext()) {
const file = files.next();
const mime = file.getMimeType();
if (!file.getName().endsWith(".json") && (mime.includes("audio") || file.getName().match(/\.(mp3|wav|m4a|aac|ogg)$/i))) {
if (!mappedFileIds.has(file.getId())) {
unmapped.push({
id: file.getId(),
name: file.getName(),
downloadUrl: file.getDownloadUrl()
});
}
}
}
return unmapped;
}