Ezmary commited on
Commit
18842d4
·
verified ·
1 Parent(s): a92c4f7

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +272 -259
index.html CHANGED
@@ -4,27 +4,25 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>مولد ویدیو با Hugging Face</title>
7
- <script src="https://cdn.jsdelivr.net/npm/@huggingface/inference@2.7.0/dist/huggingface-inference.min.js"></script>
8
  <style>
9
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;700&display=swap');
10
-
11
  :root {
12
- --primary-color: #3b82f6;
13
- --secondary-color: #1f2937;
14
- --background-color: #f9fafb;
15
- --text-color: #374151;
16
- --card-bg: #ffffff;
17
- --border-color: #e5e7eb;
18
- --success-color: #10b981;
19
- --error-color: #ef4444;
20
  }
21
 
 
 
22
  body {
23
  font-family: 'Vazirmatn', sans-serif;
24
- background-color: var(--background-color);
25
- color: var(--text-color);
26
  margin: 0;
27
- padding: 2rem;
28
  display: flex;
29
  justify-content: center;
30
  align-items: flex-start;
@@ -34,79 +32,88 @@
34
  .container {
35
  width: 100%;
36
  max-width: 800px;
37
- background: var(--card-bg);
38
  border-radius: 12px;
39
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
40
- padding: 2rem;
41
- box-sizing: border-box;
42
  }
43
 
44
  h1 {
45
- color: var(--secondary-color);
46
  text-align: center;
47
- margin-bottom: 2rem;
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
- .input-group {
51
- margin-bottom: 1.5rem;
52
  }
53
 
54
- .input-group label {
55
  display: block;
56
- margin-bottom: 0.5rem;
57
- font-weight: 500;
 
58
  }
59
 
60
- .input-group input[type="text"],
61
- .input-group input[type="password"],
62
- .input-group textarea {
63
  width: 100%;
64
- padding: 0.75rem;
65
- border: 1px solid var(--border-color);
 
66
  border-radius: 8px;
67
- font-family: inherit;
68
  font-size: 1rem;
69
  box-sizing: border-box;
70
- transition: border-color 0.2s, box-shadow 0.2s;
71
  }
72
-
73
- .input-group input:focus, .input-group textarea:focus {
 
 
74
  outline: none;
75
  border-color: var(--primary-color);
76
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
 
 
 
 
77
  }
78
 
79
  .tabs {
80
  display: flex;
81
- border-bottom: 1px solid var(--border-color);
82
- margin-bottom: 1.5rem;
83
  }
84
 
85
  .tab-button {
86
- padding: 1rem 1.5rem;
87
- border: none;
88
- background: none;
89
  cursor: pointer;
 
 
 
90
  font-size: 1rem;
91
- font-family: inherit;
92
- font-weight: 500;
93
- position: relative;
94
- color: #6b7280;
95
- transition: color 0.2s;
96
  }
97
 
98
  .tab-button.active {
99
  color: var(--primary-color);
100
- }
101
-
102
- .tab-button.active::after {
103
- content: '';
104
- position: absolute;
105
- bottom: -1px;
106
- left: 0;
107
- right: 0;
108
- height: 2px;
109
- background-color: var(--primary-color);
110
  }
111
 
112
  .tab-content {
@@ -117,292 +124,298 @@
117
  display: block;
118
  }
119
 
120
- .custom-file-upload {
121
- border: 1px solid var(--border-color);
122
- display: inline-block;
123
- padding: 0.75rem 1.25rem;
124
- cursor: pointer;
125
- border-radius: 8px;
126
- background-color: #f9fafb;
127
- text-align: center;
128
- width: 100%;
129
- box-sizing: border-box;
130
- }
131
- .custom-file-upload:hover {
132
- background-color: #f3f4f6;
133
- }
134
-
135
- #file-name {
136
- margin-top: 0.5rem;
137
- font-style: italic;
138
- color: #6b7280;
139
- }
140
-
141
- input[type="file"] {
142
- display: none;
143
- }
144
-
145
- .btn {
146
- display: block;
147
- width: 100%;
148
- padding: 0.85rem;
149
  background-color: var(--primary-color);
150
- color: white;
151
  border: none;
 
152
  border-radius: 8px;
153
- font-size: 1.1rem;
154
- font-weight: 700;
 
155
  cursor: pointer;
156
- transition: background-color 0.2s, transform 0.1s;
 
157
  }
158
 
159
- .btn:hover:not(:disabled) {
160
- background-color: #2563eb;
 
 
 
 
161
  }
162
 
163
- .btn:disabled {
164
- background-color: #9ca3af;
 
165
  cursor: not-allowed;
166
  }
167
-
168
- .output-container {
169
- margin-top: 2rem;
170
- padding-top: 2rem;
171
- border-top: 1px solid var(--border-color);
172
- text-align: center;
173
- }
174
-
175
- #result-video {
176
- width: 100%;
177
- max-width: 500px;
178
  border-radius: 8px;
179
- margin: 1rem auto;
180
- display: none;
181
- border: 1px solid var(--border-color);
 
182
  }
183
-
184
- #download-link {
185
- display: none;
186
- margin-top: 1rem;
187
- padding: 0.75rem 1.5rem;
188
- background-color: var(--success-color);
189
- color: white;
190
- text-decoration: none;
191
  border-radius: 8px;
192
- font-weight: 500;
193
- }
194
- #download-link:hover {
195
- background-color: #059669;
196
  }
197
 
 
 
 
 
198
  .loader {
199
- border: 5px solid #f3f3f3;
 
200
  border-radius: 50%;
201
- border-top: 5px solid var(--primary-color);
202
- width: 50px;
203
- height: 50px;
204
  animation: spin 1s linear infinite;
205
- margin: 1rem auto;
206
- display: none;
207
  }
208
 
209
  @keyframes spin {
210
  0% { transform: rotate(0deg); }
211
  100% { transform: rotate(360deg); }
212
  }
213
-
214
- #error-message {
 
 
 
 
 
 
215
  color: var(--error-color);
216
- margin-top: 1rem;
217
- font-weight: 500;
 
 
218
  display: none;
219
  }
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  </style>
222
  </head>
223
  <body>
224
 
225
  <div class="container">
226
- <h1>مولد ویدیو با AI</h1>
 
227
 
228
- <div class="input-group">
229
- <label for="hf-token">توکن Hugging Face شما</label>
230
- <input type="password" id="hf-token" placeholder="hf_...">
231
  </div>
232
 
233
  <div class="tabs">
234
- <button class="tab-button active" onclick="openTab(event, 'textToVideo')">ساخت ویدیو از متن</button>
235
- <button class="tab-button" onclick="openTab(event, 'imageToVideo')">ساخت ویدیو از تصویر</button>
236
  </div>
237
 
238
- <!-- Tab: Text to Video -->
239
  <div id="textToVideo" class="tab-content active">
240
- <div class="input-group">
241
- <label for="text-prompt">توصیف ویدیو (Prompt)</label>
242
- <textarea id="text-prompt" rows="3" placeholder="مثال: A young man walking on the street"></textarea>
 
 
 
 
243
  </div>
244
- <button id="generate-text-btn" class="btn">تولید ویدیو</button>
245
  </div>
246
 
247
- <!-- Tab: Image to Video -->
248
  <div id="imageToVideo" class="tab-content">
249
- <div class="input-group">
250
- <label for="image-file">تصویر ورودی را انتخاب کنید</label>
251
- <label for="image-file" class="custom-file-upload">انتخاب فایل</label>
252
- <input type="file" id="image-file" accept="image/png, image/jpeg">
253
- <div id="file-name">فایلی انتخاب نشده است</div>
254
  </div>
255
- <div class="input-group">
256
- <label for="image-prompt">دستور برای متحرک‌سازی تصویر (Prompt)</label>
257
- <input type="text" id="image-prompt" placeholder="مثال: The cat starts to dance">
 
 
258
  </div>
259
- <button id="generate-image-btn" class="btn">تولید ویدیو</button>
260
- </div>
261
-
262
- <!-- Output Area -->
263
- <div class="output-container">
264
- <div id="loader" class="loader"></div>
265
- <div id="error-message"></div>
266
- <video id="result-video" controls></video>
267
- <a id="download-link" href="#" download="generated_video.mp4">دانلود ویدیو</a>
268
  </div>
269
-
270
  </div>
271
 
272
- <script>
273
- const { HfInference } = HuggingFace;
 
274
 
275
  // DOM Elements
276
- const hfTokenInput = document.getElementById('hf-token');
277
- const loader = document.getElementById('loader');
278
- const errorMessage = document.getElementById('error-message');
279
- const resultVideo = document.getElementById('result-video');
280
- const downloadLink = document.getElementById('download-link');
281
 
282
- // Text to Video Elements
283
- const textPromptInput = document.getElementById('text-prompt');
284
- const generateTextBtn = document.getElementById('generate-text-btn');
 
 
 
 
 
 
 
 
 
 
 
285
 
286
- // Image to Video Elements
287
- const imageFileInput = document.getElementById('image-file');
288
- const fileNameDisplay = document.getElementById('file-name');
289
- const imagePromptInput = document.getElementById('image-prompt');
290
- const generateImageBtn = document.getElementById('generate-image-btn');
291
-
292
- // Tab switching logic
293
- function openTab(evt, tabName) {
294
- let i, tabcontent, tablinks;
295
- tabcontent = document.getElementsByClassName("tab-content");
296
- for (i = 0; i < tabcontent.length; i++) {
297
- tabcontent[i].style.display = "none";
298
- }
299
- tablinks = document.getElementsByClassName("tab-button");
300
- for (i = 0; i < tablinks.length; i++) {
301
- tablinks[i].className = tablinks[i].className.replace(" active", "");
302
- }
303
- document.getElementById(tabName).style.display = "block";
304
- evt.currentTarget.className += " active";
305
- }
306
 
307
- // Display selected file name
308
- imageFileInput.addEventListener('change', () => {
309
- if (imageFileInput.files.length > 0) {
310
- fileNameDisplay.textContent = imageFileInput.files[0].name;
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  } else {
312
- fileNameDisplay.textContent = 'فایلی انتخاب نشده است';
313
  }
314
  });
315
-
316
- function resetOutput() {
317
- loader.style.display = 'none';
318
- errorMessage.style.display = 'none';
319
- resultVideo.style.display = 'none';
320
- downloadLink.style.display = 'none';
321
- resultVideo.src = '';
322
- downloadLink.href = '#';
323
- }
324
-
325
- function handleApiCall(button, apiFunction) {
326
- button.disabled = true;
327
- resetOutput();
328
-
329
  const token = hfTokenInput.value.trim();
330
  if (!token) {
331
- errorMessage.textContent = 'لطفاً توکن Hugging Face خود را وارد کنید.';
332
- errorMessage.style.display = 'block';
333
- button.disabled = false;
334
- return;
335
  }
336
-
337
- loader.style.display = 'block';
338
-
339
- apiFunction(token)
340
- .then(videoBlob => {
341
- const videoUrl = URL.createObjectURL(videoBlob);
342
- resultVideo.src = videoUrl;
343
- downloadLink.href = videoUrl;
344
-
345
- resultVideo.style.display = 'block';
346
- downloadLink.style.display = 'inline-block';
347
- })
348
- .catch(err => {
349
- console.error(err);
350
- errorMessage.textContent = `خطایی رخ داد: ${err.message}`;
351
- errorMessage.style.display = 'block';
352
- })
353
- .finally(() => {
354
- loader.style.display = 'none';
355
- button.disabled = false;
356
- });
357
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
359
- // --- Event Listeners ---
360
-
361
- generateTextBtn.addEventListener('click', () => {
362
- const prompt = textPromptInput.value.trim();
 
 
363
  if (!prompt) {
364
- errorMessage.textContent = 'لطفاً توصیف ویدیو را وارد کنید.';
365
- errorMessage.style.display = 'block';
366
  return;
367
  }
368
-
369
- handleApiCall(generateTextBtn, async (token) => {
370
- const hf = new HfInference(token);
371
- return await hf.textToVideo({
 
372
  provider: "fal-ai",
373
  model: "akhaliq/veo3.1-fast",
374
  inputs: prompt,
375
  });
376
- });
 
 
 
 
377
  });
378
-
379
- generateImageBtn.addEventListener('click', () => {
380
- const prompt = imagePromptInput.value.trim();
381
- const imageFile = imageFileInput.files[0];
382
-
383
- if (!imageFile) {
384
- errorMessage.textContent = 'لطفاً یک فایل تصویر انتخاب کنید.';
385
- errorMessage.style.display = 'block';
 
386
  return;
387
  }
 
 
388
  if (!prompt) {
389
- errorMessage.textContent = 'لطفاً دستور متحرک‌سازی را وارد کنید.';
390
- errorMessage.style.display = 'block';
391
  return;
392
  }
393
 
394
- handleApiCall(generateImageBtn, async (token) => {
395
- const hf = new HfInference(token);
396
- return await hf.imageToVideo({
 
397
  provider: "fal-ai",
398
  model: "akhaliq/veo3.1-fast-image-to-video",
399
- inputs: imageFile, // The library can handle the File object directly
400
  parameters: { prompt: prompt },
401
  });
402
- });
 
 
 
 
403
  });
404
 
405
  </script>
406
-
407
  </body>
408
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>مولد ویدیو با Hugging Face</title>
 
7
  <style>
 
 
8
  :root {
9
+ --bg-color: #121212;
10
+ --surface-color: #1e1e1e;
11
+ --primary-color: #03dac6;
12
+ --primary-variant-color: #3700b3;
13
+ --on-bg-color: #e0e0e0;
14
+ --on-surface-color: #ffffff;
15
+ --error-color: #cf6679;
 
16
  }
17
 
18
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap');
19
+
20
  body {
21
  font-family: 'Vazirmatn', sans-serif;
22
+ background-color: var(--bg-color);
23
+ color: var(--on-bg-color);
24
  margin: 0;
25
+ padding: 20px;
26
  display: flex;
27
  justify-content: center;
28
  align-items: flex-start;
 
32
  .container {
33
  width: 100%;
34
  max-width: 800px;
35
+ background-color: var(--surface-color);
36
  border-radius: 12px;
37
+ padding: 25px;
38
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
 
39
  }
40
 
41
  h1 {
 
42
  text-align: center;
43
+ color: var(--on-surface-color);
44
+ margin-bottom: 10px;
45
+ }
46
+
47
+ p.subtitle {
48
+ text-align: center;
49
+ margin-top: 0;
50
+ margin-bottom: 25px;
51
+ color: #aaa;
52
+ }
53
+
54
+ .token-section {
55
+ margin-bottom: 25px;
56
  }
57
 
58
+ .form-group {
59
+ margin-bottom: 20px;
60
  }
61
 
62
+ label {
63
  display: block;
64
+ margin-bottom: 8px;
65
+ font-weight: bold;
66
+ font-size: 1rem;
67
  }
68
 
69
+ input[type="text"],
70
+ input[type="password"],
71
+ textarea {
72
  width: 100%;
73
+ padding: 12px;
74
+ background-color: #2c2c2c;
75
+ border: 1px solid #444;
76
  border-radius: 8px;
77
+ color: var(--on-bg-color);
78
  font-size: 1rem;
79
  box-sizing: border-box;
80
+ transition: border-color 0.3s, box-shadow 0.3s;
81
  }
82
+
83
+ input[type="text"]:focus,
84
+ input[type="password"]:focus,
85
+ textarea:focus {
86
  outline: none;
87
  border-color: var(--primary-color);
88
+ box-shadow: 0 0 0 3px rgba(3, 218, 198, 0.2);
89
+ }
90
+
91
+ input::placeholder, textarea::placeholder {
92
+ color: #777;
93
  }
94
 
95
  .tabs {
96
  display: flex;
97
+ border-bottom: 1px solid #444;
98
+ margin-bottom: 20px;
99
  }
100
 
101
  .tab-button {
102
+ padding: 10px 20px;
 
 
103
  cursor: pointer;
104
+ border: none;
105
+ background-color: transparent;
106
+ color: #aaa;
107
  font-size: 1rem;
108
+ font-family: 'Vazirmatn', sans-serif;
109
+ font-weight: bold;
110
+ border-bottom: 3px solid transparent;
111
+ transition: color 0.3s, border-color 0.3s;
 
112
  }
113
 
114
  .tab-button.active {
115
  color: var(--primary-color);
116
+ border-bottom-color: var(--primary-color);
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
  .tab-content {
 
124
  display: block;
125
  }
126
 
127
+ button {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  background-color: var(--primary-color);
129
+ color: #000;
130
  border: none;
131
+ padding: 12px 20px;
132
  border-radius: 8px;
133
+ font-size: 1rem;
134
+ font-weight: bold;
135
+ font-family: 'Vazirmatn', sans-serif;
136
  cursor: pointer;
137
+ transition: background-color 0.3s, transform 0.1s;
138
+ width: 100%;
139
  }
140
 
141
+ button:hover:not(:disabled) {
142
+ background-color: #03c0b1;
143
+ }
144
+
145
+ button:active:not(:disabled) {
146
+ transform: scale(0.98);
147
  }
148
 
149
+ button:disabled {
150
+ background-color: #555;
151
+ color: #999;
152
  cursor: not-allowed;
153
  }
154
+
155
+ .result-container {
156
+ margin-top: 25px;
157
+ padding: 15px;
158
+ background-color: #2c2c2c;
 
 
 
 
 
 
159
  border-radius: 8px;
160
+ min-height: 50px;
161
+ display: flex;
162
+ justify-content: center;
163
+ align-items: center;
164
  }
165
+
166
+ video, img#imagePreview {
167
+ max-width: 100%;
 
 
 
 
 
168
  border-radius: 8px;
 
 
 
 
169
  }
170
 
171
+ #imagePreview {
172
+ max-height: 250px;
173
+ }
174
+
175
  .loader {
176
+ border: 4px solid #f3f3f3;
177
+ border-top: 4px solid var(--primary-color);
178
  border-radius: 50%;
179
+ width: 30px;
180
+ height: 30px;
 
181
  animation: spin 1s linear infinite;
 
 
182
  }
183
 
184
  @keyframes spin {
185
  0% { transform: rotate(0deg); }
186
  100% { transform: rotate(360deg); }
187
  }
188
+
189
+ .status-message {
190
+ margin-top: 15px;
191
+ text-align: center;
192
+ color: #ccc;
193
+ }
194
+
195
+ .error-message {
196
  color: var(--error-color);
197
+ font-weight: bold;
198
+ }
199
+
200
+ input[type="file"] {
201
  display: none;
202
  }
203
 
204
+ .file-label {
205
+ display: inline-block;
206
+ padding: 10px 15px;
207
+ background-color: #333;
208
+ border: 1px dashed #666;
209
+ border-radius: 8px;
210
+ cursor: pointer;
211
+ text-align: center;
212
+ width: 100%;
213
+ box-sizing: border-box;
214
+ transition: background-color 0.3s;
215
+ }
216
+ .file-label:hover {
217
+ background-color: #444;
218
+ }
219
+
220
  </style>
221
  </head>
222
  <body>
223
 
224
  <div class="container">
225
+ <h1>مولد ویدیو با Hugging Face</h1>
226
+ <p class="subtitle">ویدیوهای خلاقانه از متن یا تصویر بسازید</p>
227
 
228
+ <div class="token-section form-group">
229
+ <label for="hfToken">کلید توکن Hugging Face</label>
230
+ <input type="password" id="hfToken" placeholder="توکن خود را اینجا وارد کنید (مثال: hf_...)">
231
  </div>
232
 
233
  <div class="tabs">
234
+ <button class="tab-button active" onclick="openTab('textToVideo')">ساخت ویدیو از متن</button>
235
+ <button class="tab-button" onclick="openTab('imageToVideo')">ساخت ویدیو از تصویر</button>
236
  </div>
237
 
238
+ <!-- Tab Content: Text to Video -->
239
  <div id="textToVideo" class="tab-content active">
240
+ <div class="form-group">
241
+ <label for="textPrompt">توصیف ویدیو (Prompt)</label>
242
+ <textarea id="textPrompt" rows="4" placeholder="مثال: A young man walking on the street"></textarea>
243
+ </div>
244
+ <button id="generateTextBtn">ساخت ویدیو</button>
245
+ <div class="result-container" id="textResultContainer">
246
+ <span>نتیجه در اینجا نمایش داده می‌شود</span>
247
  </div>
248
+ <div class="status-message" id="textStatus"></div>
249
  </div>
250
 
251
+ <!-- Tab Content: Image to Video -->
252
  <div id="imageToVideo" class="tab-content">
253
+ <div class="form-group">
254
+ <label for="imageFile">انتخاب تصویر</label>
255
+ <input type="file" id="imageFile" accept="image/*">
256
+ <label for="imageFile" class="file-label">برای انتخاب تصویر کلیک کنید</label>
 
257
  </div>
258
+ <div class="form-group" id="imagePreviewContainer" style="display: none;">
259
+ <label>پیش‌نمایش تصویر</label>
260
+ <div class="result-container">
261
+ <img id="imagePreview" src="" alt="Image Preview">
262
+ </div>
263
  </div>
264
+ <div class="form-group">
265
+ <label for="imagePrompt">توصیف حرکت (Prompt)</label>
266
+ <input type="text" id="imagePrompt" placeholder="مثال: The cat starts to dance">
267
+ </div>
268
+ <button id="generateImageBtn">ساخت ویدیو</button>
269
+ <div class="result-container" id="imageResultContainer">
270
+ <span>نتیجه در اینجا نمایش داده می‌شود</span>
271
+ </div>
272
+ <div class="status-message" id="imageStatus"></div>
273
  </div>
 
274
  </div>
275
 
276
+ <script type="module">
277
+ // Import the HfInference class from the CDN
278
+ import { HfInference } from "https://cdn.jsdelivr.net/npm/@huggingface/inference@2.7.0/+esm";
279
 
280
  // DOM Elements
281
+ const hfTokenInput = document.getElementById('hfToken');
 
 
 
 
282
 
283
+ // Text-to-Video elements
284
+ const textPrompt = document.getElementById('textPrompt');
285
+ const generateTextBtn = document.getElementById('generateTextBtn');
286
+ const textResultContainer = document.getElementById('textResultContainer');
287
+ const textStatus = document.getElementById('textStatus');
288
+
289
+ // Image-to-Video elements
290
+ const imageFile = document.getElementById('imageFile');
291
+ const imagePrompt = document.getElementById('imagePrompt');
292
+ const generateImageBtn = document.getElementById('generateImageBtn');
293
+ const imageResultContainer = document.getElementById('imageResultContainer');
294
+ const imageStatus = document.getElementById('imageStatus');
295
+ const imagePreviewContainer = document.getElementById('imagePreviewContainer');
296
+ const imagePreview = document.getElementById('imagePreview');
297
 
298
+ let hf;
299
+
300
+ // --- Tab Management ---
301
+ window.openTab = function(tabName) {
302
+ const tabContents = document.querySelectorAll('.tab-content');
303
+ tabContents.forEach(content => content.classList.remove('active'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
+ const tabButtons = document.querySelectorAll('.tab-button');
306
+ tabButtons.forEach(button => button.classList.remove('active'));
307
+
308
+ document.getElementById(tabName).classList.add('active');
309
+ event.currentTarget.classList.add('active');
310
+ }
311
+
312
+ // --- Image Preview ---
313
+ imageFile.addEventListener('change', () => {
314
+ const file = imageFile.files[0];
315
+ if (file) {
316
+ const reader = new FileReader();
317
+ reader.onload = (e) => {
318
+ imagePreview.src = e.target.result;
319
+ imagePreviewContainer.style.display = 'block';
320
+ };
321
+ reader.readAsDataURL(file);
322
  } else {
323
+ imagePreviewContainer.style.display = 'none';
324
  }
325
  });
326
+
327
+ // --- Helper Functions ---
328
+ function getHfClient() {
 
 
 
 
 
 
 
 
 
 
 
329
  const token = hfTokenInput.value.trim();
330
  if (!token) {
331
+ alert('لطفاً کلید توکن Hugging Face خود را وارد کنید.');
332
+ return null;
 
 
333
  }
334
+ return new HfInference(token);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  }
336
+
337
+ function showLoading(container, statusEl, buttonEl) {
338
+ container.innerHTML = '<div class="loader"></div>';
339
+ statusEl.className = 'status-message';
340
+ statusEl.textContent = 'در حال ساخت ویدیو... این فرآیند ممکن است چند دقیقه طول بکشد. لطفاً صبور باشید.';
341
+ buttonEl.disabled = true;
342
+ }
343
+
344
+ function showResult(container, statusEl, buttonEl, videoBlob) {
345
+ const videoUrl = URL.createObjectURL(videoBlob);
346
+ container.innerHTML = `<video controls src="${videoUrl}"></video>`;
347
+ statusEl.textContent = 'ویدیو با موفقیت ساخته شد!';
348
+ buttonEl.disabled = false;
349
+ }
350
+
351
+ function showError(container, statusEl, buttonEl, error) {
352
+ container.innerHTML = '<span>خطا در ساخت ویدیو</span>';
353
+ statusEl.className = 'status-message error-message';
354
+ statusEl.textContent = `خطا: ${error.message}`;
355
+ buttonEl.disabled = false;
356
+ }
357
+
358
+ // --- Event Listeners for Generation ---
359
 
360
+ // Text-to-Video Generation
361
+ generateTextBtn.addEventListener('click', async () => {
362
+ const hf = getHfClient();
363
+ if (!hf) return;
364
+
365
+ const prompt = textPrompt.value.trim();
366
  if (!prompt) {
367
+ alert('لطفاً توصیف ویدیو را وارد کنید.');
 
368
  return;
369
  }
370
+
371
+ showLoading(textResultContainer, textStatus, generateTextBtn);
372
+
373
+ try {
374
+ const videoBlob = await hf.textToVideo({
375
  provider: "fal-ai",
376
  model: "akhaliq/veo3.1-fast",
377
  inputs: prompt,
378
  });
379
+ showResult(textResultContainer, textStatus, generateTextBtn, videoBlob);
380
+ } catch (error) {
381
+ console.error("Text-to-Video Error:", error);
382
+ showError(textResultContainer, textStatus, generateTextBtn, error);
383
+ }
384
  });
385
+
386
+ // Image-to-Video Generation
387
+ generateImageBtn.addEventListener('click', async () => {
388
+ const hf = getHfClient();
389
+ if (!hf) return;
390
+
391
+ const file = imageFile.files[0];
392
+ if (!file) {
393
+ alert('لطفاً یک تصویر انتخاب کنید.');
394
  return;
395
  }
396
+
397
+ const prompt = imagePrompt.value.trim();
398
  if (!prompt) {
399
+ alert('لطفاً توصیف حرکت را وارد کنید.');
 
400
  return;
401
  }
402
 
403
+ showLoading(imageResultContainer, imageStatus, generateImageBtn);
404
+
405
+ try {
406
+ const videoBlob = await hf.imageToVideo({
407
  provider: "fal-ai",
408
  model: "akhaliq/veo3.1-fast-image-to-video",
409
+ inputs: file, // The library directly accepts the File object from the input
410
  parameters: { prompt: prompt },
411
  });
412
+ showResult(imageResultContainer, imageStatus, generateImageBtn, videoBlob);
413
+ } catch (error) {
414
+ console.error("Image-to-Video Error:", error);
415
+ showError(imageResultContainer, imageStatus, generateImageBtn, error);
416
+ }
417
  });
418
 
419
  </script>
 
420
  </body>
421
  </html>