Athspi commited on
Commit
6477083
·
verified ·
1 Parent(s): 358d8c6

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +112 -171
templates/index.html CHANGED
@@ -16,6 +16,7 @@
16
  --gray: #6c757d;
17
  --success: #4cc9f0;
18
  --error: #f72585;
 
19
  --border-radius: 12px;
20
  --shadow: 0 10px 30px rgba(0,0,0,0.1);
21
  --transition: all 0.3s ease;
@@ -118,38 +119,25 @@
118
  font-weight: 600;
119
  margin-bottom: 0.8rem;
120
  color: var(--secondary);
121
- display: flex;
122
- align-items: center;
123
- gap: 0.5rem;
124
- }
125
-
126
- .option-title i {
127
- font-size: 1.1rem;
128
  }
129
 
130
- .radio-group, .checkbox-group {
131
- display: flex;
132
- flex-wrap: wrap;
133
- gap: 1rem;
 
 
 
134
  }
135
 
136
- .radio-option, .checkbox-option {
137
  display: flex;
138
  align-items: center;
139
- gap: 0.5rem;
140
- background: white;
141
- padding: 0.8rem 1.2rem;
142
- border-radius: 8px;
143
- box-shadow: 0 2px 5px rgba(0,0,0,0.05);
144
- transition: var(--transition);
145
- }
146
-
147
- .radio-option:hover, .checkbox-option:hover {
148
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
149
  }
150
 
151
- .radio-option input, .checkbox-option input {
152
- accent-color: var(--primary);
153
  }
154
 
155
  .btn {
@@ -182,32 +170,6 @@
182
  box-shadow: none;
183
  }
184
 
185
- .btn i {
186
- font-size: 1.2rem;
187
- }
188
-
189
- .alert {
190
- padding: 1rem;
191
- border-radius: var(--border-radius);
192
- margin-bottom: 1.5rem;
193
- font-weight: 500;
194
- display: flex;
195
- align-items: center;
196
- gap: 0.8rem;
197
- }
198
-
199
- .alert-success {
200
- background: rgba(76, 201, 240, 0.2);
201
- color: #0a9396;
202
- border: 1px solid rgba(76, 201, 240, 0.3);
203
- }
204
-
205
- .alert-error {
206
- background: rgba(247, 37, 133, 0.1);
207
- color: var(--error);
208
- border: 1px solid rgba(247, 37, 133, 0.2);
209
- }
210
-
211
  .progress-container {
212
  margin: 2rem 0;
213
  display: none;
@@ -239,14 +201,6 @@
239
  color: var(--gray);
240
  }
241
 
242
- .time-estimate {
243
- margin-top: 0.5rem;
244
- display: flex;
245
- justify-content: space-between;
246
- font-size: 0.85rem;
247
- color: var(--gray);
248
- }
249
-
250
  .result-container {
251
  display: none;
252
  margin-top: 2rem;
@@ -273,18 +227,31 @@
273
  border-radius: var(--border-radius);
274
  max-height: 300px;
275
  overflow-y: auto;
276
- font-family: monospace;
277
  white-space: pre-wrap;
278
- line-height: 1.6;
279
  }
280
 
281
- .duration-notice {
282
- margin-top: 1rem;
283
- padding: 0.8rem;
284
- background: #fff8e1;
285
- border-radius: 8px;
286
- border-left: 4px solid #ffc107;
287
- font-size: 0.9rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  }
289
 
290
  @media (max-width: 768px) {
@@ -303,11 +270,6 @@
303
  .content {
304
  padding: 1.5rem;
305
  }
306
-
307
- .radio-group, .checkbox-group {
308
- flex-direction: column;
309
- gap: 0.8rem;
310
- }
311
  }
312
  </style>
313
  </head>
@@ -315,7 +277,7 @@
315
  <div class="container">
316
  <header>
317
  <h1>AI Video Dubbing</h1>
318
- <p class="subtitle">Transform your videos with AI-powered Tamil dubbing</p>
319
  </header>
320
 
321
  <div class="content">
@@ -325,38 +287,64 @@
325
  <i class="fas fa-cloud-upload-alt"></i>
326
  </div>
327
  <h3>Drag & Drop Video File</h3>
328
- <p>or click to browse (MP4, MOV, WEBM, AVI)</p>
329
  <div id="file-info" class="file-info">No file selected</div>
330
  <input type="file" id="video-input" class="file-input" accept="video/*">
331
  </div>
332
 
333
  <div class="options">
334
  <div class="option-group">
335
- <div class="option-title">
336
- <i class="fas fa-microphone"></i>
337
- Voice Style
338
- </div>
339
- <div class="radio-group" id="voice-options">
340
- {% for voice, value in voices.items() %}
341
- <label class="radio-option">
342
- <input type="radio" name="voice" value="{{ value }}" {% if loop.first %}checked{% endif %}>
343
- {{ voice }}
344
- </label>
345
  {% endfor %}
346
- </div>
347
  </div>
348
 
349
  <div class="option-group">
350
- <div class="option-title">
351
- <i class="fas fa-adjust"></i>
352
- Tone Options
353
- </div>
354
- <div class="checkbox-group">
355
- <label class="checkbox-option">
356
- <input type="checkbox" name="tone" id="tone-option">
357
- Cheerful Tone
 
 
 
 
 
 
 
 
 
 
 
358
  </label>
 
 
 
 
 
 
 
 
 
359
  </div>
 
 
 
 
 
 
 
 
 
360
  </div>
361
  </div>
362
 
@@ -364,17 +352,12 @@
364
  <i class="fas fa-magic"></i>
365
  Generate Dubbed Video
366
  </button>
367
-
368
- <div id="duration-notice" class="duration-notice" style="display: none;">
369
- <i class="fas fa-clock"></i>
370
- Note: Processing time is approximately 1.5x the video duration
371
- </div>
372
  </div>
373
 
374
  <div class="progress-container" id="progress-container">
375
  <div class="progress-header">
376
- <span>Processing your video</span>
377
- <span id="eta">Estimating time...</span>
378
  </div>
379
  <div class="progress-bar">
380
  <div class="progress-fill" id="progress-fill"></div>
@@ -382,10 +365,6 @@
382
  <div class="progress-details" id="progress-message">
383
  Preparing to process your video...
384
  </div>
385
- <div class="time-estimate">
386
- <span id="elapsed-time">Elapsed: 0s</span>
387
- <span id="remaining-time">Remaining: Calculating...</span>
388
- </div>
389
  </div>
390
 
391
  <div class="result-container" id="result-container">
@@ -397,12 +376,10 @@
397
  <h2>Generated Script</h2>
398
  <div class="script-container" id="script-container"></div>
399
 
400
- <div class="btn-container" style="margin-top: 1.5rem;">
401
- <button id="new-video-btn" class="btn">
402
- <i class="fas fa-sync-alt"></i>
403
- Process Another Video
404
- </button>
405
- </div>
406
  </div>
407
  </div>
408
  </div>
@@ -417,21 +394,17 @@
417
  const progressContainer = document.getElementById('progress-container');
418
  const progressFill = document.getElementById('progress-fill');
419
  const progressMessage = document.getElementById('progress-message');
420
- const etaDisplay = document.getElementById('eta');
421
  const resultContainer = document.getElementById('result-container');
422
  const resultVideo = document.getElementById('result-video');
423
  const scriptContainer = document.getElementById('script-container');
424
- const elapsedTimeDisplay = document.getElementById('elapsed-time');
425
- const remainingTimeDisplay = document.getElementById('remaining-time');
426
- const durationNotice = document.getElementById('duration-notice');
427
  const newVideoBtn = document.getElementById('new-video-btn');
428
 
429
  // State
430
  let currentTaskId = null;
431
  let statusCheckInterval = null;
432
- let processingStartTime = 0;
433
- let videoDuration = 0;
434
- let elapsedTimer = null;
435
 
436
  // Event Listeners
437
  fileInput.addEventListener('change', handleFileSelect);
@@ -448,9 +421,8 @@
448
  uploadArea.style.borderColor = '#4361ee';
449
  uploadArea.style.backgroundColor = 'rgba(67, 97, 238, 0.05)';
450
  processBtn.disabled = false;
451
- durationNotice.style.display = 'block';
452
 
453
- // Hide any previous results
454
  resultContainer.style.display = 'none';
455
  }
456
  }
@@ -477,38 +449,36 @@
477
  fileInput.files = e.dataTransfer.files;
478
  fileInfo.textContent = e.dataTransfer.files[0].name;
479
  processBtn.disabled = false;
480
- durationNotice.style.display = 'block';
481
 
482
- // Hide any previous results
483
  resultContainer.style.display = 'none';
484
  }
485
  }
486
 
487
  function startProcessing() {
488
  if (!fileInput.files.length) {
489
- alert('Please select a video file first.');
490
  return;
491
  }
492
 
493
  // Get processing options
494
- const voice = document.querySelector('input[name="voice"]:checked').value;
495
- const cheerful = document.getElementById('tone-option').checked;
 
496
 
497
  // Prepare form data
498
  const formData = new FormData();
499
  formData.append('video', fileInput.files[0]);
500
- formData.append('voice', voice);
501
- formData.append('cheerful', cheerful);
 
502
 
503
  // Show progress UI
504
  uploadContainer.style.display = 'none';
505
  progressContainer.style.display = 'block';
506
  progressFill.style.width = '0%';
507
- progressMessage.textContent = 'Preparing to process your video...';
508
- processingStartTime = Date.now();
509
-
510
- // Start elapsed timer
511
- startElapsedTimer();
512
 
513
  // Start processing
514
  fetch('/upload', {
@@ -521,7 +491,6 @@
521
  throw new Error(data.error);
522
  }
523
  currentTaskId = data.task_id;
524
- videoDuration = data.video_duration || 0;
525
  startStatusChecking();
526
  })
527
  .catch(error => {
@@ -535,7 +504,7 @@
535
  clearInterval(statusCheckInterval);
536
  }
537
 
538
- // Check status every 3 seconds
539
  statusCheckInterval = setInterval(() => {
540
  fetch(`/status/${currentTaskId}`)
541
  .then(response => response.json())
@@ -543,66 +512,38 @@
543
  .catch(error => {
544
  console.error('Status check failed:', error);
545
  });
546
- }, 3000);
547
- }
548
-
549
- function startElapsedTimer() {
550
- if (elapsedTimer) {
551
- clearInterval(elapsedTimer);
552
- }
553
-
554
- elapsedTimer = setInterval(() => {
555
- const elapsedSeconds = Math.floor((Date.now() - processingStartTime) / 1000);
556
- elapsedTimeDisplay.textContent = `Elapsed: ${formatTime(elapsedSeconds)}`;
557
- }, 1000);
558
- }
559
-
560
- function formatTime(seconds) {
561
- const mins = Math.floor(seconds / 60);
562
- const secs = seconds % 60;
563
- return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
564
  }
565
 
566
  function updateStatus(status) {
567
  if (status.error) {
568
- showError(status.error);
569
- clearInterval(statusCheckInterval);
570
  return;
571
  }
572
 
573
  // Update progress
574
- progressFill.style.width = `${status.progress}%`;
 
 
575
  progressMessage.textContent = status.message;
576
-
577
- // Update ETA if available
578
- if (status.eta) {
579
- etaDisplay.textContent = `ETA: ${status.eta}`;
580
- remainingTimeDisplay.textContent = `Remaining: ${status.eta}`;
581
- }
582
 
583
  // Handle completion
584
  if (status.status === 'complete') {
585
  clearInterval(statusCheckInterval);
586
- clearInterval(elapsedTimer);
587
- progressMessage.textContent = 'Processing complete!';
588
 
589
  // Show results
590
  resultVideo.src = status.result_url;
591
  scriptContainer.textContent = status.script;
592
- progressContainer.style.display = 'none';
593
  resultContainer.style.display = 'block';
594
- } else if (status.status === 'error') {
595
- showError(status.message);
596
- clearInterval(statusCheckInterval);
597
- clearInterval(elapsedTimer);
598
  }
599
  }
600
 
601
  function showError(message) {
602
- progressContainer.style.display = 'none';
603
- uploadContainer.style.display = 'block';
604
  alert(`Error: ${message}`);
605
- clearInterval(elapsedTimer);
 
 
606
  }
607
 
608
  function resetForm() {
@@ -613,10 +554,10 @@
613
  scriptContainer.textContent = '';
614
  resultContainer.style.display = 'none';
615
  uploadContainer.style.display = 'block';
 
616
  processBtn.disabled = true;
617
  uploadArea.style.borderColor = '#4895ef';
618
  uploadArea.style.backgroundColor = 'transparent';
619
- durationNotice.style.display = 'none';
620
  }
621
  </script>
622
  </body>
 
16
  --gray: #6c757d;
17
  --success: #4cc9f0;
18
  --error: #f72585;
19
+ --warning: #ffc107;
20
  --border-radius: 12px;
21
  --shadow: 0 10px 30px rgba(0,0,0,0.1);
22
  --transition: all 0.3s ease;
 
119
  font-weight: 600;
120
  margin-bottom: 0.8rem;
121
  color: var(--secondary);
 
 
 
 
 
 
 
122
  }
123
 
124
+ select, .radio-group {
125
+ width: 100%;
126
+ padding: 0.8rem;
127
+ border-radius: 8px;
128
+ border: 1px solid #dee2e6;
129
+ margin-bottom: 1rem;
130
+ background: white;
131
  }
132
 
133
+ .radio-option {
134
  display: flex;
135
  align-items: center;
136
+ margin-bottom: 0.5rem;
 
 
 
 
 
 
 
 
 
137
  }
138
 
139
+ .radio-option input {
140
+ margin-right: 0.5rem;
141
  }
142
 
143
  .btn {
 
170
  box-shadow: none;
171
  }
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  .progress-container {
174
  margin: 2rem 0;
175
  display: none;
 
201
  color: var(--gray);
202
  }
203
 
 
 
 
 
 
 
 
 
204
  .result-container {
205
  display: none;
206
  margin-top: 2rem;
 
227
  border-radius: var(--border-radius);
228
  max-height: 300px;
229
  overflow-y: auto;
 
230
  white-space: pre-wrap;
 
231
  }
232
 
233
+ .notice {
234
+ padding: 1rem;
235
+ border-radius: var(--border-radius);
236
+ margin-bottom: 1.5rem;
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 0.8rem;
240
+ }
241
+
242
+ .notice-api {
243
+ background: rgba(67, 97, 238, 0.1);
244
+ border-left: 4px solid var(--primary);
245
+ }
246
+
247
+ .notice-gtts {
248
+ background: rgba(255, 193, 7, 0.1);
249
+ border-left: 4px solid var(--warning);
250
+ }
251
+
252
+ .notice-error {
253
+ background: rgba(247, 37, 133, 0.1);
254
+ border-left: 4px solid var(--error);
255
  }
256
 
257
  @media (max-width: 768px) {
 
270
  .content {
271
  padding: 1.5rem;
272
  }
 
 
 
 
 
273
  }
274
  </style>
275
  </head>
 
277
  <div class="container">
278
  <header>
279
  <h1>AI Video Dubbing</h1>
280
+ <p class="subtitle">Professional Video Dubbing with AI</p>
281
  </header>
282
 
283
  <div class="content">
 
287
  <i class="fas fa-cloud-upload-alt"></i>
288
  </div>
289
  <h3>Drag & Drop Video File</h3>
290
+ <p>Supported formats: MP4, MOV, WEBM, AVI</p>
291
  <div id="file-info" class="file-info">No file selected</div>
292
  <input type="file" id="video-input" class="file-input" accept="video/*">
293
  </div>
294
 
295
  <div class="options">
296
  <div class="option-group">
297
+ <div class="option-title">Target Language</div>
298
+ <select id="language-select">
299
+ {% for language in languages %}
300
+ <option value="{{ language }}"
301
+ {% if language == default_language %}selected{% endif %}>
302
+ {{ language }}
303
+ </option>
 
 
 
304
  {% endfor %}
305
+ </select>
306
  </div>
307
 
308
  <div class="option-group">
309
+ <div class="option-title">Voice Type</div>
310
+ <select id="voice-type">
311
+ {% for voice_type in voice_types %}
312
+ <option value="{{ voice_type }}">{{ voice_type }}</option>
313
+ {% endfor %}
314
+ </select>
315
+ </div>
316
+
317
+ <div class="option-group">
318
+ <div class="option-title">TTS Provider</div>
319
+ <div class="radio-group">
320
+ <label class="radio-option">
321
+ <input type="radio" name="tts_provider" value="gtts" checked>
322
+ gTTS (Free)
323
+ </label>
324
+ {% if tts_api_available %}
325
+ <label class="radio-option">
326
+ <input type="radio" name="tts_provider" value="api">
327
+ Premium TTS API
328
  </label>
329
+ {% endif %}
330
+ </div>
331
+ {% if tts_api_available %}
332
+ <div class="notice notice-api">
333
+ <i class="fas fa-info-circle"></i>
334
+ <div>
335
+ <strong>Premium TTS API</strong>
336
+ <p>Higher quality with expressive narration</p>
337
+ </div>
338
  </div>
339
+ {% else %}
340
+ <div class="notice notice-gtts">
341
+ <i class="fas fa-info-circle"></i>
342
+ <div>
343
+ <strong>gTTS</strong>
344
+ <p>Basic text-to-speech with clean narration</p>
345
+ </div>
346
+ </div>
347
+ {% endif %}
348
  </div>
349
  </div>
350
 
 
352
  <i class="fas fa-magic"></i>
353
  Generate Dubbed Video
354
  </button>
 
 
 
 
 
355
  </div>
356
 
357
  <div class="progress-container" id="progress-container">
358
  <div class="progress-header">
359
+ <span>Processing Status</span>
360
+ <span id="progress-percent">0%</span>
361
  </div>
362
  <div class="progress-bar">
363
  <div class="progress-fill" id="progress-fill"></div>
 
365
  <div class="progress-details" id="progress-message">
366
  Preparing to process your video...
367
  </div>
 
 
 
 
368
  </div>
369
 
370
  <div class="result-container" id="result-container">
 
376
  <h2>Generated Script</h2>
377
  <div class="script-container" id="script-container"></div>
378
 
379
+ <button id="new-video-btn" class="btn" style="margin-top: 1.5rem;">
380
+ <i class="fas fa-redo"></i>
381
+ Process Another Video
382
+ </button>
 
 
383
  </div>
384
  </div>
385
  </div>
 
394
  const progressContainer = document.getElementById('progress-container');
395
  const progressFill = document.getElementById('progress-fill');
396
  const progressMessage = document.getElementById('progress-message');
397
+ const progressPercent = document.getElementById('progress-percent');
398
  const resultContainer = document.getElementById('result-container');
399
  const resultVideo = document.getElementById('result-video');
400
  const scriptContainer = document.getElementById('script-container');
401
+ const languageSelect = document.getElementById('language-select');
402
+ const voiceTypeSelect = document.getElementById('voice-type');
 
403
  const newVideoBtn = document.getElementById('new-video-btn');
404
 
405
  // State
406
  let currentTaskId = null;
407
  let statusCheckInterval = null;
 
 
 
408
 
409
  // Event Listeners
410
  fileInput.addEventListener('change', handleFileSelect);
 
421
  uploadArea.style.borderColor = '#4361ee';
422
  uploadArea.style.backgroundColor = 'rgba(67, 97, 238, 0.05)';
423
  processBtn.disabled = false;
 
424
 
425
+ // Hide previous results
426
  resultContainer.style.display = 'none';
427
  }
428
  }
 
449
  fileInput.files = e.dataTransfer.files;
450
  fileInfo.textContent = e.dataTransfer.files[0].name;
451
  processBtn.disabled = false;
 
452
 
453
+ // Hide previous results
454
  resultContainer.style.display = 'none';
455
  }
456
  }
457
 
458
  function startProcessing() {
459
  if (!fileInput.files.length) {
460
+ alert('Please select a video file first');
461
  return;
462
  }
463
 
464
  // Get processing options
465
+ const language = languageSelect.value;
466
+ const voiceType = voiceTypeSelect.value;
467
+ const ttsProvider = document.querySelector('input[name="tts_provider"]:checked').value;
468
 
469
  // Prepare form data
470
  const formData = new FormData();
471
  formData.append('video', fileInput.files[0]);
472
+ formData.append('language', language);
473
+ formData.append('voice_type', voiceType);
474
+ formData.append('tts_provider', ttsProvider);
475
 
476
  // Show progress UI
477
  uploadContainer.style.display = 'none';
478
  progressContainer.style.display = 'block';
479
  progressFill.style.width = '0%';
480
+ progressPercent.textContent = '0%';
481
+ progressMessage.textContent = 'Starting processing...';
 
 
 
482
 
483
  // Start processing
484
  fetch('/upload', {
 
491
  throw new Error(data.error);
492
  }
493
  currentTaskId = data.task_id;
 
494
  startStatusChecking();
495
  })
496
  .catch(error => {
 
504
  clearInterval(statusCheckInterval);
505
  }
506
 
507
+ // Check status every 2 seconds
508
  statusCheckInterval = setInterval(() => {
509
  fetch(`/status/${currentTaskId}`)
510
  .then(response => response.json())
 
512
  .catch(error => {
513
  console.error('Status check failed:', error);
514
  });
515
+ }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  }
517
 
518
  function updateStatus(status) {
519
  if (status.error) {
520
+ showError(status.error_details || status.message);
 
521
  return;
522
  }
523
 
524
  // Update progress
525
+ const progress = status.progress || 0;
526
+ progressFill.style.width = `${progress}%`;
527
+ progressPercent.textContent = `${progress}%`;
528
  progressMessage.textContent = status.message;
 
 
 
 
 
 
529
 
530
  // Handle completion
531
  if (status.status === 'complete') {
532
  clearInterval(statusCheckInterval);
533
+ progressContainer.style.display = 'none';
 
534
 
535
  // Show results
536
  resultVideo.src = status.result_url;
537
  scriptContainer.textContent = status.script;
 
538
  resultContainer.style.display = 'block';
 
 
 
 
539
  }
540
  }
541
 
542
  function showError(message) {
 
 
543
  alert(`Error: ${message}`);
544
+ uploadContainer.style.display = 'block';
545
+ progressContainer.style.display = 'none';
546
+ resultContainer.style.display = 'none';
547
  }
548
 
549
  function resetForm() {
 
554
  scriptContainer.textContent = '';
555
  resultContainer.style.display = 'none';
556
  uploadContainer.style.display = 'block';
557
+ progressContainer.style.display = 'none';
558
  processBtn.disabled = true;
559
  uploadArea.style.borderColor = '#4895ef';
560
  uploadArea.style.backgroundColor = 'transparent';
 
561
  }
562
  </script>
563
  </body>