yassinovich commited on
Commit
98aa9d0
·
verified ·
1 Parent(s): 18b88bf

Upload 3 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data/data_test_audio.flac filter=lfs diff=lfs merge=lfs -text
37
+ data/screenshot.png filter=lfs diff=lfs merge=lfs -text
data/data_test_audio.flac ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63a4b1e4c1dc655ac70961ffbf518acd249df237e5a0152faae9a4a836949715
3
+ size 1152693
data/screenshot.png ADDED

Git LFS Details

  • SHA256: 20bbb82cbeb8324646e282f014d3f25bc1b92669c3d65cf775c2d5f39e923fd2
  • Pointer size: 131 Bytes
  • Size of remote file: 563 kB
templates/index.html ADDED
@@ -0,0 +1,694 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Audio to SRT Converter</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --primary-color: #4f46e5;
12
+ --primary-hover: #4338ca;
13
+ --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14
+ --glass-bg: rgba(255, 255, 255, 0.95);
15
+ --text-color: #1f2937;
16
+ --error-color: #ef4444;
17
+ }
18
+
19
+ body {
20
+ font-family: 'Outfit', sans-serif;
21
+ background: var(--bg-gradient);
22
+ display: flex;
23
+ justify-content: center;
24
+ align-items: center;
25
+ min-height: 100vh;
26
+ margin: 0;
27
+ color: var(--text-color);
28
+ }
29
+
30
+ .container {
31
+ background: var(--glass-bg);
32
+ padding: 3rem;
33
+ border-radius: 24px;
34
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
35
+ width: 100%;
36
+ max-width: 480px;
37
+ text-align: center;
38
+ backdrop-filter: blur(10px);
39
+ border: 1px solid rgba(255, 255, 255, 0.2);
40
+ transition: transform 0.3s ease;
41
+ }
42
+
43
+ .container:hover {
44
+ transform: translateY(-5px);
45
+ }
46
+
47
+ h1 {
48
+ color: var(--text-color);
49
+ margin-bottom: 0.5rem;
50
+ font-weight: 600;
51
+ font-size: 2rem;
52
+ }
53
+
54
+ .subtitle {
55
+ color: #6b7280;
56
+ margin-bottom: 2rem;
57
+ font-weight: 300;
58
+ }
59
+
60
+ form {
61
+ display: flex;
62
+ flex-direction: column;
63
+ gap: 1.5rem;
64
+ }
65
+
66
+ .file-input-wrapper {
67
+ position: relative;
68
+ width: 100%;
69
+ }
70
+
71
+ input[type="file"] {
72
+ display: none;
73
+ }
74
+
75
+ .drop-zone {
76
+ width: 100%;
77
+ padding: 3rem 2rem;
78
+ border: 3px dashed #cbd5e1;
79
+ border-radius: 16px;
80
+ background-color: #f8fafc;
81
+ cursor: pointer;
82
+ transition: all 0.3s ease;
83
+ box-sizing: border-box;
84
+ text-align: center;
85
+ position: relative;
86
+ }
87
+
88
+ .drop-zone:hover {
89
+ border-color: var(--primary-color);
90
+ background-color: #f1f5f9;
91
+ transform: scale(1.02);
92
+ }
93
+
94
+ .drop-zone.drag-over {
95
+ border-color: var(--primary-color);
96
+ background-color: #eef2ff;
97
+ border-style: solid;
98
+ box-shadow: 0 0 20px rgba(79, 70, 229, 0.2);
99
+ }
100
+
101
+ .drop-zone-icon {
102
+ font-size: 3rem;
103
+ margin-bottom: 1rem;
104
+ opacity: 0.6;
105
+ }
106
+
107
+ .drop-zone-text {
108
+ font-size: 1.1rem;
109
+ color: var(--text-color);
110
+ font-weight: 500;
111
+ margin-bottom: 0.5rem;
112
+ }
113
+
114
+ .drop-zone-subtext {
115
+ font-size: 0.9rem;
116
+ color: #6b7280;
117
+ }
118
+
119
+ .file-name {
120
+ margin-top: 1rem;
121
+ padding: 0.75rem;
122
+ background-color: #e0e7ff;
123
+ border-radius: 8px;
124
+ color: var(--primary-color);
125
+ font-weight: 500;
126
+ display: none;
127
+ word-break: break-word;
128
+ }
129
+
130
+ .options-grid {
131
+ display: grid;
132
+ grid-template-columns: 1fr;
133
+ gap: 1rem;
134
+ text-align: left;
135
+ }
136
+
137
+ .option-group {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 0.5rem;
141
+ }
142
+
143
+ .option-group label {
144
+ font-size: 0.9rem;
145
+ font-weight: 500;
146
+ color: #6b7280;
147
+ margin-left: 0.25rem;
148
+ }
149
+
150
+ select {
151
+ width: 100%;
152
+ padding: 0.875rem 1rem;
153
+ border: 1px solid #e2e8f0;
154
+ border-radius: 12px;
155
+ background-color: white;
156
+ font-family: inherit;
157
+ font-size: 1rem;
158
+ cursor: pointer;
159
+ appearance: none;
160
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
161
+ background-repeat: no-repeat;
162
+ background-position: right 1rem center;
163
+ background-size: 1.5em;
164
+ transition: all 0.2s ease;
165
+ }
166
+
167
+ select:focus {
168
+ outline: none;
169
+ border-color: var(--primary-color);
170
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
171
+ }
172
+
173
+ button {
174
+ background-color: var(--primary-color);
175
+ color: white;
176
+ padding: 1rem;
177
+ border: none;
178
+ border-radius: 12px;
179
+ cursor: pointer;
180
+ font-size: 1.1rem;
181
+ font-weight: 500;
182
+ transition: all 0.3s ease;
183
+ display: flex;
184
+ justify-content: center;
185
+ align-items: center;
186
+ gap: 0.5rem;
187
+ }
188
+
189
+ button:hover {
190
+ background-color: var(--primary-hover);
191
+ transform: translateY(-2px);
192
+ box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
193
+ }
194
+
195
+ button:disabled {
196
+ background-color: #9ca3af;
197
+ cursor: not-allowed;
198
+ transform: none;
199
+ box-shadow: none;
200
+ }
201
+
202
+ .note {
203
+ margin-top: 1.5rem;
204
+ font-size: 0.9rem;
205
+ color: #6b7280;
206
+ line-height: 1.5;
207
+ }
208
+
209
+ #error-message {
210
+ color: var(--error-color);
211
+ font-size: 0.9rem;
212
+ margin-top: 0.5rem;
213
+ display: none;
214
+ padding: 0.5rem;
215
+ background-color: #fef2f2;
216
+ border-radius: 8px;
217
+ border: 1px solid #fee2e2;
218
+ }
219
+
220
+ /* Loading Spinner */
221
+ .spinner {
222
+ width: 20px;
223
+ height: 20px;
224
+ border: 3px solid rgba(255, 255, 255, 0.3);
225
+ border-radius: 50%;
226
+ border-top-color: white;
227
+ animation: spin 1s ease-in-out infinite;
228
+ display: none;
229
+ }
230
+
231
+ @keyframes spin {
232
+ to {
233
+ transform: rotate(360deg);
234
+ }
235
+ }
236
+
237
+ .hidden {
238
+ display: none !important;
239
+ }
240
+
241
+ /* Social Links */
242
+ .social-links {
243
+ margin-top: 2rem;
244
+ display: flex;
245
+ justify-content: center;
246
+ gap: 1.5rem;
247
+ padding-top: 1.5rem;
248
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
249
+ }
250
+
251
+ .social-links a {
252
+ display: flex;
253
+ align-items: center;
254
+ justify-content: center;
255
+ width: 48px;
256
+ height: 48px;
257
+ border-radius: 12px;
258
+ background-color: #f8fafc;
259
+ transition: all 0.3s ease;
260
+ text-decoration: none;
261
+ }
262
+
263
+ .social-links a:hover {
264
+ transform: translateY(-3px);
265
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
266
+ }
267
+
268
+ .social-links a.github:hover {
269
+ background-color: #24292e;
270
+ }
271
+
272
+ .social-links a.linkedin:hover {
273
+ background-color: #0077b5;
274
+ }
275
+
276
+ .social-links svg {
277
+ width: 24px;
278
+ height: 24px;
279
+ fill: #6b7280;
280
+ transition: fill 0.3s ease;
281
+ }
282
+
283
+ .social-links a:hover svg {
284
+ fill: white;
285
+ }
286
+
287
+ /* Modal Styles */
288
+ .modal-overlay {
289
+ display: none;
290
+ position: fixed;
291
+ top: 0;
292
+ left: 0;
293
+ width: 100%;
294
+ height: 100%;
295
+ background-color: rgba(0, 0, 0, 0.6);
296
+ backdrop-filter: blur(5px);
297
+ z-index: 1000;
298
+ justify-content: center;
299
+ align-items: center;
300
+ animation: fadeIn 0.3s ease;
301
+ }
302
+
303
+ .modal-overlay.active {
304
+ display: flex;
305
+ }
306
+
307
+ .modal-content {
308
+ background: white;
309
+ padding: 2.5rem;
310
+ border-radius: 20px;
311
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
312
+ max-width: 500px;
313
+ width: 90%;
314
+ position: relative;
315
+ animation: slideUp 0.4s ease;
316
+ text-align: center;
317
+ }
318
+
319
+ .modal-close {
320
+ position: absolute;
321
+ top: 1rem;
322
+ right: 1rem;
323
+ background: none;
324
+ border: none;
325
+ font-size: 1.5rem;
326
+ color: #9ca3af;
327
+ cursor: pointer;
328
+ padding: 0.5rem;
329
+ line-height: 1;
330
+ transition: color 0.2s ease;
331
+ width: auto;
332
+ }
333
+
334
+ .modal-close:hover {
335
+ color: #4b5563;
336
+ background: none;
337
+ transform: none;
338
+ box-shadow: none;
339
+ }
340
+
341
+ .modal-emoji {
342
+ font-size: 3rem;
343
+ margin-bottom: 1rem;
344
+ }
345
+
346
+ .modal-text {
347
+ font-size: 1.2rem;
348
+ color: var(--text-color);
349
+ margin-bottom: 1.5rem;
350
+ line-height: 1.6;
351
+ font-weight: 500;
352
+ }
353
+
354
+ .linkedin-btn {
355
+ background: linear-gradient(135deg, #0077b5 0%, #005885 100%);
356
+ color: white;
357
+ padding: 1rem 2rem;
358
+ border: none;
359
+ border-radius: 12px;
360
+ cursor: pointer;
361
+ font-size: 1.1rem;
362
+ font-weight: 500;
363
+ transition: all 0.3s ease;
364
+ display: inline-flex;
365
+ align-items: center;
366
+ gap: 0.5rem;
367
+ text-decoration: none;
368
+ box-shadow: 0 4px 12px rgba(0, 119, 181, 0.3);
369
+ }
370
+
371
+ .linkedin-btn:hover {
372
+ transform: translateY(-2px);
373
+ box-shadow: 0 6px 20px rgba(0, 119, 181, 0.4);
374
+ }
375
+
376
+ @keyframes fadeIn {
377
+ from {
378
+ opacity: 0;
379
+ }
380
+
381
+ to {
382
+ opacity: 1;
383
+ }
384
+ }
385
+
386
+ @keyframes slideUp {
387
+ from {
388
+ transform: translateY(30px);
389
+ opacity: 0;
390
+ }
391
+
392
+ to {
393
+ transform: translateY(0);
394
+ opacity: 1;
395
+ }
396
+ }
397
+ </style>
398
+ </head>
399
+
400
+ <body>
401
+ <div class="container">
402
+ <h1>Audio to SRT</h1>
403
+ <p class="subtitle">Transform your audio into subtitles instantly</p>
404
+
405
+ <form id="uploadForm">
406
+ <div class="file-input-wrapper">
407
+ <input type="file" id="fileInput" name="file" accept="audio/*,video/*" required>
408
+ <div class="drop-zone" id="dropZone">
409
+ <div class="drop-zone-icon">📁</div>
410
+ <div class="drop-zone-text">Click to browse or drag & drop</div>
411
+ <div class="drop-zone-subtext">Upload your audio or video file</div>
412
+ <div class="file-name" id="fileName"></div>
413
+ </div>
414
+ </div>
415
+
416
+ <div class="options-grid">
417
+ <div class="option-group">
418
+ <label for="languageSelect">Language</label>
419
+ <select name="language" id="languageSelect">
420
+ <option value="en" selected>English</option>
421
+ <option value="es">Spanish</option>
422
+ <option value="fr">French</option>
423
+ <option value="de">German</option>
424
+ <option value="it">Italian</option>
425
+ <option value="nl">Dutch</option>
426
+ <option value="pt">Portuguese</option>
427
+ <option value="ja">Japanese</option>
428
+ <option value="zh">Chinese</option>
429
+ <option value="ru">Russian</option>
430
+ </select>
431
+ </div>
432
+
433
+ <div class="option-group">
434
+ <label for="modelSelect">Model</label>
435
+ <select name="model_type" id="modelSelect">
436
+ <option value="accurate" selected>Accurate (Large)</option>
437
+ <option value="fast">Fast (Base)</option>
438
+ </select>
439
+ </div>
440
+
441
+ <div class="option-group">
442
+ <label for="durationSelect">Line Duration</label>
443
+ <select name="max_duration" id="durationSelect">
444
+ <option value="1">1 second</option>
445
+ <option value="2" selected>2 seconds</option>
446
+ <option value="3">3 seconds</option>
447
+ <option value="4">4 seconds</option>
448
+ <option value="5">5 seconds</option>
449
+ </select>
450
+ </div>
451
+ </div>
452
+
453
+ <div id="error-message"></div>
454
+
455
+ <button type="button" id="submitBtn">
456
+ <span class="spinner" id="spinner"></span>
457
+ <span id="btnText">Convert to SRT</span>
458
+ </button>
459
+ </form>
460
+
461
+ <p class="note">
462
+ Supported formats: MP3, WAV, FLAC, MP4, MKV, MOV, M4A, OGG, WEBM.<br>
463
+ Large files may take a few minutes to process.
464
+ </p>
465
+
466
+ <div class="social-links">
467
+ <a href="https://github.com/Jamie-Wubben/audio2srt" target="_blank" rel="noopener noreferrer" class="github"
468
+ aria-label="GitHub">
469
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
470
+ <path
471
+ d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
472
+ </svg>
473
+ </a>
474
+ <a href="https://www.linkedin.com/in/jamie-wubben/" target="_blank" rel="noopener noreferrer"
475
+ class="linkedin" aria-label="LinkedIn">
476
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
477
+ <path
478
+ d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" />
479
+ </svg>
480
+ </a>
481
+ </div>
482
+ </div>
483
+
484
+ <!-- Modal Popup -->
485
+ <div class="modal-overlay" id="modalOverlay">
486
+ <div class="modal-content">
487
+ <button class="modal-close" id="modalClose" aria-label="Close">✕</button>
488
+ <div class="modal-emoji">🤖</div>
489
+ <p class="modal-text" id="modalText">I've got the heavy lifting—take a sec, read this post, and drop a like!
490
+ </p>
491
+ <a href="https://www.linkedin.com/posts/jamie-wubben_ai-programming-futureofwork-activity-7399571504486617088-vrM0"
492
+ target="_blank" rel="noopener noreferrer" class="linkedin-btn" id="linkedinBtn">
493
+ 📖 Read the Post
494
+ </a>
495
+ </div>
496
+ </div>
497
+
498
+ <script>
499
+ const form = document.getElementById('uploadForm');
500
+ const fileInput = document.getElementById('fileInput');
501
+ const dropZone = document.getElementById('dropZone');
502
+ const fileName = document.getElementById('fileName');
503
+ const submitBtn = document.getElementById('submitBtn');
504
+ const spinner = document.getElementById('spinner');
505
+ const btnText = document.getElementById('btnText');
506
+ const errorMessage = document.getElementById('error-message');
507
+
508
+ const ALLOWED_EXTENSIONS = ['mp3', 'wav', 'flac', 'mp4', 'mkv', 'mov', 'm4a', 'ogg', 'webm'];
509
+
510
+ function showError(msg) {
511
+ errorMessage.textContent = msg;
512
+ errorMessage.style.display = 'block';
513
+ setTimeout(() => {
514
+ errorMessage.style.display = 'none';
515
+ }, 5000);
516
+ }
517
+
518
+ function validateFile(file) {
519
+ if (!file) return false;
520
+ const extension = file.name.split('.').pop().toLowerCase();
521
+ if (!ALLOWED_EXTENSIONS.includes(extension)) {
522
+ showError(`Invalid file type. Allowed: ${ALLOWED_EXTENSIONS.join(', ')}`);
523
+ return false;
524
+ }
525
+ return true;
526
+ }
527
+
528
+ function displayFileName(file) {
529
+ if (file) {
530
+ fileName.textContent = `📄 ${file.name}`;
531
+ fileName.style.display = 'block';
532
+ } else {
533
+ fileName.style.display = 'none';
534
+ }
535
+ }
536
+
537
+ // Click to browse
538
+ dropZone.addEventListener('click', () => {
539
+ fileInput.click();
540
+ });
541
+
542
+ // File input change
543
+ fileInput.addEventListener('change', function () {
544
+ const file = this.files[0];
545
+ if (file) {
546
+ if (validateFile(file)) {
547
+ displayFileName(file);
548
+ } else {
549
+ this.value = ''; // Clear input
550
+ displayFileName(null);
551
+ }
552
+ }
553
+ });
554
+
555
+ // Drag and drop handlers
556
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
557
+ dropZone.addEventListener(eventName, preventDefaults, false);
558
+ });
559
+
560
+ function preventDefaults(e) {
561
+ e.preventDefault();
562
+ e.stopPropagation();
563
+ }
564
+
565
+ ['dragenter', 'dragover'].forEach(eventName => {
566
+ dropZone.addEventListener(eventName, () => {
567
+ dropZone.classList.add('drag-over');
568
+ });
569
+ });
570
+
571
+ ['dragleave', 'drop'].forEach(eventName => {
572
+ dropZone.addEventListener(eventName, () => {
573
+ dropZone.classList.remove('drag-over');
574
+ });
575
+ });
576
+
577
+ dropZone.addEventListener('drop', (e) => {
578
+ const files = e.dataTransfer.files;
579
+ if (files.length > 0) {
580
+ const file = files[0];
581
+ // Create a new FileList-like object
582
+ const dataTransfer = new DataTransfer();
583
+ dataTransfer.items.add(file);
584
+ fileInput.files = dataTransfer.files;
585
+
586
+ if (validateFile(file)) {
587
+ displayFileName(file);
588
+ } else {
589
+ fileInput.value = '';
590
+ displayFileName(null);
591
+ }
592
+ }
593
+ });
594
+
595
+ // Modal elements
596
+ const modalOverlay = document.getElementById('modalOverlay');
597
+ const modalClose = document.getElementById('modalClose');
598
+ const modalText = document.getElementById('modalText');
599
+ const linkedinBtn = document.getElementById('linkedinBtn');
600
+
601
+ // Close modal
602
+ modalClose.addEventListener('click', () => {
603
+ modalOverlay.classList.remove('active');
604
+ });
605
+
606
+ // Close modal when clicking outside
607
+ modalOverlay.addEventListener('click', (e) => {
608
+ if (e.target === modalOverlay) {
609
+ modalOverlay.classList.remove('active');
610
+ }
611
+ });
612
+
613
+ // Update text when LinkedIn button is clicked
614
+ linkedinBtn.addEventListener('click', () => {
615
+ modalText.textContent = 'Thanks for checking it out! Your file will be ready soon. 🚀';
616
+ });
617
+
618
+ submitBtn.addEventListener('click', async function (e) {
619
+ e.preventDefault();
620
+
621
+ // Check form validity (for required fields)
622
+ if (!form.checkValidity()) {
623
+ form.reportValidity();
624
+ return;
625
+ }
626
+
627
+ const file = fileInput.files[0];
628
+ if (!file) {
629
+ showError('Please select a file first.');
630
+ return;
631
+ }
632
+
633
+ // Show modal immediately
634
+ modalOverlay.classList.add('active');
635
+
636
+ // UI Loading State
637
+ submitBtn.disabled = true;
638
+ spinner.style.display = 'block';
639
+ btnText.textContent = 'Transcribing...';
640
+ errorMessage.style.display = 'none';
641
+
642
+ const formData = new FormData(form);
643
+
644
+ try {
645
+ const response = await fetch('/transcribe', {
646
+ method: 'POST',
647
+ body: formData
648
+ });
649
+
650
+ if (!response.ok) {
651
+ const errorText = await response.text();
652
+ throw new Error(errorText || 'Transcription failed');
653
+ }
654
+
655
+ // Handle successful download
656
+ const blob = await response.blob();
657
+ const url = window.URL.createObjectURL(blob);
658
+ const a = document.createElement('a');
659
+ a.style.display = 'none';
660
+ a.href = url;
661
+ // Get filename from header or default
662
+ const contentDisposition = response.headers.get('Content-Disposition');
663
+ let filename = 'output.srt';
664
+ if (contentDisposition) {
665
+ const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
666
+ if (filenameMatch.length === 2)
667
+ filename = filenameMatch[1];
668
+ }
669
+ a.download = filename;
670
+ document.body.appendChild(a);
671
+ a.click();
672
+ window.URL.revokeObjectURL(url);
673
+
674
+ // Close modal after successful download
675
+ modalOverlay.classList.remove('active');
676
+
677
+ } catch (error) {
678
+ showError(error.message);
679
+ modalOverlay.classList.remove('active');
680
+ } finally {
681
+ // Reset UI
682
+ submitBtn.disabled = false;
683
+ spinner.style.display = 'none';
684
+ btnText.textContent = 'Convert to SRT';
685
+ fileInput.value = ''; // Optional: clear file after success
686
+ // Reset modal text for next time
687
+ modalText.textContent = "I've got the heavy lifting—take a sec, read this post, and drop a like!";
688
+ displayFileName(null);
689
+ }
690
+ });
691
+ </script>
692
+ </body>
693
+
694
+ </html>