Zirnavis100 / static /js /globals.js
Transfer Bot
Moved to Hugging Face automatically
8e64a96
Raw
History Blame Contribute Delete
19.7 kB
// متغیرهای دیتابیس
const DB_NAME = "SubtitleStudioDB";
const DB_VERSION = 1;
let db;
let currentProjectId = null;
let currentActionProjectId = null;
let currentActionProjectName = "";
// وضعیت سراسری برنامه
let state = {
id: null, w: 1080, h: 1920, segs: [], language: 'fa',
st: { f: 'pinar', fz: 45, col: '#A020F0', bg: '#000000', type: 'solid', y: 150, x: 0, name: 'alpha_gradient', radius: 24, paddingX: 5, paddingY: 15, useActiveColor: true }
};
let curSeg = -1;
let manualOverride = false;
// --- چندزبانه (Multi-language) ---
// لیست پیش‌فرض (در صورت عدم دسترسی به سرور موقع بارگذاری صفحه از همین استفاده می‌شود)
let LANGUAGES_LIST = [
{ code: 'fa', label: 'فارسی', flag: '🇮🇷', rtl: true },
{ code: 'en', label: 'English', flag: '🇺🇸', rtl: false },
{ code: 'ar', label: 'العربية', flag: '🇸🇦', rtl: true },
{ code: 'tr', label: 'Türkçe', flag: '🇹🇷', rtl: false },
{ code: 'es', label: 'Español', flag: '🇪🇸', rtl: false },
{ code: 'fr', label: 'Français', flag: '🇫🇷', rtl: false },
{ code: 'de', label: 'Deutsch', flag: '🇩🇪', rtl: false },
{ code: 'it', label: 'Italiano', flag: '🇮🇹', rtl: false },
{ code: 'pt', label: 'Português', flag: '🇵🇹', rtl: false },
{ code: 'ur', label: 'اردو', flag: '🇵🇰', rtl: true },
];
let selectedLanguage = 'fa'; // زبانی که کاربر برای پروژه بعدی انتخاب کرده
let pendingSelectedVideoFile = null; // فایل انتخاب‌شده پیش از شروع آپلود
function getLangInfo(code) {
return LANGUAGES_LIST.find(l => l.code === code) || LANGUAGES_LIST[0];
}
function getDefaultFontForLang(code) {
if (code === 'fa') return 'pinar';
if (code === 'ar') return 'amiri';
if (code === 'ur') return 'nastaliq';
return 'vazir';
}
async function initLanguagesList() {
try {
const r = await fetch('/api/languages');
if (r.ok) {
const d = await r.json();
if (d.languages && d.languages.length > 0) {
LANGUAGES_LIST = d.languages;
}
}
} catch (e) { console.warn('Could not fetch languages list, using defaults', e); }
renderLanguageList();
updateLanguageButtons();
}
function renderLanguageList() {
const box = document.getElementById('languageList');
if (!box) return;
box.innerHTML = LANGUAGES_LIST.map(l => `
<div class="lang-row ${l.code === selectedLanguage ? 'active' : ''}" onclick="selectLanguage('${l.code}')">
<span class="lang-flag">${l.flag}</span>
<span class="lang-name">${l.label}</span>
${l.code === selectedLanguage ? '<i class="fa-solid fa-circle-check lang-check"></i>' : ''}
</div>
`).join('');
}
function selectLanguage(code) {
selectedLanguage = code;
renderLanguageList();
updateLanguageButtons();
closeLanguageSheet();
}
function updateLanguageButtons() {
const info = getLangInfo(selectedLanguage);
const homeFlag = document.getElementById('homeLangFlag');
const homeLabel = document.getElementById('homeLangLabel');
if (homeFlag) homeFlag.innerText = info.flag;
if (homeLabel) homeLabel.innerText = info.label;
const pillFlag = document.getElementById('langPillFlag');
const pillLabel = document.getElementById('langPillLabel');
if (pillFlag) pillFlag.innerText = info.flag;
if (pillLabel) pillLabel.innerText = info.label;
}
function openLanguageSheet() {
renderLanguageList();
document.getElementById('languageSheetOverlay').style.display = 'flex';
}
function closeLanguageSheet() {
document.getElementById('languageSheetOverlay').style.display = 'none';
}
function formatFileSize(bytes) {
if (!bytes) return '۰ مگابایت';
const mb = bytes / (1024 * 1024);
if (mb < 1) return Math.round(bytes / 1024) + ' کیلوبایت';
return mb.toFixed(1) + ' مگابایت';
}
// وقتی کاربر ویدیویی را از گالری/فایل‌ها انتخاب می‌کند: بلافاصله آپلود شروع نمی‌شود
// بلکه کارت پیش‌نمایش با انتخاب زبان نمایش داده می‌شود تا کاربر تایید کند
function onVideoFileChosen() {
const inp = document.getElementById('fileIn');
const f = inp.files && inp.files[0];
if (!f) return;
pendingSelectedVideoFile = f;
document.getElementById('preUploadFileName').innerText = f.name;
document.getElementById('preUploadFileSize').innerText = formatFileSize(f.size);
updateLanguageButtons();
const overlay = document.getElementById('preUploadOverlay');
overlay.style.display = 'flex';
setTimeout(() => overlay.classList.add('show'), 10);
}
function cancelPreUpload() {
pendingSelectedVideoFile = null;
document.getElementById('fileIn').value = '';
const overlay = document.getElementById('preUploadOverlay');
overlay.classList.remove('show');
setTimeout(() => overlay.style.display = 'none', 250);
}
function confirmStartUpload() {
const overlay = document.getElementById('preUploadOverlay');
overlay.classList.remove('show');
setTimeout(() => overlay.style.display = 'none', 250);
startUpload();
}
// --- ترجمه متن نمونه در قالب‌های آماده (سلام اینجا آلفا است) ---
const DEMO_WORDS = {
fa: ["سلام", "اینجا", "آلفا", "است"],
en: ["Hi", "this", "is", "Alpha"],
ar: ["مرحباً", "هنا", "ألفا", "الآن"],
tr: ["Merhaba", "burada", "Alfa", "şimdi"],
es: ["Hola", "aquí", "Alfa", "ahora"],
fr: ["Salut", "ici", "Alpha", "maintenant"],
de: ["Hallo", "hier", "Alpha", "jetzt"],
it: ["Ciao", "qui", "Alfa", "adesso"],
pt: ["Oi", "aqui", "Alfa", "agora"],
ur: ["ہیلو", "یہاں", "الفا", "ہے"],
};
function applyDemoLanguage(langCode) {
const info = getLangInfo(langCode);
const words = DEMO_WORDS[langCode] || DEMO_WORDS.fa;
const isRtl = info.rtl;
// چیدمان راست‌به‌چپ/چپ‌به‌راست کارت‌های پیش‌نمایش قالب‌ها بر اساس زبان پروژه
document.querySelectorAll('.demo-row').forEach(row => {
row.style.direction = isRtl ? 'rtl' : 'ltr';
});
// کلمه‌های تکی
document.querySelectorAll('[data-dw]').forEach(el => {
const idx = parseInt(el.getAttribute('data-dw'), 10);
if (!isNaN(idx) && words[idx] !== undefined) el.textContent = words[idx];
});
// بازه‌ای از کلمات (مثلاً کلمات ۱ تا ۳ با هم در یک span)
document.querySelectorAll('[data-dw-range]').forEach(el => {
const range = el.getAttribute('data-dw-range').split('-').map(Number);
el.textContent = words.slice(range[0], range[1] + 1).join(' ');
});
// عبارت کامل در یک تکه (بدون نیاز به تغییر جهت چون یک متن یکپارچه است)
document.querySelectorAll('.demo-full').forEach(el => {
el.textContent = words.join(' ');
});
}
function aiEase(name, p) {
p = Math.max(0, Math.min(1, p));
if (name === 'linear') return p;
if (name === 'punch') return 1 - Math.pow(1 - p, 3);
if (name === 'elastic') {
if (p <= 0 || p >= 1) return p;
return Math.pow(2, -10 * p) * Math.sin((p * 10 - 0.75) * (2 * Math.PI) / 3) + 1;
}
// smooth (پیش‌فرض)
return 1 - (1 - p) * (1 - p);
}
function applyEditorTextDirection(langCode) {
const info = getLangInfo(langCode);
const dir = info.rtl ? 'rtl' : 'ltr';
const activeTextEl = document.getElementById('activeText');
if (activeTextEl) activeTextEl.dir = dir;
const timelineEl = document.getElementById('timelineScroll');
if (timelineEl) timelineEl.dir = dir;
}
// المان‌های رابط کاربری
const v = document.getElementById('vid');
const tEl = document.getElementById('activeText');
const toolsContainer = document.getElementById('toolsContainer');
// متغیرهای ویرایش متن
let activeWordId = null; // Format: "segIndex-wordIndex"
let previewInterval;
let tempStartTime = 0;
let tempEndTime = 0;
// متغیرهای تاچ
let initialY = 0, initialX = 0, initialBottom = 0, initialXState = 0, initialDist = 0, initialFontSize = 0, touchMode = null;
// --- تابع اصلی نمایش زیرنویس در مرورگر (با منطق جدید) ---
function updateOverlayContent(currentTime) {
if (!state.segs) return;
const idx = state.segs.findIndex(s => currentTime >= s.start && currentTime < s.end);
if(idx !== -1) {
const seg = state.segs[idx];
if(seg.isHidden) { tEl.style.opacity = 0; }
else {
tEl.style.opacity = 1;
// ==========================================================
// === بلوک ۱: منطق قدیمی برای استایل‌های کارائوکه (دست‌نخورده) ===
// ==========================================================
const karaokeStyles = ['auto_director', 'karaoke_static', 'instagram_box', 'alpha_gradient'];
if(karaokeStyles.includes(state.st.name) && seg.words) {
let html = "";
const GAP = 5;
seg.words.forEach((w, i) => {
let isActive = (currentTime >= w.start && currentTime <= w.end);
let boxColor = state.st.col;
let textColor = '#ffffff';
if (state.st.name === 'instagram_box') {
boxColor = '#1a1a1a';
if (isActive) { textColor = '#ffffff'; }
else if (currentTime > w.end) { textColor = '#000000'; }
else { textColor = '#d1d1d6'; }
} else if (state.st.name === 'alpha_gradient') {
boxColor = 'linear-gradient(135deg, #7c4dff, #b388ff)';
if (isActive) { textColor = '#ffffff'; }
else if (currentTime > w.end) { textColor = '#ffffff'; }
else { textColor = 'rgba(255,255,255,0.35)'; }
} else if (state.st.name === 'auto_director') {
boxColor = (i % 2 === 0 ? '#00D7FF' : '#FF0080');
}
if (w.color) textColor = w.color;
let baseStyle = "display: inline-block; vertical-align: middle; transition: all 0.1s; font-family: inherit;";
if(isActive) {
let py = state.st.paddingY; let px = state.st.paddingX;
html += `<span style="${baseStyle} background: ${boxColor}; color: ${textColor} !important; border-radius: ${state.st.radius}px; box-shadow: 0 0 10px rgba(0,0,0,0.3); padding: ${py}px ${px}px; margin: -${py}px calc(${GAP}px - ${px}px); position: relative; z-index: 100; white-space: nowrap;">${w.word}</span>`;
} else {
html += `<span style="${baseStyle} color: ${textColor} !important; text-shadow: none; margin: 0 ${GAP}px; padding: 0; position: relative; z-index: 1;">${w.word}</span>`;
}
if ((i + 1) % 5 === 0 && i !== seg.words.length - 1) { html += "<br><br>"; }
});
tEl.innerHTML = html;
}
// =================================================================
// === بلوک ۲: منطق قدیمی برای استایل تایپی (Progressive) (دست‌نخورده) ===
// =================================================================
else if (state.st.name === 'progressive_write' && seg.words) {
let html = "";
seg.words.forEach((w, i) => {
let styleExtra = w.color ? `color:${w.color} !important;` : "";
let baseStyle = "display: inline-block; vertical-align: middle; margin: 0px 3px;";
if(currentTime >= w.start) html += `<span style="${baseStyle} opacity:1; ${styleExtra}">${w.word}</span> `;
else html += `<span style="${baseStyle} opacity:0; ${styleExtra}">${w.word}</span> `;
if ((i + 1) % 5 === 0 && i !== seg.words.length - 1) { html += "<br><br>"; }
});
tEl.innerHTML = html.trim();
}
// ============================================================================
// === بلوک ۳: منطق جدید و هوشمند برای موزیکال و استایل‌های آینده (کد جدید) ===
// ============================================================================
else if (STYLE_TEMPLATES && STYLE_TEMPLATES[state.st.name]) {
const template = STYLE_TEMPLATES[state.st.name];
// برای استایل‌هایی مثل موزیک پلیر، کل متن سگمنت را جایگزین می‌کنیم
const renderedHtml = template.replace(/{{WORD}}/g, seg.text);
tEl.innerHTML = renderedHtml;
}
// =========================================================
// === بلوک ۴: منطق نهایی برای استایل‌های کلاسیک (دست‌نخورده) ===
// =========================================================
else {
if (seg.words && seg.words.length > 0) {
let html = "";
seg.words.forEach((w, i) => {
let styleExtra = w.color ? `color:${w.color} !important;` : "";
html += `<span style="${styleExtra}">${w.word}</span> `;
if ((i + 1) % 5 === 0 && i !== seg.words.length - 1) { html += "<br><br>"; }
});
tEl.innerHTML = html;
} else {
tEl.innerText = seg.text;
}
}
}
} else { tEl.style.opacity = 0; }
}
function upd() {
saveProjectToDB();
const elFz = document.getElementById('fz');
if(elFz) state.st.fz = Math.round(10 + (parseFloat(elFz.value) / 100) * 140);
const elPos = document.getElementById('pos');
if(elPos) state.st.y = Math.round((parseFloat(elPos.value) / 100) * 1200);
const elPosX = document.getElementById('posX');
if(elPosX) state.st.x = Math.round((parseFloat(elPosX.value) / 100) * 500);
const elRadius = document.getElementById('radius');
if(elRadius) state.st.radius = parseInt(elRadius.value, 10);
const elPadX = document.getElementById('paddingX');
if(elPadX) state.st.paddingX = parseInt(elPadX.value, 10);
const elPadY = document.getElementById('paddingY');
if(elPadY) state.st.paddingY = parseInt(elPadY.value, 10);
updateColorPreviewButtons();
let font = 'Vazirmatn';
if(state.st.f === 'lalezar') font = 'Lalezar';
if(state.st.f === 'bangers') font = 'Impact';
if(state.st.f === 'roboto') font = 'Arial';
tEl.style.fontFamily = font;
tEl.style.fontSize = state.st.fz + 'px';
tEl.style.lineHeight = '0.8';
tEl.style.bottom = state.st.y + 'px';
tEl.style.textAlign = 'center';
tEl.style.left = '50%';
tEl.style.transform = `translateX(calc(-50% + ${state.st.x || 0}px))`;
tEl.style.padding = '0';
tEl.style.borderRadius = '0px';
const karaokeStyles = ['karaoke_static', 'auto_director', 'alpha_gradient'];
if (state.st.name === 'instagram_box') {
tEl.style.backgroundColor = '#ffffff';
tEl.style.color = '#d1d1d6';
tEl.style.webkitTextStroke = '0px';
tEl.style.textShadow = 'none';
const finalPaddingTop = (state.st.paddingY || 10) + 4;
const finalPaddingBottom = (state.st.paddingY || 10) + 4;
const finalPaddingX = (state.st.paddingX || 20) + 15;
tEl.style.padding = `${finalPaddingTop}px ${finalPaddingX}px ${finalPaddingBottom}px ${finalPaddingX}px`;
tEl.style.borderRadius = `${state.st.radius || 16}px`;
} else if(karaokeStyles.includes(state.st.name)) {
tEl.style.backgroundColor = 'transparent'; tEl.style.color = '#FFFFFF'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
} else if (state.st.name === 'plain_white' || state.st.name === 'white_outline') {
tEl.style.color = '#FFFFFF'; tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = (state.st.name === 'white_outline') ? `${Math.max(3, state.st.fz / 4.5)}px #000000` : '0px'; tEl.style.paintOrder = 'stroke fill'; tEl.style.webkitPaintOrder = 'stroke fill'; tEl.style.textShadow = 'none';
} else {
if(!state.st.col) state.st.col = '#FFFFFF'; if(!state.st.bg) state.st.bg = '#000000';
tEl.style.color = state.st.col;
tEl.style.padding = `${state.st.paddingY}px ${state.st.paddingX}px`;
tEl.style.borderRadius = `${state.st.radius}px`;
if(state.st.type === 'solid' || state.st.type === 'transparent') {
let bgColor = state.st.bg;
if (state.st.type === 'transparent') {
if(bgColor.startsWith('#') && bgColor.length === 7) {
let r = parseInt(bgColor.substring(1, 3), 16); let g = parseInt(bgColor.substring(3, 5), 16); let b = parseInt(bgColor.substring(5, 7), 16);
bgColor = `rgba(${r},${g},${b},0.6)`;
} else if(bgColor.startsWith('rgba')) {
bgColor = bgColor.replace(/[\d.]+\)$/, '0.6)');
}
}
tEl.style.backgroundColor = bgColor;
tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
} else if (state.st.type === 'outline') {
tEl.style.backgroundColor = 'transparent';
const s = Math.max(3, state.st.fz / 4.5); tEl.style.webkitTextStroke = `${s}px ${state.st.bg}`;
tEl.style.paintOrder = 'stroke fill'; tEl.style.webkitPaintOrder = 'stroke fill';
tEl.style.textShadow = 'none';
} else {
tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
}
}
const boxControlsPanel = document.getElementById('boxControlsPanel');
const currentConfig = STYLE_CONFIGS[state.st.name];
if (boxControlsPanel && currentConfig) {
const hasRadius = currentConfig.panels.includes('radius');
boxControlsPanel.style.display = hasRadius ? 'block' : 'none';
const boxPreview = document.getElementById('boxPreview');
if (boxPreview && hasRadius) {
boxPreview.style.borderRadius = `${state.st.radius || 16}px`;
boxPreview.style.padding = `${state.st.paddingY || 10}px ${state.st.paddingX || 20}px`;
let previewBg = '#000';
if(state.st.name === 'instagram_box') previewBg = '#ffffff';
else if(state.st.name === 'alpha_gradient') previewBg = 'linear-gradient(135deg, #7c4dff, #b388ff)';
else if(state.st.bg) previewBg = state.st.bg;
boxPreview.style.background = previewBg;
boxPreview.style.color = (state.st.name === 'instagram_box') ? '#000' : '#fff';
}
}
}