Ezmary commited on
Commit
77d8a7a
·
verified ·
1 Parent(s): c7a1b6d

Delete index.html

Browse files
Files changed (1) hide show
  1. 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="حذف فایل صوتی">&times;</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>