Spaces:
Sleeping
Sleeping
Delete index.html
Browse files- index.html +0 -576
index.html
DELETED
|
@@ -1,576 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="fa" dir="rtl">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Wan2.2-S2V Video Generator</title>
|
| 7 |
-
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 8 |
-
<style>
|
| 9 |
-
:root {
|
| 10 |
-
--app-font: 'Vazirmatn', sans-serif;
|
| 11 |
-
--app-bg: #F8F9FC;
|
| 12 |
-
--panel-bg: #FFFFFF;
|
| 13 |
-
--panel-border: #EAEFF7;
|
| 14 |
-
--input-bg: #F6F8FB;
|
| 15 |
-
--input-border: #E1E7EF;
|
| 16 |
-
--text-primary: #1A202C;
|
| 17 |
-
--text-secondary: #626F86;
|
| 18 |
-
--text-tertiary: #8A94A6;
|
| 19 |
-
--accent-primary: #4A6CFA;
|
| 20 |
-
--accent-primary-hover: #3553D6;
|
| 21 |
-
--accent-primary-glow: rgba(74, 108, 250, 0.25);
|
| 22 |
-
--accent-secondary: #0FD4A8;
|
| 23 |
-
--success-color: #38A169;
|
| 24 |
-
--danger-color: #e53e3e;
|
| 25 |
-
--danger-color-hover: #c53030;
|
| 26 |
-
--shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
|
| 27 |
-
--shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
|
| 28 |
-
--shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
|
| 29 |
-
--shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
|
| 30 |
-
--radius-card: 24px;
|
| 31 |
-
--radius-btn: 14px;
|
| 32 |
-
--radius-input: 12px;
|
| 33 |
-
--transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
@keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
|
| 37 |
-
@keyframes fill-progress { from { width: 0%; } to { width: 100%; } }
|
| 38 |
-
@keyframes pulse-loader { 0% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } 50% { box-shadow: 0 0 60px rgba(56, 189, 248, 0.7); } 100% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } }
|
| 39 |
-
@keyframes fade-noise { 0% { opacity: 1; filter: blur(5px); } 30% { opacity: 0.8; filter: blur(2px); } 100% { opacity: 0; filter: blur(0px); } }
|
| 40 |
-
@keyframes node-pulse { 0%, 100% { transform: scale(0.98); box-shadow: 0 0 20px -5px var(--accent-primary-glow); } 50% { transform: scale(1.02); box-shadow: 0 0 30px 0px var(--accent-primary-glow); } }
|
| 41 |
-
@keyframes waveform-pulse { 0% { transform: scaleY(0.2); } 50% { transform: scaleY(1); } 100% { transform: scaleY(0.2); } }
|
| 42 |
-
@keyframes path-flow { 0% { offset-distance: 0%; opacity: 1; } 90% { opacity: 1; } 100% { offset-distance: 100%; opacity: 0; } }
|
| 43 |
-
@keyframes scan-glow { 50% { filter: drop-shadow(0 0 6px var(--accent-secondary)); opacity: 1; } }
|
| 44 |
-
|
| 45 |
-
body { font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 2.5rem 1rem; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; }
|
| 46 |
-
.container { max-width: 820px; width: 100%; }
|
| 47 |
-
header { position: relative; text-align: center; margin-bottom: 2.5rem; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; }
|
| 48 |
-
#neural-network-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
|
| 49 |
-
.header-content { position: relative; z-index: 2; }
|
| 50 |
-
.ai-synapse-container { width: 300px; height: 120px; margin: 0 auto 1.5rem; display: flex; align-items: center; justify-content: space-between; position: relative; }
|
| 51 |
-
.synapse-node { width: 80px; height: 80px; position: relative; display: flex; align-items: center; justify-content: center; background: radial-gradient(circle, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); border-radius: 50%; border: 1px solid rgba(74, 108, 250, 0.2); animation: node-pulse 6s ease-in-out infinite; }
|
| 52 |
-
.node-glow { position: absolute; width: 100%; height: 100%; border-radius: 50%; background: var(--accent-primary); filter: blur(20px); opacity: 0.3; }
|
| 53 |
-
.synapse-node.audio-node .node-glow { background: var(--accent-secondary); animation-delay: -3s; }
|
| 54 |
-
.node-icon { width: 60%; height: 60%; position: relative; z-index: 2; display: flex; align-items: center; justify-content: center; }
|
| 55 |
-
.audio-node .node-icon { gap: 4px; }
|
| 56 |
-
.waveform-bar { width: 4px; height: 100%; background-color: var(--accent-secondary); border-radius: 2px; animation: waveform-pulse 1.2s ease-in-out infinite; }
|
| 57 |
-
.waveform-bar:nth-child(1) { height: 60%; animation-delay: 0s; }
|
| 58 |
-
.waveform-bar:nth-child(2) { height: 100%; animation-delay: -0.2s; }
|
| 59 |
-
.waveform-bar:nth-child(3) { height: 80%; animation-delay: -0.4s; }
|
| 60 |
-
.waveform-bar:nth-child(4) { height: 50%; animation-delay: -0.6s; }
|
| 61 |
-
.image-node .node-icon svg { width: 100%; height: 100%; stroke: var(--accent-primary); stroke-width: 1.5; filter: drop-shadow(0 0 3px var(--accent-primary)); animation: scan-glow 3s ease-in-out infinite; }
|
| 62 |
-
|
| 63 |
-
.synapse-path-svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 140px; height: 60px; overflow: visible; }
|
| 64 |
-
.synapse-path { stroke: url(#synapse-gradient); stroke-width: 2; fill: none; stroke-dasharray: 4 8; stroke-linecap: round; }
|
| 65 |
-
.synapse-particle { width: 5px; height: 5px; border-radius: 50%; background: white; position: absolute; top: 0; left: 0; offset-path: path("M5,30 C40,0 100,60 135,30"); animation: path-flow 3s linear infinite; box-shadow: 0 0 8px 2px white; }
|
| 66 |
-
.synapse-particle:nth-child(2) { animation-delay: -0.75s; }
|
| 67 |
-
.synapse-particle:nth-child(3) { animation-delay: -1.5s; }
|
| 68 |
-
.synapse-particle:nth-child(4) { animation-delay: -2.25s; }
|
| 69 |
-
|
| 70 |
-
h1 { font-size: 2.8rem; font-weight: 800; margin: 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; }
|
| 71 |
-
.subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0.5rem; }
|
| 72 |
-
.instruction { font-size: 0.95rem; color: var(--text-tertiary); margin-top: 0.75rem; font-weight: 500; }
|
| 73 |
-
main { padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); animation: fadeIn 0.8s 0.3s ease-out backwards; }
|
| 74 |
-
.form-group { margin-bottom: 2.5rem; }
|
| 75 |
-
.form-group:last-child { margin-bottom: 0; }
|
| 76 |
-
.form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem; }
|
| 77 |
-
.form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
|
| 78 |
-
|
| 79 |
-
#image-drop-zone { position: relative; border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 2.5rem; text-align: center; cursor: pointer; transition: var(--transition-smooth); background-color: var(--input-bg); min-height: 150px; display: flex; flex-direction: column; justify-content: center; align-items: center; overflow: hidden; }
|
| 80 |
-
#image-drop-zone.drag-over, #image-drop-zone:hover:not(.has-file) { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
|
| 81 |
-
#image-drop-zone.has-file { border-style: solid; border-color: var(--success-color); padding: 0; cursor: default; }
|
| 82 |
-
.upload-content { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
|
| 83 |
-
.upload-icon svg { width: 48px; height: 48px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.8; }
|
| 84 |
-
#image-drop-zone p { margin: 0; color: var(--text-secondary); font-weight: 500; }
|
| 85 |
-
#imagePreview { display: none; width: 100%; height: 100%; object-fit: contain; position: absolute; top: 0; left: 0; }
|
| 86 |
-
#image-drop-zone.has-file .upload-content { display: none; }
|
| 87 |
-
#image-drop-zone.has-file #imagePreview { display: block; }
|
| 88 |
-
|
| 89 |
-
.audio-input-container { position: relative; border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 1.5rem; text-align: center; cursor: pointer; transition: var(--transition-smooth); background-color: var(--input-bg); min-height: 80px; display: flex; justify-content: center; align-items: center; }
|
| 90 |
-
.audio-input-container:hover, .audio-input-container.drag-over { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
|
| 91 |
-
.audio-input-container.has-file { border-style: solid; border-color: var(--success-color); background-color: #fff; box-shadow: 0 0 15px rgba(56, 161, 105, 0.2); cursor: default; }
|
| 92 |
-
#audio-prompt { color: var(--text-secondary); font-weight: 500; }
|
| 93 |
-
.audio-player { display: none; align-items: center; width: 100%; gap: 1.5rem; }
|
| 94 |
-
.audio-input-container.has-file .audio-player { display: flex; }
|
| 95 |
-
.audio-input-container.has-file #audio-prompt { display: none; }
|
| 96 |
-
.play-pause-btn { background: linear-gradient(45deg, var(--accent-primary), var(--accent-secondary)); border: none; border-radius: 50%; width: 52px; height: 52px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: var(--transition-smooth); flex-shrink: 0; box-shadow: var(--shadow-md); }
|
| 97 |
-
.play-pause-btn:hover { transform: scale(1.08); box-shadow: var(--shadow-lg); }
|
| 98 |
-
.play-pause-btn svg { width: 26px; height: 26px; color: white; }
|
| 99 |
-
.play-pause-btn .icon-pause { display: none; }
|
| 100 |
-
.waveform-container { flex-grow: 1; display: flex; flex-direction: column; gap: 0.5rem; }
|
| 101 |
-
#waveform { width: 100%; height: 60px; cursor: pointer; background-color: var(--app-bg); border-radius: 8px; border: 1px solid var(--input-border); }
|
| 102 |
-
.audio-info { font-size: 0.85rem; color: var(--text-tertiary); width: 100%; display: flex; justify-content: space-between; padding: 0 0.25rem; }
|
| 103 |
-
#audioFileName { font-weight: 500; color: var(--text-secondary); word-break: break-all; text-align: right; }
|
| 104 |
-
#resetAudioBtn { position: absolute; top: -12px; left: -12px; z-index: 10; background-color: var(--panel-bg); border: 1px solid var(--panel-border); color: var(--text-tertiary); width: 28px; height: 28px; border-radius: 50%; font-size: 24px; font-weight: bold; line-height: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0 0 4px 1px; box-shadow: var(--shadow-md); transition: var(--transition-smooth); opacity: 0; transform: scale(0.8); pointer-events: none; }
|
| 105 |
-
.audio-input-container.has-file #resetAudioBtn { opacity: 1; transform: scale(1); pointer-events: all; }
|
| 106 |
-
#resetAudioBtn:hover { background-color: var(--danger-color); color: white; border-color: var(--danger-color-hover); transform: scale(1.1) rotate(90deg); }
|
| 107 |
-
|
| 108 |
-
/* استایل بخش انتخاب رزولوشن (جایگزین احساسات) */
|
| 109 |
-
.resolution-container {
|
| 110 |
-
background-color: var(--input-bg);
|
| 111 |
-
border: 2px solid var(--input-border);
|
| 112 |
-
border-radius: var(--radius-input);
|
| 113 |
-
padding: 0.5rem;
|
| 114 |
-
position: relative;
|
| 115 |
-
}
|
| 116 |
-
.resolution-select {
|
| 117 |
-
width: 100%;
|
| 118 |
-
padding: 1rem;
|
| 119 |
-
background-color: transparent;
|
| 120 |
-
border: none;
|
| 121 |
-
font-family: var(--app-font);
|
| 122 |
-
font-size: 1rem;
|
| 123 |
-
color: var(--text-primary);
|
| 124 |
-
cursor: pointer;
|
| 125 |
-
appearance: none;
|
| 126 |
-
outline: none;
|
| 127 |
-
}
|
| 128 |
-
.resolution-icon {
|
| 129 |
-
position: absolute;
|
| 130 |
-
left: 15px;
|
| 131 |
-
top: 50%;
|
| 132 |
-
transform: translateY(-50%);
|
| 133 |
-
pointer-events: none;
|
| 134 |
-
color: var(--text-tertiary);
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
/* === اصلاح دکمه اصلی برای جلوگیری از بزرگ شدن در موبایل === */
|
| 138 |
-
#generateButton {
|
| 139 |
-
display: flex;
|
| 140 |
-
align-items: center;
|
| 141 |
-
justify-content: center;
|
| 142 |
-
gap: 0.75rem;
|
| 143 |
-
width: 100%;
|
| 144 |
-
padding: 15px 20px; /* پدینگ ثابت و کمتر */
|
| 145 |
-
font-size: 1.2rem;
|
| 146 |
-
font-weight: 700;
|
| 147 |
-
background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
|
| 148 |
-
color: #fff;
|
| 149 |
-
border: none;
|
| 150 |
-
border-radius: var(--radius-btn);
|
| 151 |
-
cursor: pointer;
|
| 152 |
-
transition: all 0.3s ease;
|
| 153 |
-
box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px rgba(15, 212, 168, 0.25);
|
| 154 |
-
margin-top: 2.5rem;
|
| 155 |
-
height: auto; /* جلوگیری از کش آمدن ارتفاع */
|
| 156 |
-
}
|
| 157 |
-
/* کنترل دقیق سایز آیکون ستاره */
|
| 158 |
-
#generateButton svg {
|
| 159 |
-
width: 24px !important;
|
| 160 |
-
height: 24px !important;
|
| 161 |
-
min-width: 24px;
|
| 162 |
-
min-height: 24px;
|
| 163 |
-
max-width: 24px;
|
| 164 |
-
max-height: 24px;
|
| 165 |
-
margin-left: 4px;
|
| 166 |
-
filter: drop-shadow(0 0 5px rgba(255,255,255,0.5));
|
| 167 |
-
flex-shrink: 0; /* جلوگیری از له شدن آیکون */
|
| 168 |
-
}
|
| 169 |
-
#generateButton:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px rgba(15, 212, 168, 0.3); }
|
| 170 |
-
#generateButton:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
|
| 171 |
-
|
| 172 |
-
#result-container { min-height: 350px; position: relative; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
| 173 |
-
#result-container.active { border-style: solid; border-color: var(--panel-border); }
|
| 174 |
-
#statusSection, #outputSection, #criticalErrorSection { display: none; width: 100%; }
|
| 175 |
-
#statusSection.active, #outputSection.active, #criticalErrorSection.active { display: block; animation: fadeIn 0.5s; }
|
| 176 |
-
#statusMessages { max-height: 150px; overflow-y: auto; width: 100%; padding: 0.5rem; margin-bottom: 1rem; }
|
| 177 |
-
.status-message { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 1rem; margin-bottom: 0.5rem; border-radius: var(--radius-input); font-size: 0.9rem; background: var(--panel-bg); border: 1px solid var(--panel-border); color: var(--text-secondary); }
|
| 178 |
-
.status-message.success { border-left: 4px solid var(--success-color); color: var(--success-color); }
|
| 179 |
-
.status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); }
|
| 180 |
-
.status-icon { width: 18px; height: 18px; }
|
| 181 |
-
#aiLoader { align-items: center; justify-content: center; }
|
| 182 |
-
.generator-container { position: relative; width: 400px; max-width: 100%; height: 300px; border: 2px solid #38bdf8; border-radius: 20px; overflow: hidden; box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); animation: pulse-loader 5s infinite cubic-bezier(0.4, 0, 0.6, 1); background-color: #161b22; color: #f0f6fc; }
|
| 183 |
-
.noise-layer { background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="none"/><filter id="noise"><feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="4" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23noise)" opacity="0.6"/></svg>') repeat; opacity: 1; animation: fade-noise 7s infinite ease-in-out; }
|
| 184 |
-
.text-overlay { position: absolute; top: 45%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); font-family: var(--app-font); width: 90%; text-align: center;}
|
| 185 |
-
.progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, var(--accent-secondary), var(--accent-primary), #facc15); }
|
| 186 |
-
.progress-bar.animate-progress { animation: fill-progress 60s linear forwards; }
|
| 187 |
-
|
| 188 |
-
#outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); }
|
| 189 |
-
.video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
|
| 190 |
-
.video-button { padding: 0.7rem 1.5rem; border-radius: var(--radius-btn); border: none; cursor: pointer; font-family: var(--app-font); font-weight: 600; font-size: 1rem; transition: var(--transition-smooth); display: flex; align-items: center; gap: 0.5rem; }
|
| 191 |
-
.video-button.primary { background-color: var(--accent-primary); color: white; }
|
| 192 |
-
.video-button.primary:hover { background-color: var(--accent-primary-hover); transform: translateY(-2px); }
|
| 193 |
-
.video-button:not(.primary) { background-color: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); }
|
| 194 |
-
.video-button:not(.primary):hover { background-color: var(--panel-border); color: var(--text-primary); }
|
| 195 |
-
.critical-error-content { color: var(--danger-color); font-weight: 500; margin-bottom: 1.5rem; line-height: 1.6; text-align: center; }
|
| 196 |
-
|
| 197 |
-
@media (max-width: 768px) {
|
| 198 |
-
main { padding: 1.5rem; } h1 { font-size: 2.2rem; }
|
| 199 |
-
.generator-container { height: 250px; }
|
| 200 |
-
.text-overlay { font-size: 18px; }
|
| 201 |
-
/* تنظیم دکمه در موبایل */
|
| 202 |
-
#generateButton { font-size: 1.1rem; padding: 12px 15px; }
|
| 203 |
-
}
|
| 204 |
-
</style>
|
| 205 |
-
</head>
|
| 206 |
-
<body>
|
| 207 |
-
<div class="container">
|
| 208 |
-
<header>
|
| 209 |
-
<canvas id="neural-network-canvas"></canvas>
|
| 210 |
-
<div class="header-content">
|
| 211 |
-
<div class="ai-synapse-container">
|
| 212 |
-
<div class="synapse-node audio-node"><div class="node-glow"></div><div class="node-icon"><div class="waveform-bar"></div><div class="waveform-bar"></div><div class="waveform-bar"></div><div class="waveform-bar"></div></div></div>
|
| 213 |
-
<div class="synapse-path-container">
|
| 214 |
-
<svg class="synapse-path-svg" viewBox="0 0 140 60">
|
| 215 |
-
<defs><linearGradient id="synapse-gradient" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="var(--accent-secondary)" stop-opacity="0.8"/><stop offset="100%" stop-color="var(--accent-primary)" stop-opacity="0.8"/></linearGradient></defs>
|
| 216 |
-
<path class="synapse-path" d="M5,30 C40,0 100,60 135,30"></path>
|
| 217 |
-
</svg>
|
| 218 |
-
<div class="synapse-particle"></div><div class="synapse-particle"></div><div class="synapse-particle"></div><div class="synapse-particle"></div>
|
| 219 |
-
</div>
|
| 220 |
-
<div class="synapse-node image-node"><div class="node-glow"></div><div class="node-icon"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 4.5C7 4.5 3 9 3 12C3 15 7 19.5 12 19.5C17 19.5 21 15 21 12C21 9 17 4.5 12 4.5Z" stroke-opacity="0.5"/><circle cx="12" cy="12" r="2.5" /></svg></div></div>
|
| 221 |
-
</div>
|
| 222 |
-
<h1>Wan2.2-S2V Video Generator</h1>
|
| 223 |
-
<p class="subtitle">تبدیل تصویر و صدا به ویدیو با قدرت هوش مصنوعی</p>
|
| 224 |
-
<p class="instruction">یک عکس چهره و یک فایل صوتی انتخاب کنید تا ویدیوی خود را بسازید.</p>
|
| 225 |
-
</div>
|
| 226 |
-
</header>
|
| 227 |
-
<main>
|
| 228 |
-
<div class="form-group">
|
| 229 |
-
<div class="form-label">
|
| 230 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="10" r="3"></circle><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"></path></svg>
|
| 231 |
-
۱. عکس ورودی را انتخاب کنید
|
| 232 |
-
</div>
|
| 233 |
-
<label id="image-drop-zone" for="imageInput">
|
| 234 |
-
<div class="upload-content">
|
| 235 |
-
<div class="upload-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
|
| 236 |
-
<p>فایل عکس را اینجا بکشید یا برای انتخاب کلیک کنید</p>
|
| 237 |
-
</div>
|
| 238 |
-
<img id="imagePreview" src="" alt="Preview">
|
| 239 |
-
</label>
|
| 240 |
-
<input type="file" id="imageInput" accept="image/jpeg, image/png, image/webp" hidden>
|
| 241 |
-
</div>
|
| 242 |
-
|
| 243 |
-
<div class="form-group">
|
| 244 |
-
<label for="audioInput" class="form-label">
|
| 245 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" x2="12" y1="19" y2="22"></line></svg>
|
| 246 |
-
۲. فایل صوتی را انتخاب کنید (حداکثر ۳۰ ثانیه)
|
| 247 |
-
</label>
|
| 248 |
-
<label for="audioInput" class="audio-input-container">
|
| 249 |
-
<div id="audio-prompt">
|
| 250 |
-
<span>برای انتخاب فایل صوتی کلیک کنید</span>
|
| 251 |
-
</div>
|
| 252 |
-
<div class="audio-player">
|
| 253 |
-
<button type="button" class="play-pause-btn"><svg class="icon-play" viewBox="0 0 24 24"><path fill="currentColor" d="M8 5v14l11-7z"></path></svg><svg class="icon-pause" viewBox="0 0 24 24"><path fill="currentColor" d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg></button>
|
| 254 |
-
<div class="waveform-container"><canvas id="waveform"></canvas><div class="audio-info"><span id="audioFileName"></span><span id="audioDuration"></span></div></div>
|
| 255 |
-
</div>
|
| 256 |
-
<button id="resetAudioBtn" type="button" title="حذف فایل صوتی">×</button>
|
| 257 |
-
</label>
|
| 258 |
-
<input type="file" id="audioInput" accept=".mp3,.wav" hidden>
|
| 259 |
-
</div>
|
| 260 |
-
|
| 261 |
-
<div class="form-group">
|
| 262 |
-
<div class="form-label">
|
| 263 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10z"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><path d="M9 9h.01"/><path d="M15 9h.01"/></svg>
|
| 264 |
-
۳. کیفیت ویدیو را انتخاب کنید
|
| 265 |
-
</div>
|
| 266 |
-
<div class="resolution-container">
|
| 267 |
-
<select id="resolutionInput" class="resolution-select">
|
| 268 |
-
<option value="480P" selected>480P (پیشفرض - سریعتر)</option>
|
| 269 |
-
<option value="720P">720P (کیفیت بالاتر)</option>
|
| 270 |
-
</select>
|
| 271 |
-
<svg class="resolution-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
| 272 |
-
</div>
|
| 273 |
-
</div>
|
| 274 |
-
|
| 275 |
-
<button id="generateButton">
|
| 276 |
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/></svg>
|
| 277 |
-
<span>ساخت ویدیو</span>
|
| 278 |
-
</button>
|
| 279 |
-
|
| 280 |
-
<div class="form-group" style="margin-top: 2.5rem;">
|
| 281 |
-
<div class="form-label">
|
| 282 |
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/><path d="M21 14l-1.5 3-3-1.5 3-3 1.5 3z"/><path d="M19.5 2.5l-3 1.5 1.5 3 3-1.5-1.5-3z"/></svg>
|
| 283 |
-
۴. نتیجه را ببینید
|
| 284 |
-
</div>
|
| 285 |
-
<div id="result-container">
|
| 286 |
-
<div id="statusSection">
|
| 287 |
-
<div id="statusMessages"></div>
|
| 288 |
-
<div id="aiLoader" style="display: none;"><div class="generator-container"><div class="noise-layer"></div><div class="text-overlay">در حال پردازش ویدیو...</div><div class="progress-bar"></div></div></div>
|
| 289 |
-
</div>
|
| 290 |
-
<div id="outputSection">
|
| 291 |
-
<video id="outputVideo" controls preload="metadata" playsinline></video>
|
| 292 |
-
<div class="video-controls">
|
| 293 |
-
<button id="btnDownload" class="video-button primary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>دانلود ویدیو</button>
|
| 294 |
-
<button id="btnRestart" class="video-button"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>ساخت ویدیو جدید</button>
|
| 295 |
-
</div>
|
| 296 |
-
</div>
|
| 297 |
-
<div id="criticalErrorSection"></div>
|
| 298 |
-
</div>
|
| 299 |
-
</div>
|
| 300 |
-
</main>
|
| 301 |
-
</div>
|
| 302 |
-
|
| 303 |
-
<script>
|
| 304 |
-
(function() {
|
| 305 |
-
const imageInput = document.getElementById('imageInput'), audioInput = document.getElementById('audioInput'), imagePreview = document.getElementById('imagePreview'), imageDropZone = document.getElementById('image-drop-zone'), audioInputContainer = document.querySelector('.audio-input-container'), resetAudioBtn = document.getElementById('resetAudioBtn'), generateButton = document.getElementById('generateButton'), resultContainer = document.getElementById('result-container'), statusSection = document.getElementById('statusSection'), outputSection = document.getElementById('outputSection'), criticalErrorSection = document.getElementById('criticalErrorSection'), statusMessagesDiv = document.getElementById('statusMessages'), aiLoader = document.getElementById('aiLoader'), loaderTextOverlay = document.querySelector('#aiLoader .text-overlay'), outputVideo = document.getElementById('outputVideo'), btnRestart = document.getElementById('btnRestart'), btnDownload = document.getElementById('btnDownload'), loaderProgressBar = document.querySelector('#aiLoader .progress-bar'), playPauseBtn = document.querySelector('.play-pause-btn'), iconPlay = document.querySelector('.icon-play'), iconPause = document.querySelector('.icon-pause'), waveformCanvas = document.getElementById('waveform'), audioFileNameEl = document.getElementById('audioFileName'), audioDurationEl = document.getElementById('audioDuration'), waveformCtx = waveformCanvas.getContext('2d'), resolutionInput = document.getElementById('resolutionInput');
|
| 306 |
-
|
| 307 |
-
const BASE_API = 'https://wan-ai-wan2-2-s2v.ms.show/gradio_api';
|
| 308 |
-
const TRANSPARENT_PIXEL_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
| 309 |
-
|
| 310 |
-
let lastAttemptPayload = null, audioContext, audioBuffer, audioSource, analyser, animationFrameId, isPlaying = false;
|
| 311 |
-
|
| 312 |
-
function showCriticalError(message) {
|
| 313 |
-
criticalErrorSection.innerHTML = `<div class="critical-error-content"><p>${message}</p></div><div class="video-controls"><button id="errorGoBackBtn" class="video-button">بازگشت</button><button id="errorRetryBtn" class="video-button primary">تلاش مجدد</button></div>`;
|
| 314 |
-
document.getElementById('errorGoBackBtn').addEventListener('click', hideCriticalError);
|
| 315 |
-
document.getElementById('errorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 316 |
-
resultContainer.classList.add('active');
|
| 317 |
-
criticalErrorSection.classList.add('active');
|
| 318 |
-
statusSection.classList.remove('active');
|
| 319 |
-
outputSection.classList.remove('active');
|
| 320 |
-
aiLoader.style.display = 'none';
|
| 321 |
-
generateButton.disabled = false;
|
| 322 |
-
generateButton.querySelector('span').innerText = 'دوباره تلاش کنید';
|
| 323 |
-
if (loaderProgressBar) loaderProgressBar.classList.remove('animate-progress');
|
| 324 |
-
}
|
| 325 |
-
|
| 326 |
-
function hideCriticalError() {
|
| 327 |
-
criticalErrorSection.classList.remove('active');
|
| 328 |
-
criticalErrorSection.innerHTML = '';
|
| 329 |
-
}
|
| 330 |
-
function setAiLoaderText(text) { if (loaderTextOverlay) loaderTextOverlay.textContent = text; }
|
| 331 |
-
function addStatusMessage(message, type = 'info') {
|
| 332 |
-
resultContainer.classList.add('active');
|
| 333 |
-
statusSection.classList.add('active');
|
| 334 |
-
const messageDiv = document.createElement('div');
|
| 335 |
-
messageDiv.className = `status-message ${type}`;
|
| 336 |
-
const iconSvg = type === 'success' ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>' : type === 'error' ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>' : '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
|
| 337 |
-
messageDiv.innerHTML = `${iconSvg}<span class="status-text">${message}</span>`;
|
| 338 |
-
statusMessagesDiv.insertBefore(messageDiv, statusMessagesDiv.firstChild);
|
| 339 |
-
}
|
| 340 |
-
function clearStatusMessages() {
|
| 341 |
-
statusMessagesDiv.innerHTML = '';
|
| 342 |
-
aiLoader.style.display = 'none';
|
| 343 |
-
if (loaderProgressBar) loaderProgressBar.classList.remove('animate-progress');
|
| 344 |
-
if (outputVideo.src && outputVideo.src.startsWith('blob:')) { URL.revokeObjectURL(outputVideo.src); }
|
| 345 |
-
outputVideo.src = '';
|
| 346 |
-
}
|
| 347 |
-
function resetAudioSelection() {
|
| 348 |
-
if (audioSource) { audioSource.stop(); }
|
| 349 |
-
cancelAnimationFrame(animationFrameId);
|
| 350 |
-
isPlaying = false; audioBuffer = null;
|
| 351 |
-
iconPlay.style.display = 'block'; iconPause.style.display = 'none';
|
| 352 |
-
audioInputContainer.classList.remove('has-file');
|
| 353 |
-
audioInput.value = '';
|
| 354 |
-
waveformCtx.clearRect(0, 0, waveformCanvas.width, waveformCanvas.height);
|
| 355 |
-
audioInputContainer.setAttribute('for', 'audioInput');
|
| 356 |
-
}
|
| 357 |
-
function initializeForm() {
|
| 358 |
-
hideCriticalError();
|
| 359 |
-
resultContainer.classList.remove('active');
|
| 360 |
-
statusSection.classList.remove('active'); outputSection.classList.remove('active');
|
| 361 |
-
clearStatusMessages();
|
| 362 |
-
lastAttemptPayload = null;
|
| 363 |
-
imageInput.value = '';
|
| 364 |
-
imagePreview.src = TRANSPARENT_PIXEL_SRC;
|
| 365 |
-
imageDropZone.classList.remove('has-file');
|
| 366 |
-
resetAudioSelection();
|
| 367 |
-
generateButton.disabled = false;
|
| 368 |
-
generateButton.querySelector('span').innerText = 'ساخت ویدیو';
|
| 369 |
-
resolutionInput.value = "480P";
|
| 370 |
-
}
|
| 371 |
-
async function setupAudio(file) {
|
| 372 |
-
if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); }
|
| 373 |
-
analyser = audioContext.createAnalyser();
|
| 374 |
-
analyser.fftSize = 2048;
|
| 375 |
-
const arrayBuffer = await file.arrayBuffer();
|
| 376 |
-
audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
| 377 |
-
audioFileNameEl.textContent = file.name;
|
| 378 |
-
audioDurationEl.textContent = `${audioBuffer.duration.toFixed(2)}s`;
|
| 379 |
-
audioInputContainer.classList.add('has-file');
|
| 380 |
-
audioInputContainer.removeAttribute('for');
|
| 381 |
-
visualizeAudio();
|
| 382 |
-
}
|
| 383 |
-
function playAudio() {
|
| 384 |
-
if (!audioBuffer) return;
|
| 385 |
-
audioSource = audioContext.createBufferSource();
|
| 386 |
-
audioSource.buffer = audioBuffer;
|
| 387 |
-
audioSource.connect(analyser);
|
| 388 |
-
analyser.connect(audioContext.destination);
|
| 389 |
-
audioSource.start(0);
|
| 390 |
-
isPlaying = true;
|
| 391 |
-
iconPlay.style.display = 'none'; iconPause.style.display = 'block';
|
| 392 |
-
audioSource.onended = () => { isPlaying = false; iconPlay.style.display = 'block'; iconPause.style.display = 'none'; }
|
| 393 |
-
}
|
| 394 |
-
function stopAudio() {
|
| 395 |
-
if (audioSource) { audioSource.stop(); }
|
| 396 |
-
isPlaying = false;
|
| 397 |
-
iconPlay.style.display = 'block'; iconPause.style.display = 'none';
|
| 398 |
-
}
|
| 399 |
-
function visualizeAudio() {
|
| 400 |
-
const bufferLength = analyser.frequencyBinCount, dataArray = new Uint8Array(bufferLength), waveformCtx = waveformCanvas.getContext('2d');
|
| 401 |
-
function draw() {
|
| 402 |
-
animationFrameId = requestAnimationFrame(draw);
|
| 403 |
-
analyser.getByteTimeDomainData(dataArray);
|
| 404 |
-
waveformCtx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--app-bg').trim();
|
| 405 |
-
waveformCtx.fillRect(0, 0, waveformCanvas.width, waveformCanvas.height);
|
| 406 |
-
waveformCtx.lineWidth = 2;
|
| 407 |
-
waveformCtx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--accent-primary').trim();
|
| 408 |
-
waveformCtx.beginPath();
|
| 409 |
-
const sliceWidth = waveformCanvas.width * 1.0 / bufferLength;
|
| 410 |
-
let x = 0;
|
| 411 |
-
for (let i = 0; i < bufferLength; i++) { const v = dataArray[i] / 128.0, y = v * waveformCanvas.height / 2; if (i === 0) { waveformCtx.moveTo(x, y); } else { waveformCtx.lineTo(x, y); } x += sliceWidth; }
|
| 412 |
-
waveformCtx.lineTo(waveformCanvas.width, waveformCanvas.height / 2);
|
| 413 |
-
waveformCtx.stroke();
|
| 414 |
-
}
|
| 415 |
-
draw();
|
| 416 |
-
}
|
| 417 |
-
|
| 418 |
-
async function uploadFile(file) {
|
| 419 |
-
const formData = new FormData();
|
| 420 |
-
formData.append('files', file);
|
| 421 |
-
const response = await fetch(`${BASE_API}/upload`, { method: 'POST', body: formData });
|
| 422 |
-
if (!response.ok) throw new Error(`خطا در آپلود فایل: ${file.name}`);
|
| 423 |
-
const json = await response.json();
|
| 424 |
-
return json[0];
|
| 425 |
-
}
|
| 426 |
-
|
| 427 |
-
async function startGenerationProcess(imageFile, audioFile) {
|
| 428 |
-
clearStatusMessages(); hideCriticalError();
|
| 429 |
-
outputSection.classList.remove('active'); criticalErrorSection.classList.remove('active');
|
| 430 |
-
generateButton.disabled = true; generateButton.querySelector('span').innerText = 'در حال پردازش...';
|
| 431 |
-
resultContainer.classList.add('active'); statusSection.classList.add('active');
|
| 432 |
-
aiLoader.style.display = 'flex';
|
| 433 |
-
if (loaderProgressBar) loaderProgressBar.classList.add('animate-progress');
|
| 434 |
-
|
| 435 |
-
lastAttemptPayload = { imageFile, audioFile };
|
| 436 |
-
|
| 437 |
-
try {
|
| 438 |
-
const resolution = resolutionInput.value;
|
| 439 |
-
|
| 440 |
-
addStatusMessage('در حال آپلود عکس...'); setAiLoaderText('آپلود عکس...');
|
| 441 |
-
const imgPath = await uploadFile(imageFile);
|
| 442 |
-
|
| 443 |
-
addStatusMessage('در حال آپلود فایل صوتی...'); setAiLoaderText('آپلود صدا...');
|
| 444 |
-
const audPath = await uploadFile(audioFile);
|
| 445 |
-
|
| 446 |
-
addStatusMessage('ارسال به هوش مصنوعی...'); setAiLoaderText('در حال پردازش (صبر کنید)...');
|
| 447 |
-
|
| 448 |
-
const payload = {
|
| 449 |
-
"data": [
|
| 450 |
-
{"path": imgPath, "meta": {"_type": "gradio.FileData"}},
|
| 451 |
-
{"path": audPath, "meta": {"_type": "gradio.FileData"}},
|
| 452 |
-
resolution
|
| 453 |
-
]
|
| 454 |
-
};
|
| 455 |
-
|
| 456 |
-
const callRes = await fetch(`${BASE_API}/call/predict`, {
|
| 457 |
-
method: 'POST',
|
| 458 |
-
headers: { "Content-Type": "application/json" },
|
| 459 |
-
body: JSON.stringify(payload)
|
| 460 |
-
});
|
| 461 |
-
|
| 462 |
-
if (!callRes.ok) throw new Error(`خطا در اتصال به سرور: ${callRes.status}`);
|
| 463 |
-
const eventId = (await callRes.json()).event_id;
|
| 464 |
-
|
| 465 |
-
addStatusMessage('ویدیو در صف ساخت قرار گرفت...');
|
| 466 |
-
|
| 467 |
-
const streamRes = await fetch(`${BASE_API}/call/predict/${eventId}`, {
|
| 468 |
-
headers: { "Accept": "text/event-stream" }
|
| 469 |
-
});
|
| 470 |
-
|
| 471 |
-
const reader = streamRes.body.getReader();
|
| 472 |
-
const decoder = new TextDecoder();
|
| 473 |
-
let buffer = "";
|
| 474 |
-
|
| 475 |
-
while (true) {
|
| 476 |
-
const { value, done } = await reader.read();
|
| 477 |
-
if (done) break;
|
| 478 |
-
|
| 479 |
-
buffer += decoder.decode(value, { stream: true });
|
| 480 |
-
const lines = buffer.split('\n');
|
| 481 |
-
buffer = lines.pop();
|
| 482 |
-
|
| 483 |
-
for (const line of lines) {
|
| 484 |
-
if (line.startsWith('data: ')) {
|
| 485 |
-
try {
|
| 486 |
-
const dataObj = JSON.parse(line.slice(6));
|
| 487 |
-
|
| 488 |
-
if (Array.isArray(dataObj) && dataObj.length > 0) {
|
| 489 |
-
const resultItem = dataObj[0];
|
| 490 |
-
let videoUrl = null;
|
| 491 |
-
|
| 492 |
-
if (resultItem?.video?.url) {
|
| 493 |
-
videoUrl = resultItem.video.url;
|
| 494 |
-
} else if (resultItem?.url) {
|
| 495 |
-
videoUrl = resultItem.url;
|
| 496 |
-
} else if (resultItem?.name) {
|
| 497 |
-
videoUrl = `${BASE_API.replace('/gradio_api', '')}/file=${resultItem.name}`;
|
| 498 |
-
} else if (typeof resultItem === 'string' && (resultItem.endsWith('.mp4') || resultItem.includes('/file='))) {
|
| 499 |
-
videoUrl = resultItem;
|
| 500 |
-
}
|
| 501 |
-
|
| 502 |
-
if (videoUrl) {
|
| 503 |
-
if (videoUrl.startsWith('/')) {
|
| 504 |
-
videoUrl = "https://wan-ai-wan2-2-s2v.ms.show" + videoUrl;
|
| 505 |
-
}
|
| 506 |
-
|
| 507 |
-
addStatusMessage('ویدیو با موفقیت ساخته شد!', 'success');
|
| 508 |
-
aiLoader.style.display = 'none';
|
| 509 |
-
setTimeout(() => {
|
| 510 |
-
statusSection.classList.remove('active');
|
| 511 |
-
outputSection.classList.add('active');
|
| 512 |
-
outputVideo.src = videoUrl;
|
| 513 |
-
outputVideo.load();
|
| 514 |
-
}, 500);
|
| 515 |
-
|
| 516 |
-
generateButton.disabled = false;
|
| 517 |
-
generateButton.querySelector('span').innerText = 'ساخت ویدیو جدید';
|
| 518 |
-
return;
|
| 519 |
-
}
|
| 520 |
-
}
|
| 521 |
-
} catch (e) { /* ignore incomplete json */ }
|
| 522 |
-
}
|
| 523 |
-
}
|
| 524 |
-
}
|
| 525 |
-
|
| 526 |
-
} catch (error) {
|
| 527 |
-
showCriticalError(`خطا: ${error.message}`);
|
| 528 |
-
}
|
| 529 |
-
}
|
| 530 |
-
|
| 531 |
-
async function retryLastAttempt() {
|
| 532 |
-
if (lastAttemptPayload && lastAttemptPayload.imageFile && lastAttemptPayload.audioFile) { await startGenerationProcess(lastAttemptPayload.imageFile, lastAttemptPayload.audioFile); } else { showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد."); }
|
| 533 |
-
}
|
| 534 |
-
function downloadVideo() {
|
| 535 |
-
if (!outputVideo.src) { alert("ویدیویی برای دانلود وجود ندارد."); return; }
|
| 536 |
-
const a = document.createElement('a');
|
| 537 |
-
a.href = outputVideo.src;
|
| 538 |
-
a.download = "generated_video.mp4";
|
| 539 |
-
document.body.appendChild(a);
|
| 540 |
-
a.click();
|
| 541 |
-
document.body.removeChild(a);
|
| 542 |
-
}
|
| 543 |
-
|
| 544 |
-
document.addEventListener('DOMContentLoaded', initializeForm);
|
| 545 |
-
btnRestart.addEventListener('click', initializeForm);
|
| 546 |
-
btnDownload.addEventListener('click', downloadVideo);
|
| 547 |
-
generateButton.addEventListener('click', () => { const imageFile = imageInput.files[0], audioFile = audioInput.files[0]; if (!imageFile || !audioFile) { addStatusMessage('لطفاً هم فایل عکس و هم فایل صوتی را انتخاب کنید.', 'error'); return; } startGenerationProcess(imageFile, audioFile); });
|
| 548 |
-
|
| 549 |
-
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => imageDropZone.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }));
|
| 550 |
-
['dragenter', 'dragover'].forEach(eventName => imageDropZone.addEventListener(eventName, () => { if (!imageDropZone.classList.contains('has-file')) imageDropZone.classList.add('drag-over'); }));
|
| 551 |
-
['dragleave', 'drop'].forEach(eventName => imageDropZone.addEventListener(eventName, () => imageDropZone.classList.remove('drag-over')));
|
| 552 |
-
imageDropZone.addEventListener('drop', e => { if (!imageDropZone.classList.contains('has-file') && e.dataTransfer.files.length) { imageInput.files = e.dataTransfer.files; imageInput.dispatchEvent(new Event('change', { bubbles: true })); } });
|
| 553 |
-
imageInput.addEventListener('change', (e) => { const file = e.target.files[0]; hideCriticalError(); if (file) { const reader = new FileReader(); reader.onload = (e) => { imagePreview.src = e.target.result; imageDropZone.classList.add('has-file'); }; reader.readAsDataURL(file); } else { imagePreview.src = TRANSPARENT_PIXEL_SRC; imageDropZone.classList.remove('has-file'); } });
|
| 554 |
-
audioInput.addEventListener('change', (e) => { const file = e.target.files[0]; hideCriticalError(); if (file) { setupAudio(file); } else { resetAudioSelection(); } });
|
| 555 |
-
resetAudioBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); resetAudioSelection(); });
|
| 556 |
-
playPauseBtn.addEventListener('click', () => { if (isPlaying) { stopAudio(); } else { playAudio(); } });
|
| 557 |
-
})();
|
| 558 |
-
</script>
|
| 559 |
-
|
| 560 |
-
<script>
|
| 561 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 562 |
-
const canvas = document.getElementById('neural-network-canvas'); if (!canvas) return;
|
| 563 |
-
const header = canvas.parentElement, ctx = canvas.getContext('2d');
|
| 564 |
-
let particles = [];
|
| 565 |
-
const particleCount = 20, maxDistance = 100, computedStyles = getComputedStyle(document.documentElement), particleColor = computedStyles.getPropertyValue('--accent-primary').trim(), lineColor = computedStyles.getPropertyValue('--text-tertiary').trim();
|
| 566 |
-
function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
|
| 567 |
-
class Particle { constructor() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 0.3; this.vy = (Math.random() - 0.5) * 0.3; this.radius = 1.2; } update() { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = particleColor; ctx.fill(); } }
|
| 568 |
-
function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
|
| 569 |
-
function connectParticles() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x, dy = particles[i].y - particles[j].y, distance = Math.sqrt(dx * dx + dy * dy); if (distance < maxDistance) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = lineColor; ctx.lineWidth = 0.2; ctx.globalAlpha = 1 - distance / maxDistance; ctx.stroke(); } } } ctx.globalAlpha = 1; }
|
| 570 |
-
function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }
|
| 571 |
-
window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate();
|
| 572 |
-
});
|
| 573 |
-
</script>
|
| 574 |
-
|
| 575 |
-
</body>
|
| 576 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|