get music timestamps
This commit is contained in:
@@ -69,19 +69,19 @@ Pour chaque ` +
|
||||
.string()
|
||||
.describe(
|
||||
'Type de section : "couplet" ou "refrain" ' +
|
||||
"selon la structure",
|
||||
"selon la structure"
|
||||
),
|
||||
lyrics: z
|
||||
.string()
|
||||
.describe(
|
||||
"Paroles de la section, chaque ligne séparée " +
|
||||
"par un retour à la ligne",
|
||||
"par un retour à la ligne"
|
||||
),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.describe(
|
||||
"Paroles de la chanson sous forme de tableau de sections " +
|
||||
"structurées.",
|
||||
"structurées."
|
||||
),
|
||||
success: z.boolean().describe("Indique si la génération a réussi"),
|
||||
});
|
||||
|
||||
+137
-12
@@ -13,6 +13,7 @@ const db = admin.firestore();
|
||||
const SUNO_API_BASE = "https://api.sunoapi.org";
|
||||
const SUNO_API_PATH = "/api/v1/generate";
|
||||
const SUNO_STATUS_PATH = "/api/v1/generate/record-info";
|
||||
const SUNO_TIMESTAMPED_LYRICS_PATH = "/api/v1/generate/get-timestamped-lyrics";
|
||||
const SUNO_MODEL = "V4_5";
|
||||
// const SUNO_MODEL = "V3_5";
|
||||
const SUNO_CALLBACK_URL =
|
||||
@@ -199,7 +200,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const parsed = response.data;
|
||||
@@ -218,7 +219,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur lors de la génération de musique:", error);
|
||||
throw new Error(
|
||||
`Erreur lors de la génération de musique: ${error.message}`,
|
||||
`Erreur lors de la génération de musique: ${error.message}`
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -247,7 +248,7 @@ exports.getSunoStatus = onCall(async ({ data = {} }) => {
|
||||
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||
},
|
||||
timeout: 30000, // 30 secondes de timeout
|
||||
},
|
||||
}
|
||||
);
|
||||
parsed = response.data;
|
||||
|
||||
@@ -324,6 +325,131 @@ exports.getSunoStatus = onCall(async ({ data = {} }) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Récupère les timestamps (aligned words) pour une génération Suno
|
||||
* Attend: { taskId: string, musicIndex: number, projectId: string }
|
||||
*/
|
||||
exports.getSunoTimestamps = onCall(async ({ data = {} }) => {
|
||||
try {
|
||||
const { taskId, musicIndex, projectId } = data || {};
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error("TaskId manquant");
|
||||
}
|
||||
|
||||
const index = Number(musicIndex);
|
||||
if (!Number.isFinite(index) || index < 0) {
|
||||
throw new Error("musicIndex invalide");
|
||||
}
|
||||
|
||||
if (!projectId || typeof projectId !== "string" || !projectId.trim()) {
|
||||
throw new Error("projectId manquant ou invalide");
|
||||
}
|
||||
|
||||
console.log("🔎 Récupération des timestamps Suno", {
|
||||
taskId,
|
||||
musicIndex: index,
|
||||
});
|
||||
|
||||
let response;
|
||||
let parsed;
|
||||
|
||||
try {
|
||||
response = await axios.post(
|
||||
`${SUNO_API_BASE}${SUNO_TIMESTAMPED_LYRICS_PATH}`,
|
||||
{ taskId, musicIndex: index },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||
},
|
||||
timeout: 30000,
|
||||
}
|
||||
);
|
||||
parsed = response.data;
|
||||
console.log("📊 Réponse timestamps Suno API:", parsed?.code, parsed?.msg);
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur API Suno Timestamps:", {
|
||||
message: error.message,
|
||||
status: error.response?.status,
|
||||
statusText: error.response?.statusText,
|
||||
data: error.response?.data,
|
||||
config: {
|
||||
url: error.config?.url,
|
||||
headers: error.config?.headers
|
||||
? { ...error.config.headers, Authorization: "[redacted]" }
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const errorStatus = error.response?.status || "UNKNOWN";
|
||||
const errorMessage =
|
||||
error.response?.data?.msg ||
|
||||
error.response?.data?.message ||
|
||||
error.response?.statusText ||
|
||||
error.message;
|
||||
|
||||
return {
|
||||
success: false,
|
||||
taskId,
|
||||
musicIndex: index,
|
||||
error: {
|
||||
status: errorStatus,
|
||||
message: errorMessage,
|
||||
type: "API_ERROR",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const dataToReturn = parsed?.data || parsed || {};
|
||||
|
||||
// Sauvegarde obligatoire dans le document projet
|
||||
let saved = false;
|
||||
try {
|
||||
await db
|
||||
.collection("projects")
|
||||
.doc(projectId)
|
||||
.set(
|
||||
{
|
||||
[`musicTimestamps.${index}`]: dataToReturn,
|
||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
saved = true;
|
||||
console.log("💾 Timestamps sauvegardés dans Firestore", {
|
||||
projectId,
|
||||
index,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"❌ Échec sauvegarde timestamps Firestore:",
|
||||
e?.message || e
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
taskId,
|
||||
musicIndex: index,
|
||||
projectId,
|
||||
saved,
|
||||
musicTimestamps: dataToReturn,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur interne getSunoTimestamps:", error);
|
||||
return {
|
||||
success: false,
|
||||
taskId: data?.taskId,
|
||||
musicIndex: data?.musicIndex,
|
||||
error: {
|
||||
message: error.message,
|
||||
type: "INTERNAL_ERROR",
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Cloud Function pour recevoir les callbacks de l'API Suno
|
||||
* Cette fonction est appelée par l'API Suno lorsque la génération
|
||||
@@ -340,7 +466,9 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
||||
|
||||
const body = req.body || {};
|
||||
const code = body.code ?? body.statusCode ?? null;
|
||||
const callbackType = (body?.data?.callbackType || "").toString().toLowerCase();
|
||||
const callbackType = (body?.data?.callbackType || "")
|
||||
.toString()
|
||||
.toLowerCase();
|
||||
const status = (body.status || body.state || callbackType)
|
||||
.toString()
|
||||
.toLowerCase();
|
||||
@@ -402,10 +530,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
||||
const audioUrls = tracks
|
||||
.map(
|
||||
(t) =>
|
||||
t.audio_url ||
|
||||
t.audioUrl ||
|
||||
t.stream_audio_url ||
|
||||
t.streamAudioUrl,
|
||||
t.audio_url || t.audioUrl || t.stream_audio_url || t.streamAudioUrl
|
||||
)
|
||||
.filter(Boolean)
|
||||
.slice(0, 2);
|
||||
@@ -420,7 +545,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
||||
has_audio_url: !!t.audio_url,
|
||||
has_stream_audio_url: !!t.stream_audio_url,
|
||||
})),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -445,14 +570,14 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
||||
},
|
||||
});
|
||||
const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
|
||||
path,
|
||||
path
|
||||
)}?alt=media&token=${token}`;
|
||||
console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl);
|
||||
return { path, url: downloadUrl };
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`❌ [SunoCallback] Échec save piste ${index + 1}:`,
|
||||
e.message,
|
||||
e.message
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -472,7 +597,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
||||
musicUrls,
|
||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
},
|
||||
{ merge: true },
|
||||
{ merge: true }
|
||||
);
|
||||
console.log("🏷️ [SunoCallback] Projet marqué GENERATED", {
|
||||
projectId,
|
||||
|
||||
Reference in New Issue
Block a user