|
|
<!DOCTYPE html> |
|
|
<html lang="fa" dir="rtl"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>تولید و تغییر صدای هوشمند | AI Sada</title> |
|
|
<meta name="description" content="ابزار پیشرفته تبدیل متن به صدا و تغییر صدا با هوش مصنوعی."> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;300;400;500;600;700;800;900&display=swap'); |
|
|
|
|
|
:root { |
|
|
--app-font: 'Vazirmatn', sans-serif; --app-bg: #F8F9FC; --panel-bg: #FFFFFF; |
|
|
--panel-border: #EAEFF7; --input-bg: #F6F8FB; --input-border: #E1E7EF; |
|
|
--text-primary: #1A202C; --text-secondary: #626F86; --text-tertiary: #8A94A6; |
|
|
--accent-primary: #4A6CFA; --accent-primary-hover: #3553D6; |
|
|
--accent-secondary: #0FD4A8; |
|
|
--shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03); |
|
|
--shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06); |
|
|
--radius-card: 24px; --radius-btn: 14px; |
|
|
--glass-bg: rgba(255, 255, 255, 0.85); |
|
|
--glass-border: rgba(255, 255, 255, 0.5); |
|
|
--glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15); |
|
|
} |
|
|
|
|
|
body { font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 0; min-height: 100vh; overflow-x: hidden; } |
|
|
* { box-sizing: border-box; } |
|
|
|
|
|
.page-wrapper { max-width: 820px; width: 92%; margin: 0 auto; padding: 1.5rem 0 4rem 0; } |
|
|
|
|
|
/* Header & User Status */ |
|
|
.app-header { text-align: center; margin-bottom: 1.5rem; position: relative; z-index: 10; } |
|
|
.header-actions { display: flex; justify-content: center; align-items: center; gap: 1rem; margin-bottom: 1.5rem; } |
|
|
|
|
|
#user-status-container { |
|
|
padding: 0.5rem 1rem; background: white; border-radius: 50px; display: none; |
|
|
align-items: center; gap: 0.8rem; border: 1px solid var(--panel-border); box-shadow: var(--shadow-sm); |
|
|
} |
|
|
.user-sub-status { font-size: 0.8em; padding: 0.2rem 0.8rem; border-radius: 20px; background: #eee; font-weight: 700; } |
|
|
.user-sub-status.paid { background: #e6fffa; color: #047481; border: 1px solid #b2f5ea; } |
|
|
|
|
|
#login-check-btn { |
|
|
background: var(--accent-primary); color: white; border: none; padding: 0.6rem 1.5rem; |
|
|
border-radius: 50px; font-family: var(--app-font); font-weight: 700; cursor: pointer; |
|
|
box-shadow: 0 4px 10px rgba(74, 108, 250, 0.3); transition: transform 0.2s; |
|
|
} |
|
|
#login-check-btn:hover { transform: translateY(-2px); } |
|
|
|
|
|
/* Glassy Navigation Menu */ |
|
|
.glass-nav-container { |
|
|
position: sticky; top: 1rem; z-index: 100; margin-bottom: 2rem; |
|
|
display: flex; justify-content: center; |
|
|
} |
|
|
.glass-nav { |
|
|
background: var(--glass-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); |
|
|
border: 1px solid var(--glass-border); border-radius: 20px; padding: 0.5rem; |
|
|
display: flex; gap: 0.5rem; box-shadow: var(--glass-shadow); |
|
|
overflow-x: auto; max-width: 100%; white-space: nowrap; scrollbar-width: none; |
|
|
} |
|
|
.glass-nav::-webkit-scrollbar { display: none; } |
|
|
|
|
|
.nav-item { |
|
|
background: transparent; border: none; padding: 0.7rem 1.2rem; border-radius: 14px; |
|
|
color: var(--text-secondary); font-family: var(--app-font); font-weight: 700; font-size: 0.95em; |
|
|
cursor: pointer; transition: all 0.3s ease; position: relative; |
|
|
} |
|
|
.nav-item.active { |
|
|
background: white; color: var(--accent-primary); box-shadow: 0 4px 12px rgba(0,0,0,0.05); |
|
|
} |
|
|
.nav-item .badge { |
|
|
font-size: 0.6em; background: #FFC107; color: #000; padding: 2px 6px; border-radius: 6px; |
|
|
position: absolute; top: 2px; left: 2px; |
|
|
} |
|
|
|
|
|
/* Content Sections */ |
|
|
.content-section { display: none; animation: fadeIn 0.5s ease-out; } |
|
|
.content-section.active { display: block; } |
|
|
|
|
|
.main-card { |
|
|
background: var(--panel-bg); border-radius: var(--radius-card); padding: 2.5rem; |
|
|
box-shadow: var(--shadow-lg); border: 1px solid var(--panel-border); |
|
|
} |
|
|
|
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } |
|
|
|
|
|
/* Forms & Inputs */ |
|
|
.form-group { margin-bottom: 1.5rem; } |
|
|
label { display: block; font-weight: 700; margin-bottom: 0.8rem; color: var(--text-primary); } |
|
|
textarea, input[type="text"], input[type="email"] { |
|
|
width: 100%; padding: 1rem; border-radius: var(--radius-btn); border: 1px solid var(--input-border); |
|
|
background: var(--input-bg); font-family: var(--app-font); font-size: 1rem; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
textarea:focus, input:focus { outline: none; border-color: var(--accent-primary); background: white; } |
|
|
|
|
|
.generate-btn { |
|
|
width: 100%; padding: 1.2rem; border-radius: var(--radius-btn); border: none; |
|
|
background: linear-gradient(90deg, var(--accent-primary), #6B46C1); color: white; |
|
|
font-family: var(--app-font); font-weight: 800; font-size: 1.1em; cursor: pointer; |
|
|
box-shadow: 0 10px 20px -5px rgba(74, 108, 250, 0.4); transition: transform 0.2s; |
|
|
display: flex; justify-content: center; align-items: center; gap: 10px; |
|
|
} |
|
|
.generate-btn:hover { transform: translateY(-3px); } |
|
|
.generate-btn:disabled { background: #cbd5e0; cursor: not-allowed; transform: none; box-shadow: none; } |
|
|
.spinner { width: 20px; height: 20px; border: 3px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 1s linear infinite; display: none; } |
|
|
@keyframes spin { 100% { transform: rotate(360deg); } } |
|
|
|
|
|
/* Voice Changer Specifics */ |
|
|
.model-grid { |
|
|
display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 1rem; |
|
|
margin-bottom: 2rem; max-height: 300px; overflow-y: auto; padding: 5px; |
|
|
} |
|
|
.model-item { |
|
|
background: var(--input-bg); border: 2px solid transparent; border-radius: 16px; |
|
|
padding: 0.5rem; text-align: center; cursor: pointer; transition: all 0.2s; |
|
|
} |
|
|
.model-item:hover { transform: translateY(-3px); } |
|
|
.model-item.selected { border-color: var(--accent-primary); background: white; box-shadow: 0 5px 15px rgba(74, 108, 250, 0.2); } |
|
|
.model-item img { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; margin-bottom: 0.5rem; } |
|
|
.model-item span { display: block; font-size: 0.8em; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
|
|
|
|
|
.file-upload-box { |
|
|
border: 2px dashed var(--input-border); border-radius: var(--radius-card); padding: 2rem; |
|
|
text-align: center; cursor: pointer; background: var(--input-bg); transition: all 0.3s; |
|
|
} |
|
|
.file-upload-box:hover { border-color: var(--accent-primary); background: #f0f4ff; } |
|
|
.file-info { display: flex; align-items: center; justify-content: space-between; background: white; padding: 0.8rem; border-radius: 12px; margin-top: 1rem; border: 1px solid var(--panel-border); } |
|
|
|
|
|
/* Audio Player & Output */ |
|
|
.output-box { |
|
|
margin-top: 2rem; background: var(--input-bg); border-radius: var(--radius-card); padding: 1.5rem; |
|
|
border: 1px solid var(--input-border); min-height: 100px; display: flex; align-items: center; justify-content: center; |
|
|
flex-direction: column; |
|
|
} |
|
|
.output-box.ready { background: white; border-color: var(--accent-secondary); } |
|
|
audio { width: 100%; margin-top: 1rem; } |
|
|
|
|
|
/* Landing Content */ |
|
|
.landing-hero { text-align: center; margin: 3rem 0; } |
|
|
.landing-hero h2 { font-size: 2em; margin-bottom: 1rem; color: #1A202C; } |
|
|
.landing-hero p { color: var(--text-secondary); max-width: 600px; margin: 0 auto; } |
|
|
|
|
|
.faq-section { margin-top: 4rem; } |
|
|
.faq-item { background: white; border-radius: 16px; padding: 1.5rem; margin-bottom: 1rem; border: 1px solid var(--panel-border); } |
|
|
.faq-item h3 { margin: 0 0 0.5rem 0; font-size: 1.1em; color: var(--accent-primary); } |
|
|
.faq-item p { margin: 0; font-size: 0.95em; color: var(--text-secondary); } |
|
|
|
|
|
/* Podcast Coming Soon */ |
|
|
.coming-soon-card { |
|
|
text-align: center; padding: 4rem 2rem; background: linear-gradient(135deg, #1A202C, #2D3748); |
|
|
border-radius: var(--radius-card); color: white; position: relative; overflow: hidden; |
|
|
} |
|
|
.coming-soon-card::before { |
|
|
content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; |
|
|
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 60%); |
|
|
animation: spin 10s linear infinite; |
|
|
} |
|
|
|
|
|
/* Responsive */ |
|
|
@media(max-width: 600px) { |
|
|
.page-wrapper { padding-top: 0.5rem; } |
|
|
.glass-nav-container { top: 0.5rem; } |
|
|
.main-card { padding: 1.5rem; } |
|
|
.model-grid { grid-template-columns: repeat(3, 1fr); } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div class="page-wrapper"> |
|
|
|
|
|
|
|
|
<header class="app-header"> |
|
|
<div class="header-actions"> |
|
|
<div id="user-status-container"> |
|
|
<span class="user-email" id="display-email"></span> |
|
|
<span class="user-sub-status" id="display-sub">رایگان</span> |
|
|
<button onclick="handleLogout()" style="background:none;border:none;cursor:pointer;color:#e53e3e;">خروج</button> |
|
|
</div> |
|
|
<button id="login-check-btn" onclick="showEmailModal()">ورود / ثبت نام</button> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="glass-nav-container"> |
|
|
<nav class="glass-nav"> |
|
|
<button class="nav-item active" onclick="switchTab('tts')">تبدیل متن به صدا</button> |
|
|
<button class="nav-item" onclick="switchTab('vc')">تغییر صدا (AI)</button> |
|
|
<button class="nav-item" onclick="switchTab('podcast')">ساخت پادکست <span class="badge">بزودی</span></button> |
|
|
</nav> |
|
|
</div> |
|
|
|
|
|
|
|
|
<section id="section-tts" class="content-section active"> |
|
|
<div class="main-card"> |
|
|
<div class="form-group"> |
|
|
<label>متن خود را وارد کنید</label> |
|
|
<textarea id="tts-input" rows="5" placeholder="اینجا بنویسید..."></textarea> |
|
|
</div> |
|
|
<div class="form-group"> |
|
|
<label>انتخاب گوینده</label> |
|
|
|
|
|
<button type="button" class="generate-btn" style="background:#f0f0f0; color:#333; margin-bottom:1rem;" onclick="document.getElementById('speaker-modal').classList.add('visible')"> |
|
|
انتخاب گوینده از لیست |
|
|
</button> |
|
|
<div id="tts-selected-speaker-name" style="text-align:center; margin-bottom:1rem; font-weight:bold;">گوینده پیشفرض: شهاب</div> |
|
|
</div> |
|
|
<button class="generate-btn" id="btn-tts-generate" onclick="generateTTS()"> |
|
|
<span>تولید صدا</span> <div class="spinner"></div> |
|
|
</button> |
|
|
|
|
|
<div class="output-box" id="tts-output"> |
|
|
<p style="color:var(--text-secondary)">فایل صوتی اینجا ظاهر میشود</p> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
|
|
|
<section id="section-vc" class="content-section"> |
|
|
<div class="landing-hero"> |
|
|
<h2>تغییر صدای جادویی</h2> |
|
|
<p>صدای خود را ضبط کنید و آن را به صدای خوانندگان یا گویندگان مشهور تبدیل کنید. بدون افت کیفیت، با قدرت هوش مصنوعی.</p> |
|
|
</div> |
|
|
|
|
|
<div class="main-card"> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label>۱. مدلی که میخواهید صدایتان شبیه آن شود را انتخاب کنید:</label> |
|
|
<div class="model-grid" id="vc-model-grid"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="form-group"> |
|
|
<label>۲. صدای خود را آپلود کنید (۳ تا ۹ ثانیه، بدون نویز):</label> |
|
|
<input type="file" id="vc-source-file" accept="audio/*" style="display:none" onchange="handleFileSelect(this)"> |
|
|
<div class="file-upload-box" onclick="document.getElementById('vc-source-file').click()"> |
|
|
<div style="font-size:2rem; margin-bottom:0.5rem;">🎤</div> |
|
|
<p>برای انتخاب فایل کلیک کنید</p> |
|
|
</div> |
|
|
<div id="vc-file-info" class="file-info" style="display:none;"> |
|
|
<span id="vc-filename"></span> |
|
|
<span style="color:green">✓ آماده</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<button class="generate-btn" id="btn-vc-generate" onclick="generateVoiceConversion()"> |
|
|
<span>تغییر صدا (جادوی آلفا)</span> <div class="spinner"></div> |
|
|
</button> |
|
|
|
|
|
|
|
|
<div class="output-box" id="vc-output"> |
|
|
<p id="vc-status-text" style="color:var(--text-secondary)">منتظر دستور شما...</p> |
|
|
<div id="vc-loader" style="display:none; text-align:center;"> |
|
|
<div class="spinner" style="display:inline-block; border-color:var(--accent-primary); border-top-color:transparent;"></div> |
|
|
<p style="margin-top:10px; font-size:0.9rem;">در حال پردازش در سرور ابری... (ممکن است ۱ دقیقه طول بکشد)</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="faq-section"> |
|
|
<h3>سوالات متداول تغییر صدا</h3> |
|
|
<div class="faq-item"> |
|
|
<h3>چطور بهترین کیفیت را بگیرم؟</h3> |
|
|
<p>فایل ورودی شما باید بسیار باکیفیت و بدون نویز محیط باشد. بهتر است در محیطی ساکت ضبط کنید.</p> |
|
|
</div> |
|
|
<div class="faq-item"> |
|
|
<h3>آیا صدای من ذخیره میشود؟</h3> |
|
|
<p>خیر، فایلهای شما فقط برای پردازش استفاده شده و بلافاصله پس از تولید فایل نهایی از سرورهای ما حذف میشوند.</p> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
|
|
|
<section id="section-podcast" class="content-section"> |
|
|
<div class="coming-soon-card"> |
|
|
<h2 style="font-size:2.5em; margin-bottom:1rem;">استودیو پادکست</h2> |
|
|
<p style="font-size:1.2em; opacity:0.8;">به زودی... یک انقلاب در تولید محتوای صوتی</p> |
|
|
<p style="margin-top:2rem;">سناریو بدهید، پادکست چند نفره تحویل بگیرید.</p> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
|
|
|
<div id="email-modal" class="modal-overlay" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;"> |
|
|
<div style="background:white; padding:2rem; border-radius:20px; width:90%; max-width:400px; text-align:center;"> |
|
|
<h3>ورود / ثبت نام</h3> |
|
|
<input type="email" id="auth-email" placeholder="ایمیل خود را وارد کنید" style="margin-bottom:1rem;"> |
|
|
<button class="generate-btn" onclick="sendAuthCode()">ارسال کد تایید</button> |
|
|
<div id="auth-step-2" style="display:none; margin-top:1rem;"> |
|
|
<input type="text" id="auth-code" placeholder="کد تایید" style="margin-bottom:1rem;"> |
|
|
<button class="generate-btn" onclick="verifyAuthCode()">تایید</button> |
|
|
</div> |
|
|
<button onclick="document.getElementById('email-modal').style.display='none'" style="margin-top:1rem; background:none; border:none; color:#888;">بستن</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<audio id="hidden-player" style="display:none;"></audio> |
|
|
|
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const PROXY_URL = '/tts/proxy.php'; |
|
|
let currentUser = { email: null, status: 'free' }; |
|
|
let selectedVcModel = null; |
|
|
let selectedTtsSpeaker = 'Charon'; |
|
|
|
|
|
|
|
|
const VC_MODELS = [ |
|
|
{ id: 'shadmehr', name: 'شادمهر عقیلی', img: 'https://uploadkon.ir/uploads/55c918_25شادمهر-قوی-2-.mp3.jpg' }, |
|
|
{ id: 'moein', name: 'معین', img: 'https://uploadkon.ir/uploads/f8bb17_25معین-2-.mp3.jpg' }, |
|
|
{ id: 'billie', name: 'بیلی آیلیش', img: 'https://uploadkon.ir/uploads/c21018_25بیلی-آیلیش-2-.mp3.jpg' }, |
|
|
{ id: 'chavoshi', name: 'محسن چاوشی', img: 'https://uploadkon.ir/uploads/7ca518_25محسن-چاووشی-3-2-.mp3.jpg' }, |
|
|
{ id: 'pishro', name: 'رضا پیشرو', img: 'https://uploadkon.ir/uploads/placeholder.jpg' } |
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
checkLogin(); |
|
|
renderVcModels(); |
|
|
|
|
|
switchTab('tts'); |
|
|
}); |
|
|
|
|
|
function switchTab(tabId) { |
|
|
document.querySelectorAll('.nav-item').forEach(btn => btn.classList.remove('active')); |
|
|
document.querySelectorAll('.content-section').forEach(sec => sec.classList.remove('active')); |
|
|
|
|
|
document.querySelector(`.nav-item[onclick="switchTab('${tabId}')"]`).classList.add('active'); |
|
|
document.getElementById(`section-${tabId}`).classList.add('active'); |
|
|
} |
|
|
|
|
|
function renderVcModels() { |
|
|
const grid = document.getElementById('vc-model-grid'); |
|
|
|
|
|
const models = [ |
|
|
{ id: 'shadmehr', name: 'شادمهر', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/1000188203.jpg?_t=1725334498' }, |
|
|
{ id: 'moein', name: 'معین', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/5dbc55de-d6ab-442f-9a00-da874521cc0b.jpg?_t=1725334795' }, |
|
|
{ id: 'billie', name: 'بیلی آیلیش', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/1551c598-f02f-4ced-a037-33d2d7317edd.jpg?_t=1726723022' }, |
|
|
{ id: 'chavoshi', name: 'چاوشی', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/c52eefb1-071e-40ea-9bc2-e20a7c29cb81.jpg?_t=1726907812' } |
|
|
]; |
|
|
|
|
|
grid.innerHTML = ''; |
|
|
models.forEach(model => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'model-item'; |
|
|
div.onclick = () => selectVcModel(model.id, div); |
|
|
div.innerHTML = `<img src="${model.img}" alt="${model.name}"><span>${model.name}</span>`; |
|
|
grid.appendChild(div); |
|
|
}); |
|
|
} |
|
|
|
|
|
function selectVcModel(id, el) { |
|
|
document.querySelectorAll('.model-item').forEach(i => i.classList.remove('selected')); |
|
|
el.classList.add('selected'); |
|
|
selectedVcModel = id; |
|
|
} |
|
|
|
|
|
function handleFileSelect(input) { |
|
|
if(input.files && input.files[0]) { |
|
|
document.getElementById('vc-filename').textContent = input.files[0].name; |
|
|
document.getElementById('vc-file-info').style.display = 'flex'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function generateVoiceConversion() { |
|
|
if (!currentUser.email) { alert("لطفا وارد شوید"); showEmailModal(); return; } |
|
|
if (!selectedVcModel) { alert("لطفا یک مدل انتخاب کنید"); return; } |
|
|
const fileInput = document.getElementById('vc-source-file'); |
|
|
if (!fileInput.files[0]) { alert("لطفا فایل صدای خود را آپلود کنید"); return; } |
|
|
|
|
|
const btn = document.getElementById('btn-vc-generate'); |
|
|
const output = document.getElementById('vc-output'); |
|
|
const loader = document.getElementById('vc-loader'); |
|
|
const statusText = document.getElementById('vc-status-text'); |
|
|
|
|
|
|
|
|
btn.disabled = true; |
|
|
btn.querySelector('.spinner').style.display = 'inline-block'; |
|
|
loader.style.display = 'block'; |
|
|
statusText.style.display = 'none'; |
|
|
output.classList.remove('ready'); |
|
|
output.innerHTML = ''; |
|
|
output.appendChild(statusText); |
|
|
output.appendChild(loader); |
|
|
|
|
|
try { |
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('email', currentUser.email); |
|
|
formData.append('source_audio', fileInput.files[0]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const modelRefUrl = `/refs/${selectedVcModel}.wav`; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dummyBlob = new Blob(["dummy"], { type: "audio/wav" }); |
|
|
formData.append('ref_audio', dummyBlob, 'ref.wav'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uploadRes = await fetch(PROXY_URL + '?endpoint=vc-upload', { |
|
|
method: 'POST', |
|
|
body: formData |
|
|
}); |
|
|
|
|
|
if(!uploadRes.ok) throw new Error("Upload failed"); |
|
|
const uploadData = await uploadRes.json(); |
|
|
const jobId = uploadData.job_id; |
|
|
|
|
|
|
|
|
const checkStatus = async () => { |
|
|
const statusRes = await fetch(PROXY_URL + '?endpoint=vc-status', { |
|
|
method: 'POST', |
|
|
body: JSON.stringify({ job_id: jobId }) |
|
|
}); |
|
|
const statusData = await statusRes.json(); |
|
|
|
|
|
if (statusData.status === 'completed') { |
|
|
|
|
|
const audioUrl = `https://ezmary-sada.hf.space/download/${statusData.filename}`; |
|
|
|
|
|
loader.style.display = 'none'; |
|
|
output.classList.add('ready'); |
|
|
output.innerHTML = ` |
|
|
<p style="color:green; font-weight:bold;">تغییر صدا با موفقیت انجام شد!</p> |
|
|
<audio controls src="${audioUrl}"></audio> |
|
|
<a href="${audioUrl}" class="generate-btn" style="margin-top:1rem; text-decoration:none;" download>دانلود فایل</a> |
|
|
`; |
|
|
btn.disabled = false; |
|
|
btn.querySelector('.spinner').style.display = 'none'; |
|
|
} else if (statusData.status === 'failed') { |
|
|
throw new Error("پردازش با خطا مواجه شد."); |
|
|
} else { |
|
|
|
|
|
setTimeout(checkStatus, 3000); |
|
|
} |
|
|
}; |
|
|
|
|
|
checkStatus(); |
|
|
|
|
|
} catch (e) { |
|
|
alert("خطا: " + e.message); |
|
|
btn.disabled = false; |
|
|
btn.querySelector('.spinner').style.display = 'none'; |
|
|
loader.style.display = 'none'; |
|
|
statusText.style.display = 'block'; |
|
|
statusText.textContent = "خطا در ارتباط با سرور"; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function showEmailModal() { |
|
|
document.getElementById('email-modal').style.display = 'flex'; |
|
|
} |
|
|
|
|
|
async function checkLogin() { |
|
|
const email = localStorage.getItem('userEmail'); |
|
|
if (email) { |
|
|
|
|
|
try { |
|
|
const res = await fetch('/tts/check_status.php', { |
|
|
method: 'POST', |
|
|
body: JSON.stringify({email}) |
|
|
}); |
|
|
const data = await res.json(); |
|
|
updateUserUI(data.email, data.status); |
|
|
currentUser.email = data.email; |
|
|
currentUser.status = data.status; |
|
|
} catch(e) { console.log("Login check failed"); } |
|
|
} |
|
|
} |
|
|
|
|
|
function updateUserUI(email, status) { |
|
|
if(email) { |
|
|
document.getElementById('login-check-btn').style.display = 'none'; |
|
|
document.getElementById('user-status-container').style.display = 'flex'; |
|
|
document.getElementById('display-email').textContent = email; |
|
|
const subBadge = document.getElementById('display-sub'); |
|
|
subBadge.textContent = status === 'paid' ? 'اشتراک ویژه' : 'رایگان'; |
|
|
if(status==='paid') subBadge.classList.add('paid'); |
|
|
} else { |
|
|
document.getElementById('login-check-btn').style.display = 'block'; |
|
|
document.getElementById('user-status-container').style.display = 'none'; |
|
|
} |
|
|
} |
|
|
|
|
|
async function sendAuthCode() { |
|
|
const email = document.getElementById('auth-email').value; |
|
|
if(!email) return alert('ایمیل را وارد کنید'); |
|
|
|
|
|
try { |
|
|
const res = await fetch('/tts/send_code.php', { |
|
|
method: 'POST', |
|
|
body: JSON.stringify({email}) |
|
|
}); |
|
|
const data = await res.json(); |
|
|
if(data.status === 'success') { |
|
|
document.getElementById('auth-step-2').style.display = 'block'; |
|
|
alert('کد ارسال شد'); |
|
|
} else { |
|
|
alert(data.message); |
|
|
} |
|
|
} catch(e) { alert('خطا در ارسال'); } |
|
|
} |
|
|
|
|
|
async function verifyAuthCode() { |
|
|
const email = document.getElementById('auth-email').value; |
|
|
const code = document.getElementById('auth-code').value; |
|
|
|
|
|
try { |
|
|
const res = await fetch('/tts/verify_code.php', { |
|
|
method: 'POST', |
|
|
body: JSON.stringify({email, code}) |
|
|
}); |
|
|
const data = await res.json(); |
|
|
if(data.status === 'success') { |
|
|
localStorage.setItem('userEmail', email); |
|
|
document.getElementById('email-modal').style.display = 'none'; |
|
|
checkLogin(); |
|
|
} else { |
|
|
alert(data.message); |
|
|
} |
|
|
} catch(e) { alert('خطا'); } |
|
|
} |
|
|
|
|
|
function handleLogout() { |
|
|
localStorage.removeItem('userEmail'); |
|
|
fetch('/tts/logout.php'); |
|
|
location.reload(); |
|
|
} |
|
|
|
|
|
|
|
|
function generateTTS() { |
|
|
|
|
|
alert("بخش تبدیل متن به صدا در حال حاضر برای ادغام آماده است."); |
|
|
} |
|
|
</script> |
|
|
|
|
|
</body> |
|
|
</html> |
|
|
|