Elias207's picture
Update static/js/api.js
2498a32 verified
// تعامل با سرور 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 = '<div class="spinner" style="width:20px;height:20px;border-width:3px;"></div> در حال طراحی...';
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 = `شما نفر <span style="color:#00e676; font-size:1.3em;">${d.queue_position}</span> در صف ساخت هستید...`;
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 = ""; }