Update index.html
Browse files- index.html +152 -92
index.html
CHANGED
|
@@ -360,7 +360,7 @@
|
|
| 360 |
const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
|
| 361 |
// مشخصات نسخه پولی
|
| 362 |
const PREMIUM_PAGE_ID = '1149636';
|
| 363 |
-
const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/
|
| 364 |
|
| 365 |
let db;
|
| 366 |
let songToDeleteId = null;
|
|
@@ -401,6 +401,7 @@
|
|
| 401 |
function getFingerprint() {
|
| 402 |
let fp = localStorage.getItem('user_fingerprint');
|
| 403 |
if (!fp) {
|
|
|
|
| 404 |
fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
|
| 405 |
localStorage.setItem('user_fingerprint', fp);
|
| 406 |
}
|
|
@@ -425,6 +426,7 @@
|
|
| 425 |
}
|
| 426 |
}
|
| 427 |
|
|
|
|
| 428 |
window.addEventListener('message', (event) => {
|
| 429 |
if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
|
| 430 |
try {
|
|
@@ -437,16 +439,26 @@
|
|
| 437 |
}
|
| 438 |
});
|
| 439 |
|
|
|
|
| 440 |
parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
|
| 441 |
|
|
|
|
| 442 |
function handleSecureDownload(url, e) {
|
| 443 |
if (e) e.preventDefault();
|
|
|
|
| 444 |
if (userSubscriptionStatus === 'free') {
|
|
|
|
| 445 |
upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
|
| 446 |
upgradeModalText.innerText = "برای دانلود آهنگهای ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
|
| 447 |
upgradeModal.classList.add('open');
|
| 448 |
} else {
|
| 449 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 450 |
const btn = e ? e.target.closest('button') : null;
|
| 451 |
if(btn) {
|
| 452 |
const originalText = btn.innerHTML;
|
|
@@ -456,8 +468,12 @@
|
|
| 456 |
}
|
| 457 |
}
|
| 458 |
|
|
|
|
| 459 |
document.getElementById('btnGoPremium').addEventListener('click', () => {
|
| 460 |
-
parent.postMessage({
|
|
|
|
|
|
|
|
|
|
| 461 |
});
|
| 462 |
|
| 463 |
function closeUpgradeModal() {
|
|
@@ -474,6 +490,7 @@
|
|
| 474 |
|
| 475 |
async function saveToHistory(idea, lyrics, audioUrl) {
|
| 476 |
try {
|
|
|
|
| 477 |
const response = await fetch(audioUrl);
|
| 478 |
const audioBlob = await response.blob();
|
| 479 |
const newItem = {
|
|
@@ -484,6 +501,7 @@
|
|
| 484 |
const store = transaction.objectStore("songs");
|
| 485 |
store.add(newItem);
|
| 486 |
|
|
|
|
| 487 |
const countReq = store.count();
|
| 488 |
countReq.onsuccess = () => {
|
| 489 |
if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
|
|
@@ -518,7 +536,9 @@
|
|
| 518 |
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>
|
| 519 |
</div>
|
| 520 |
`;
|
|
|
|
| 521 |
div.onclick = (e) => {
|
|
|
|
| 522 |
if (!e.target.closest('.h-delete')) {
|
| 523 |
openHistoryItem(item);
|
| 524 |
}
|
|
@@ -530,7 +550,7 @@
|
|
| 530 |
|
| 531 |
// --- توابع حذف ---
|
| 532 |
function askToDelete(event, id) {
|
| 533 |
-
event.stopPropagation();
|
| 534 |
songToDeleteId = id;
|
| 535 |
deleteModal.classList.add('open');
|
| 536 |
}
|
|
@@ -552,6 +572,7 @@
|
|
| 552 |
}
|
| 553 |
}
|
| 554 |
|
|
|
|
| 555 |
deleteModal.addEventListener('click', (e) => {
|
| 556 |
if (e.target === deleteModal) closeDeleteModal();
|
| 557 |
});
|
|
@@ -568,6 +589,7 @@
|
|
| 568 |
headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> آهنگ آرشیو شده`;
|
| 569 |
headerText.style.color = 'var(--accent-primary)';
|
| 570 |
|
|
|
|
| 571 |
mainDownloadBtn.style.display = 'inline-flex';
|
| 572 |
mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
|
| 573 |
|
|
@@ -580,36 +602,122 @@
|
|
| 580 |
|
| 581 |
initDB();
|
| 582 |
|
| 583 |
-
// --- توابع تبدیل
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
async function convertAudioToWav(file) {
|
| 585 |
return new Promise((resolve, reject) => {
|
| 586 |
const reader = new FileReader();
|
| 587 |
-
reader.onload = (e)
|
| 588 |
-
const
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
|
|
|
|
|
|
| 596 |
};
|
| 597 |
-
reader.onerror =
|
| 598 |
reader.readAsArrayBuffer(file);
|
| 599 |
});
|
| 600 |
}
|
| 601 |
|
|
|
|
| 602 |
async function uploadAudioFile(file) {
|
| 603 |
const formData = new FormData();
|
| 604 |
formData.append("files", file);
|
| 605 |
try {
|
| 606 |
-
const response = await fetch(`${ACE_SPACE_URL}gradio_api/upload`, {
|
|
|
|
|
|
|
|
|
|
| 607 |
const data = await response.json();
|
| 608 |
if (data && data.length > 0) {
|
| 609 |
return {
|
| 610 |
"path": data[0],
|
| 611 |
"url": `${ACE_SPACE_URL}gradio_api/file=${data[0]}`,
|
| 612 |
-
"orig_name": file.name,
|
|
|
|
|
|
|
| 613 |
"meta": {"_type": "gradio.FileData"}
|
| 614 |
};
|
| 615 |
}
|
|
@@ -630,43 +738,53 @@
|
|
| 630 |
loader.style.display = 'block';
|
| 631 |
|
| 632 |
try {
|
|
|
|
| 633 |
const audioInput = document.getElementById('audio_reference');
|
| 634 |
let uploadedAudioObj = null;
|
| 635 |
-
let mode = "custom"; // حالت پیشفرض
|
| 636 |
|
| 637 |
if (audioInput.files.length > 0) {
|
| 638 |
let fileToUpload = audioInput.files[0];
|
|
|
|
|
|
|
| 639 |
if (fileToUpload.type !== 'audio/wav' && !fileToUpload.name.toLowerCase().endsWith('.wav')) {
|
| 640 |
loaderText.innerText = "در حال تبدیل فرمت فایل صوتی به WAV...";
|
| 641 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 642 |
}
|
| 643 |
|
| 644 |
loaderText.innerText = "در حال آپلود فایل نمونه...";
|
| 645 |
uploadedAudioObj = await uploadAudioFile(fileToUpload);
|
| 646 |
-
|
| 647 |
-
if (uploadedAudioObj) {
|
| 648 |
-
mode = "Continuation";
|
| 649 |
-
}
|
| 650 |
}
|
| 651 |
|
|
|
|
| 652 |
loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
|
| 653 |
const isInstrumental = getChk('instrumental_chk');
|
| 654 |
|
|
|
|
| 655 |
const response = await fetch('/api/refine', {
|
| 656 |
method: 'POST',
|
| 657 |
headers: {'Content-Type': 'application/json'},
|
| 658 |
body: JSON.stringify({
|
| 659 |
-
idea: ideaInput.value,
|
|
|
|
| 660 |
is_premium: userSubscriptionStatus === 'paid',
|
| 661 |
is_instrumental: isInstrumental
|
| 662 |
})
|
| 663 |
});
|
| 664 |
|
|
|
|
| 665 |
if (response.status === 429) {
|
| 666 |
loader.style.display = 'none';
|
| 667 |
step1.style.display = 'block';
|
| 668 |
historySection.style.display = 'block';
|
| 669 |
processBtn.disabled = false;
|
|
|
|
| 670 |
upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
|
| 671 |
upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیدهاید. برای ساخت آهنگهای بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
|
| 672 |
upgradeModal.classList.add('open');
|
|
@@ -681,9 +799,10 @@
|
|
| 681 |
|
| 682 |
const finalLyrics = isInstrumental ? "" : lyrics;
|
| 683 |
|
|
|
|
| 684 |
const payload = [
|
| 685 |
getVal('model_select'),
|
| 686 |
-
|
| 687 |
uploadedAudioObj,
|
| 688 |
getVal('duration_select'),
|
| 689 |
musicPrompt,
|
|
@@ -720,7 +839,7 @@
|
|
| 720 |
}
|
| 721 |
});
|
| 722 |
|
| 723 |
-
function handleAudioOutput(
|
| 724 |
const processedUrls = new Set();
|
| 725 |
let hasResult = false;
|
| 726 |
|
|
@@ -739,24 +858,14 @@
|
|
| 739 |
playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
|
| 740 |
}
|
| 741 |
|
| 742 |
-
// **اصلاح کلیدی:** جستجو را از داخل آرایه data در آبجکت خروجی شروع میکنیم
|
| 743 |
function traverse(obj) {
|
| 744 |
-
if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3'))
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
obj.forEach(traverse);
|
| 748 |
-
} else if (obj && typeof obj === 'object') {
|
| 749 |
-
if (obj.url && obj.url.endsWith('.mp3')) {
|
| 750 |
-
addAudio(obj.url);
|
| 751 |
-
} else {
|
| 752 |
-
Object.values(obj).forEach(traverse);
|
| 753 |
-
}
|
| 754 |
}
|
| 755 |
}
|
| 756 |
-
|
| 757 |
-
if (outputData && outputData.data) {
|
| 758 |
-
traverse(outputData.data);
|
| 759 |
-
}
|
| 760 |
|
| 761 |
if (hasResult) {
|
| 762 |
const headerText = document.getElementById('resultHeaderText');
|
|
@@ -766,17 +875,13 @@
|
|
| 766 |
finalLyricsBox.innerHTML = formatLyrics(lyrics);
|
| 767 |
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 768 |
} else {
|
| 769 |
-
|
| 770 |
-
alert("فایل صوتی یافت نشد! (ممکن است خطایی در سرور رخ داده باشد)");
|
| 771 |
step1.style.display = 'block';
|
| 772 |
historySection.style.display = 'block';
|
| 773 |
}
|
| 774 |
}
|
| 775 |
|
| 776 |
-
function formatLyrics(text) {
|
| 777 |
-
if(!text) return "";
|
| 778 |
-
return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
|
| 779 |
-
}
|
| 780 |
|
| 781 |
const canvas = document.getElementById('music-canvas');
|
| 782 |
const ctx = canvas.getContext('2d');
|
|
@@ -798,51 +903,6 @@
|
|
| 798 |
requestAnimationFrame(anim);
|
| 799 |
}
|
| 800 |
anim();
|
| 801 |
-
|
| 802 |
-
// --- توابع کمکی برای تبدیل به WAV ---
|
| 803 |
-
function bufferToWave(aBuffer, len) {
|
| 804 |
-
let numOfChan = aBuffer.numberOfChannels,
|
| 805 |
-
length = len * numOfChan * 2 + 44,
|
| 806 |
-
buffer = new ArrayBuffer(length),
|
| 807 |
-
view = new DataView(buffer),
|
| 808 |
-
channels = [], i, sample,
|
| 809 |
-
offset = 0,
|
| 810 |
-
pos = 0;
|
| 811 |
-
|
| 812 |
-
writeString(view, 0, 'RIFF');
|
| 813 |
-
view.setUint32(4, 36 + len * numOfChan * 2, true);
|
| 814 |
-
writeString(view, 8, 'WAVE');
|
| 815 |
-
writeString(view, 12, 'fmt ');
|
| 816 |
-
view.setUint32(16, 16, true);
|
| 817 |
-
view.setUint16(20, 1, true);
|
| 818 |
-
view.setUint16(22, numOfChan, true);
|
| 819 |
-
view.setUint32(24, aBuffer.sampleRate, true);
|
| 820 |
-
view.setUint32(28, aBuffer.sampleRate * 2 * numOfChan, true);
|
| 821 |
-
view.setUint16(32, numOfChan * 2, true);
|
| 822 |
-
view.setUint16(34, 16, true);
|
| 823 |
-
writeString(view, 36, 'data');
|
| 824 |
-
view.setUint32(40, len * numOfChan * 2, true);
|
| 825 |
-
|
| 826 |
-
for (i = 0; i < aBuffer.numberOfChannels; i++)
|
| 827 |
-
channels.push(aBuffer.getChannelData(i));
|
| 828 |
-
|
| 829 |
-
while (pos < len) {
|
| 830 |
-
for (i = 0; i < numOfChan; i++) {
|
| 831 |
-
sample = Math.max(-1, Math.min(1, channels[i][pos]));
|
| 832 |
-
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0;
|
| 833 |
-
view.setInt16(offset, sample, true);
|
| 834 |
-
offset += 2;
|
| 835 |
-
}
|
| 836 |
-
pos++;
|
| 837 |
-
}
|
| 838 |
-
return new Blob([view], { type: 'audio/wav' });
|
| 839 |
-
}
|
| 840 |
-
|
| 841 |
-
function writeString(view, offset, string) {
|
| 842 |
-
for (var i = 0; i < string.length; i++) {
|
| 843 |
-
view.setUint8(offset + i, string.charCodeAt(i));
|
| 844 |
-
}
|
| 845 |
-
}
|
| 846 |
</script>
|
| 847 |
</body>
|
| 848 |
</html>
|
|
|
|
| 360 |
const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
|
| 361 |
// مشخصات نسخه پولی
|
| 362 |
const PREMIUM_PAGE_ID = '1149636';
|
| 363 |
+
const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
|
| 364 |
|
| 365 |
let db;
|
| 366 |
let songToDeleteId = null;
|
|
|
|
| 401 |
function getFingerprint() {
|
| 402 |
let fp = localStorage.getItem('user_fingerprint');
|
| 403 |
if (!fp) {
|
| 404 |
+
// تولید یک شناسه تصادفی و ذخیره آن
|
| 405 |
fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
|
| 406 |
localStorage.setItem('user_fingerprint', fp);
|
| 407 |
}
|
|
|
|
| 426 |
}
|
| 427 |
}
|
| 428 |
|
| 429 |
+
// شنونده پیام از آیفریم مادر
|
| 430 |
window.addEventListener('message', (event) => {
|
| 431 |
if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
|
| 432 |
try {
|
|
|
|
| 439 |
}
|
| 440 |
});
|
| 441 |
|
| 442 |
+
// درخواست وضعیت کاربر در شروع برنامه
|
| 443 |
parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
|
| 444 |
|
| 445 |
+
// مدیریت کلیک دکمه دانلود (تابع امنیتی)
|
| 446 |
function handleSecureDownload(url, e) {
|
| 447 |
if (e) e.preventDefault();
|
| 448 |
+
|
| 449 |
if (userSubscriptionStatus === 'free') {
|
| 450 |
+
// تنظیم متن مودال برای دانلود
|
| 451 |
upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
|
| 452 |
upgradeModalText.innerText = "برای دانلود آهنگهای ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
|
| 453 |
upgradeModal.classList.add('open');
|
| 454 |
} else {
|
| 455 |
+
// ارسال درخواست دانلود به آیفریم مادر
|
| 456 |
+
parent.postMessage({
|
| 457 |
+
type: 'INITIATE_DOWNLOAD_FROM_URL',
|
| 458 |
+
payload: { audioUrl: url }
|
| 459 |
+
}, '*');
|
| 460 |
+
|
| 461 |
+
// تغییر موقت متن دکمه برای فیدبک
|
| 462 |
const btn = e ? e.target.closest('button') : null;
|
| 463 |
if(btn) {
|
| 464 |
const originalText = btn.innerHTML;
|
|
|
|
| 468 |
}
|
| 469 |
}
|
| 470 |
|
| 471 |
+
// دکمه ارتقا در مودال
|
| 472 |
document.getElementById('btnGoPremium').addEventListener('click', () => {
|
| 473 |
+
parent.postMessage({
|
| 474 |
+
type: 'NAVIGATE_TO_PREMIUM',
|
| 475 |
+
payload: { url: PREMIUM_URL }
|
| 476 |
+
}, '*');
|
| 477 |
});
|
| 478 |
|
| 479 |
function closeUpgradeModal() {
|
|
|
|
| 490 |
|
| 491 |
async function saveToHistory(idea, lyrics, audioUrl) {
|
| 492 |
try {
|
| 493 |
+
// برای ذخیره در DB باید فایل را فچ کنیم
|
| 494 |
const response = await fetch(audioUrl);
|
| 495 |
const audioBlob = await response.blob();
|
| 496 |
const newItem = {
|
|
|
|
| 501 |
const store = transaction.objectStore("songs");
|
| 502 |
store.add(newItem);
|
| 503 |
|
| 504 |
+
// شرط نگهداری فقط ۱۰ آیتم آخر
|
| 505 |
const countReq = store.count();
|
| 506 |
countReq.onsuccess = () => {
|
| 507 |
if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
|
|
|
|
| 536 |
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>
|
| 537 |
</div>
|
| 538 |
`;
|
| 539 |
+
// کلیک روی خود کارت برای پخش
|
| 540 |
div.onclick = (e) => {
|
| 541 |
+
// اگر روی دکمه حذف کلیک نشده باشد
|
| 542 |
if (!e.target.closest('.h-delete')) {
|
| 543 |
openHistoryItem(item);
|
| 544 |
}
|
|
|
|
| 550 |
|
| 551 |
// --- توابع حذف ---
|
| 552 |
function askToDelete(event, id) {
|
| 553 |
+
event.stopPropagation(); // جلوگیری از باز شدن پلیر
|
| 554 |
songToDeleteId = id;
|
| 555 |
deleteModal.classList.add('open');
|
| 556 |
}
|
|
|
|
| 572 |
}
|
| 573 |
}
|
| 574 |
|
| 575 |
+
// بستن مودال با کلیک بیرون
|
| 576 |
deleteModal.addEventListener('click', (e) => {
|
| 577 |
if (e.target === deleteModal) closeDeleteModal();
|
| 578 |
});
|
|
|
|
| 589 |
headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> آهنگ آرشیو شده`;
|
| 590 |
headerText.style.color = 'var(--accent-primary)';
|
| 591 |
|
| 592 |
+
// تنظیم دکمه دانلود
|
| 593 |
mainDownloadBtn.style.display = 'inline-flex';
|
| 594 |
mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
|
| 595 |
|
|
|
|
| 602 |
|
| 603 |
initDB();
|
| 604 |
|
| 605 |
+
// --- توابع تبدیل فرمت صوتی (Client-Side WAV Converter) ---
|
| 606 |
+
function writeString(view, offset, string) {
|
| 607 |
+
for (let i = 0; i < string.length; i++) {
|
| 608 |
+
view.setUint8(offset + i, string.charCodeAt(i));
|
| 609 |
+
}
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
function floatTo16BitPCM(output, offset, input) {
|
| 613 |
+
for (let i = 0; i < input.length; i++, offset += 2) {
|
| 614 |
+
let s = Math.max(-1, Math.min(1, input[i]));
|
| 615 |
+
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
|
| 616 |
+
}
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
function interleave(inputL, inputR) {
|
| 620 |
+
const length = inputL.length + inputR.length;
|
| 621 |
+
const result = new Float32Array(length);
|
| 622 |
+
let index = 0;
|
| 623 |
+
let inputIndex = 0;
|
| 624 |
+
while (index < length) {
|
| 625 |
+
result[index++] = inputL[inputIndex];
|
| 626 |
+
result[index++] = inputR[inputIndex];
|
| 627 |
+
inputIndex++;
|
| 628 |
+
}
|
| 629 |
+
return result;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
function bufferToWave(abuffer, len) {
|
| 633 |
+
let numOfChan = abuffer.numberOfChannels;
|
| 634 |
+
let length = len * numOfChan * 2 + 44;
|
| 635 |
+
let buffer = new ArrayBuffer(length);
|
| 636 |
+
let view = new DataView(buffer);
|
| 637 |
+
let channels = [], i, sample;
|
| 638 |
+
let offset = 0;
|
| 639 |
+
let pos = 0;
|
| 640 |
+
|
| 641 |
+
// write WAVE header
|
| 642 |
+
writeString(view, 0, 'RIFF');
|
| 643 |
+
view.setUint32(4, 36 + len * numOfChan * 2, true);
|
| 644 |
+
writeString(view, 8, 'WAVE');
|
| 645 |
+
writeString(view, 12, 'fmt ');
|
| 646 |
+
view.setUint32(16, 16, true);
|
| 647 |
+
view.setUint16(20, 1, true);
|
| 648 |
+
view.setUint16(22, numOfChan, true);
|
| 649 |
+
view.setUint32(24, abuffer.sampleRate, true);
|
| 650 |
+
view.setUint32(28, abuffer.sampleRate * 2 * numOfChan, true);
|
| 651 |
+
view.setUint16(32, numOfChan * 2, true);
|
| 652 |
+
view.setUint16(34, 16, true);
|
| 653 |
+
writeString(view, 36, 'data');
|
| 654 |
+
view.setUint32(40, len * numOfChan * 2, true);
|
| 655 |
+
|
| 656 |
+
// write interleaved data
|
| 657 |
+
for(i = 0; i < abuffer.numberOfChannels; i++)
|
| 658 |
+
channels.push(abuffer.getChannelData(i));
|
| 659 |
+
|
| 660 |
+
offset = 44;
|
| 661 |
+
// Interleave logic handled simply here for 1 or 2 channels
|
| 662 |
+
if (numOfChan === 2) {
|
| 663 |
+
while(pos < len) {
|
| 664 |
+
for(i = 0; i < numOfChan; i++) {
|
| 665 |
+
sample = Math.max(-1, Math.min(1, channels[i][pos]));
|
| 666 |
+
sample = (sample < 0 ? sample * 0x8000 : sample * 0x7FFF) | 0;
|
| 667 |
+
view.setInt16(offset, sample, true);
|
| 668 |
+
offset += 2;
|
| 669 |
+
}
|
| 670 |
+
pos++;
|
| 671 |
+
}
|
| 672 |
+
} else {
|
| 673 |
+
while(pos < len) {
|
| 674 |
+
sample = Math.max(-1, Math.min(1, channels[0][pos]));
|
| 675 |
+
sample = (sample < 0 ? sample * 0x8000 : sample * 0x7FFF) | 0;
|
| 676 |
+
view.setInt16(offset, sample, true);
|
| 677 |
+
offset += 2;
|
| 678 |
+
pos++;
|
| 679 |
+
}
|
| 680 |
+
}
|
| 681 |
+
return new Blob([buffer], { type: "audio/wav" });
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
async function convertAudioToWav(file) {
|
| 685 |
return new Promise((resolve, reject) => {
|
| 686 |
const reader = new FileReader();
|
| 687 |
+
reader.onload = function(e) {
|
| 688 |
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
| 689 |
+
audioContext.decodeAudioData(e.target.result, function(buffer) {
|
| 690 |
+
const wavBlob = bufferToWave(buffer, buffer.length);
|
| 691 |
+
// ساخت فایل جدید با فرمت wav
|
| 692 |
+
const wavFile = new File([wavBlob], file.name.replace(/\.[^/.]+$/, "") + ".wav", { type: "audio/wav" });
|
| 693 |
+
resolve(wavFile);
|
| 694 |
+
}, function(e){
|
| 695 |
+
console.error("Audio decode failed", e);
|
| 696 |
+
reject("فرمت فایل صوتی پشتیبانی نمیشود.");
|
| 697 |
+
});
|
| 698 |
};
|
| 699 |
+
reader.onerror = reject;
|
| 700 |
reader.readAsArrayBuffer(file);
|
| 701 |
});
|
| 702 |
}
|
| 703 |
|
| 704 |
+
// --- تابع آپلود فایل صوتی به Gradio ---
|
| 705 |
async function uploadAudioFile(file) {
|
| 706 |
const formData = new FormData();
|
| 707 |
formData.append("files", file);
|
| 708 |
try {
|
| 709 |
+
const response = await fetch(`${ACE_SPACE_URL}gradio_api/upload`, {
|
| 710 |
+
method: "POST",
|
| 711 |
+
body: formData
|
| 712 |
+
});
|
| 713 |
const data = await response.json();
|
| 714 |
if (data && data.length > 0) {
|
| 715 |
return {
|
| 716 |
"path": data[0],
|
| 717 |
"url": `${ACE_SPACE_URL}gradio_api/file=${data[0]}`,
|
| 718 |
+
"orig_name": file.name,
|
| 719 |
+
"size": file.size,
|
| 720 |
+
"mime_type": file.type,
|
| 721 |
"meta": {"_type": "gradio.FileData"}
|
| 722 |
};
|
| 723 |
}
|
|
|
|
| 738 |
loader.style.display = 'block';
|
| 739 |
|
| 740 |
try {
|
| 741 |
+
// 1. آپلود و تبدیل فایل نمونه
|
| 742 |
const audioInput = document.getElementById('audio_reference');
|
| 743 |
let uploadedAudioObj = null;
|
|
|
|
| 744 |
|
| 745 |
if (audioInput.files.length > 0) {
|
| 746 |
let fileToUpload = audioInput.files[0];
|
| 747 |
+
|
| 748 |
+
// بررسی و تبدیل به WAV
|
| 749 |
if (fileToUpload.type !== 'audio/wav' && !fileToUpload.name.toLowerCase().endsWith('.wav')) {
|
| 750 |
loaderText.innerText = "در حال تبدیل فرمت فایل صوتی به WAV...";
|
| 751 |
+
try {
|
| 752 |
+
fileToUpload = await convertAudioToWav(fileToUpload);
|
| 753 |
+
console.log("Converted to WAV:", fileToUpload.name, fileToUpload.size);
|
| 754 |
+
} catch (err) {
|
| 755 |
+
console.error(err);
|
| 756 |
+
alert("خطا در تبدیل فایل صوتی. لطفاً فایل WAV معتبر آپلود کنید.");
|
| 757 |
+
throw new Error("Audio conversion failed");
|
| 758 |
+
}
|
| 759 |
}
|
| 760 |
|
| 761 |
loaderText.innerText = "در حال آپلود فایل نمونه...";
|
| 762 |
uploadedAudioObj = await uploadAudioFile(fileToUpload);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
}
|
| 764 |
|
| 765 |
+
// 2. درخواست متن و پرامپت
|
| 766 |
loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
|
| 767 |
const isInstrumental = getChk('instrumental_chk');
|
| 768 |
|
| 769 |
+
// ارسال درخواست به سرور با فینگرپرینت و وضعیت اشتراک
|
| 770 |
const response = await fetch('/api/refine', {
|
| 771 |
method: 'POST',
|
| 772 |
headers: {'Content-Type': 'application/json'},
|
| 773 |
body: JSON.stringify({
|
| 774 |
+
idea: ideaInput.value,
|
| 775 |
+
fingerprint: getFingerprint(),
|
| 776 |
is_premium: userSubscriptionStatus === 'paid',
|
| 777 |
is_instrumental: isInstrumental
|
| 778 |
})
|
| 779 |
});
|
| 780 |
|
| 781 |
+
// بررسی خطای محدودیت روزانه (429)
|
| 782 |
if (response.status === 429) {
|
| 783 |
loader.style.display = 'none';
|
| 784 |
step1.style.display = 'block';
|
| 785 |
historySection.style.display = 'block';
|
| 786 |
processBtn.disabled = false;
|
| 787 |
+
|
| 788 |
upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
|
| 789 |
upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیدهاید. برای ساخت آهنگهای بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
|
| 790 |
upgradeModal.classList.add('open');
|
|
|
|
| 799 |
|
| 800 |
const finalLyrics = isInstrumental ? "" : lyrics;
|
| 801 |
|
| 802 |
+
// ایندکس 3: مدت زمان آهنگ
|
| 803 |
const payload = [
|
| 804 |
getVal('model_select'),
|
| 805 |
+
"custom",
|
| 806 |
uploadedAudioObj,
|
| 807 |
getVal('duration_select'),
|
| 808 |
musicPrompt,
|
|
|
|
| 839 |
}
|
| 840 |
});
|
| 841 |
|
| 842 |
+
function handleAudioOutput(data, lyrics, idea) {
|
| 843 |
const processedUrls = new Set();
|
| 844 |
let hasResult = false;
|
| 845 |
|
|
|
|
| 858 |
playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
|
| 859 |
}
|
| 860 |
|
|
|
|
| 861 |
function traverse(obj) {
|
| 862 |
+
if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
|
| 863 |
+
else if (obj && typeof obj === 'object') {
|
| 864 |
+
if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
|
| 865 |
+
Object.values(obj).forEach(traverse);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 866 |
}
|
| 867 |
}
|
| 868 |
+
traverse(data);
|
|
|
|
|
|
|
|
|
|
| 869 |
|
| 870 |
if (hasResult) {
|
| 871 |
const headerText = document.getElementById('resultHeaderText');
|
|
|
|
| 875 |
finalLyricsBox.innerHTML = formatLyrics(lyrics);
|
| 876 |
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 877 |
} else {
|
| 878 |
+
alert("فایل صوتی یافت نشد!");
|
|
|
|
| 879 |
step1.style.display = 'block';
|
| 880 |
historySection.style.display = 'block';
|
| 881 |
}
|
| 882 |
}
|
| 883 |
|
| 884 |
+
function formatLyrics(text) { return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>'); }
|
|
|
|
|
|
|
|
|
|
| 885 |
|
| 886 |
const canvas = document.getElementById('music-canvas');
|
| 887 |
const ctx = canvas.getContext('2d');
|
|
|
|
| 903 |
requestAnimationFrame(anim);
|
| 904 |
}
|
| 905 |
anim();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 906 |
</script>
|
| 907 |
</body>
|
| 908 |
</html>
|