// تعامل با سرور FastAPI // متغیر وضعیت برای بازتولید زیرنویس var isRegenerating = false; async function startUpload() { console.log("Upload started..."); const f = document.getElementById('fileIn').files[0]; if(!f) return; if (!db) { try { console.log("Waiting for DB..."); await initDB(); } catch(e) { alert("خطا در بارگذاری دیتابیس. لطفا صفحه را رفرش کنید."); return; } } document.getElementById('homeScreen').style.display = 'none'; document.getElementById('loader').style.display = 'flex'; document.getElementById('queueStatusMsg').innerText = "در حال آپلود و پردازش اولیه..."; const txCheck = db.transaction("projects", "readwrite"); const storeCheck = txCheck.objectStore("projects"); const reqAll = storeCheck.getAll(); reqAll.onsuccess = async (e) => { let projects = e.target.result; projects.sort((a, b) => a.id - b.id); if (projects.length >= 4) { const toDelete = projects[0]; storeCheck.delete(toDelete.id); } const defaultName = `پروژه ${projects.length >= 4 ? 4 : projects.length + 1}`; const fd = new FormData(); fd.append('file', f); try { const r = await fetch('/api/upload', {method:'POST', body:fd}); if (!r.ok) throw new Error("Server Error: " + r.status); const d = await r.json(); state.id = d.file_id; state.w = d.width; state.h = d.height; state.st = { f: 'vazir', fz: 50, col: '#A020F0', bg: '#000000', type: 'solid', y: 150, x: 0, name: 'karaoke_static', radius: 24, paddingX: 5, paddingY: 30 }; let rawSegs = d.segments.map(s => ({...s, isHidden: false})); rawSegs.sort((a, b) => a.start - b.start); for(let i = 0; i < rawSegs.length - 1; i++) { const curr = rawSegs[i]; const next = rawSegs[i+1]; if (curr.end > next.start) { curr.end = next.start; if (curr.words) { curr.words = curr.words.filter(w => w.start < curr.end); if(curr.words.length > 0) { let lastW = curr.words[curr.words.length - 1]; if(lastW.end > curr.end) lastW.end = curr.end; } } } } state.segs = rawSegs; const durSec = rawSegs.length > 0 ? rawSegs[rawSegs.length-1].end : 0; let mm = Math.floor(durSec / 60); let ss = Math.floor(durSec % 60); const durStr = `${mm < 10 ? '0'+mm : mm}:${ss < 10 ? '0'+ss : ss}`; const txSave = db.transaction("projects", "readwrite"); const newProject = { id: Date.now(), name: defaultName, dateStr: getPersianDate(), lastModified: Date.now(), videoBlob: f, state: JSON.parse(JSON.stringify(state)), duration: durStr, thumbnail: null }; txSave.objectStore("projects").add(newProject); txSave.oncomplete = () => { openProject(newProject.id); }; } catch(e) { console.error(e); alert("خطا در آپلود یا پردازش: " + e); loadHome(); } finally { document.getElementById('loader').style.display='none'; document.getElementById('fileIn').value = ''; document.getElementById('queueStatusMsg').innerText = ""; } }; } // تابع جدید: منطق بازتولید زیرنویس با استفاده از Blob ذخیره شده async function regenerateProjectSubtitles() { isRegenerating = true; // فعال کردن فلگ وضعیت // 1. تغییر وضعیت UI شیت به حالت لودینگ const sheet = document.getElementById('sheet-regenerate'); const stepStart = document.getElementById('regen-step-start'); const stepLoad = document.getElementById('regen-step-loading'); const stepSuccess = document.getElementById('regen-step-success'); stepStart.style.display = 'none'; stepLoad.style.display = 'flex'; // اطمینان از باز بودن شیت document.getElementById('sheetOverlay').classList.add('show'); sheet.classList.add('active'); try { // 2. دریافت فایل ویدیو از دیتابیس const videoBlob = await getVideoBlobFromDB(currentProjectId); if (!videoBlob) throw new Error("فایل ویدیو در دیتابیس یافت نشد"); // 3. ارسال به سرور برای پردازش مجدد const fd = new FormData(); fd.append('file', videoBlob); const r = await fetch('/api/upload', {method:'POST', body:fd}); if (!r.ok) throw new Error("خطای سرور: " + r.status); const d = await r.json(); // 4. پردازش سگمنت‌های جدید let rawSegs = d.segments.map(s => ({...s, isHidden: false})); rawSegs.sort((a, b) => a.start - b.start); for(let i = 0; i < rawSegs.length - 1; i++) { const curr = rawSegs[i]; const next = rawSegs[i+1]; if (curr.end > next.start) { curr.end = next.start; if (curr.words) { curr.words = curr.words.filter(w => w.start < curr.end); if(curr.words.length > 0) { let lastW = curr.words[curr.words.length - 1]; if(lastW.end > curr.end) lastW.end = curr.end; } } } } state.id = d.file_id; state.segs = rawSegs; // 5. ذخیره و رندر مجدد renderSegList(); upd(); saveProjectToDB(); // 6. اتمام موفقیت آمیز isRegenerating = false; // غیرفعال کردن فلگ // نمایش انیمیشن تیک داخل شیت برای لحظه‌ای کوتاه stepLoad.style.display = 'none'; stepSuccess.style.display = 'flex'; setTimeout(() => { // بستن شیت closeAllSheets(); // نمایش Toast زیبا در بالا if (typeof showToast === 'function') { showToast("زیرنویس مجدد تولید شد!", "fa-solid fa-circle-check"); } // ریست کردن مراحل شیت برای دفعه بعد setTimeout(() => { stepSuccess.style.display = 'none'; stepStart.style.display = 'flex'; }, 400); }, 1200); } catch (e) { console.error(e); isRegenerating = false; alert("خطا در بازتولید زیرنویس: " + e.message); // بازگشت به حالت اول stepLoad.style.display = 'none'; stepStart.style.display = 'flex'; } } async function generateAIStyle() { const desc = document.getElementById('magicPrompt').value; if (!desc) return; const btn = document.querySelector('.btn-magic-action'); const originalText = btn.innerHTML; btn.innerHTML = '
در حال طراحی...'; btn.disabled = true; try { const r = await fetch('/api/generate-style', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ description: desc }) }); if (!r.ok) throw new Error('Server returned an error'); const styleData = await r.json(); const currentStyleName = state.st.name; const nonCustomizableStyles = ['plain_white', 'white_outline', 'auto_director']; state.st.col = styleData.primaryColor; state.st.bg = styleData.outlineColor; state.st.f = styleData.font; state.st.fz = parseInt(styleData.fontSize, 10) || 65; if (nonCustomizableStyles.includes(currentStyleName)) { setStylePreset('classic', document.querySelector('#customAccordion .style-card')); setMode('solid'); if (!document.getElementById('customAccordion').classList.contains('open')) { toggleCustomAccordion(); } } else { setMode(styleData.backType); } syncUIWithState(); document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked')); const fontBtn = Array.from(document.querySelectorAll('.font-btn')).find(btn => btn.getAttribute('onclick').includes(`'${state.st.f}'`)); if (fontBtn) fontBtn.classList.add('ticked'); upd(); toggleTool('style'); showToast("طراحی هوشمند اعمال شد!", "fa-solid fa-wand-magic-sparkles"); } catch (e) { console.error(e); alert('خطا در ارتباط با هوش مصنوعی. لطفا دوباره تلاش کنید.'); } finally { btn.innerHTML = originalText; btn.disabled = false; } } async function render() { v.pause(); togglePlayIcon(false); document.getElementById('loader').style.display='flex'; const statusMsg = document.getElementById('queueStatusMsg'); statusMsg.innerText = "در حال ارسال به صف..."; const activeSegments = state.segs.filter(s => !s.isHidden); // پاکسازی داده‌ها برای ارسال به سرور (رفع خطای 422) const cleanSegments = activeSegments.map(s => ({ id: s.id, start: s.start, end: s.end, text: s.text, words: s.words ? s.words.map(w => ({ word: w.word, start: w.start, end: w.end, highlight: w.highlight || false, color: w.color || null // ارسال رنگ فقط در صورت وجود })) : [] })); // اطمینان از وجود ابعاد ویدیو let finalW = parseInt(state.w) || 0; let finalH = parseInt(state.h) || 0; // اگر ابعاد صفر بود، سعی کن از ویدیو پلیر بگیری if ((finalW === 0 || finalH === 0) && v) { finalW = v.videoWidth || 1080; finalH = v.videoHeight || 1920; } const pl = { file_id: state.id, segments: cleanSegments, video_width: finalW, video_height: finalH, style: { font: state.st.f, fontSize: state.st.fz, primaryColor: state.st.col, outlineColor: state.st.bg, backType: state.st.type, marginV: state.st.y, x: state.st.x || 0, name: state.st.name, radius: state.st.radius || 16, paddingX: state.st.paddingX || 20, paddingY: state.st.paddingY || 10 } }; try { let r = await fetch('/api/enqueue-render', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(pl)}); let d = await r.json(); if (d.error_code && d.error_code === 'VIDEO_NOT_FOUND') { statusMsg.innerText = "ویدیو در سرور یافت نشد، در حال بارگذاری مجدد..."; const videoBlob = await getVideoBlobFromDB(currentProjectId); const fd = new FormData(); fd.append('file', videoBlob); fd.append('file_id', state.id); const reuploadResponse = await fetch('/api/reupload', { method: 'POST', body: fd }); if (!reuploadResponse.ok) throw new Error('خطا در بارگذاری مجدد ویدیو.'); statusMsg.innerText = "بارگذاری مجدد موفق بود، ارسال دوباره به صف..."; r = await fetch('/api/enqueue-render', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(pl)}); d = await r.json(); } if (d.job_id) { pollStatus(d.job_id); } else { throw new Error(d.error || "خطا لطفاً این پروژه را حذف و ویدیو را مجدداً آپلود کنید."); } } catch(e) { console.error(e); alert('خطا: ' + e.message); document.getElementById('loader').style.display='none'; } } function pollStatus(jobId) { const statusMsg = document.getElementById('queueStatusMsg'); const interval = setInterval(async () => { try { const r = await fetch(`/api/job-status/${jobId}`); const d = await r.json(); if (d.status === 'queued') statusMsg.innerHTML = `شما نفر ${d.queue_position} در صف ساخت هستید...`; else if (d.status === 'processing') statusMsg.innerText = "نوبت شماست! در حال ساخت ویدیو..."; else if (d.status === 'completed') { clearInterval(interval); showFinalResult(d.url); } else if (d.status === 'failed') { clearInterval(interval); alert("خطا در ساخت ویدیو: " + d.error); document.getElementById('loader').style.display='none'; } } catch (e) { console.error("خطا در چک کردن وضعیت", e); } }, 2500); } function showFinalResult(url) { document.getElementById('loader').style.display='none'; const resVid = document.getElementById('resultVideo'); resVid.src = url + "?t=" + new Date().getTime(); resVid.load(); const fullUrl = new URL(url, window.location.origin).href; const dlBtn = document.getElementById('downloadBtn'); dlBtn.href = fullUrl; dlBtn.onclick = function(e) { e.preventDefault(); window.parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: fullUrl }, '*'); }; document.getElementById('resultScreen').style.display='flex'; resVid.play(); } function closeResult() { document.getElementById('resultScreen').style.display='none'; const rv = document.getElementById('resultVideo'); rv.pause(); rv.src = ""; }