dimchr commited on
Commit
683bc3a
·
verified ·
1 Parent(s): 00ad140

In reality , each line may be made up of more than one sentences or phrases. Provide an example - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +615 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Captions
3
- emoji: 😻
4
- colorFrom: pink
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: captions
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,615 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="View YouTube videos with original and translated captions side by side for better language learning and accessibility.">
7
+ <meta name="keywords" content="YouTube captions, video captions, caption translator, language learning, accessibility">
8
+ <meta name="author" content="YouTube Caption Viewer">
9
+ <title>YouTube Dual Caption Viewer | Compare Original and Translated Captions</title>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
12
+ <style>
13
+ .caption-container {
14
+ scroll-behavior: smooth;
15
+ }
16
+ .highlight {
17
+ background-color: rgba(255, 235, 59, 0.3);
18
+ transition: background-color 0.3s ease;
19
+ }
20
+ .loading-spinner {
21
+ border: 4px solid rgba(0, 0, 0, 0.1);
22
+ border-radius: 50%;
23
+ border-top: 4px solid #3498db;
24
+ width: 40px;
25
+ height: 40px;
26
+ animation: spin 1s linear infinite;
27
+ }
28
+ @keyframes spin {
29
+ 0% { transform: rotate(0deg); }
30
+ 100% { transform: rotate(360deg); }
31
+ }
32
+ </style>
33
+ </head>
34
+ <body class="bg-gray-100 min-h-screen">
35
+ <header class="bg-red-600 text-white shadow-lg">
36
+ <div class="container mx-auto px-4 py-6">
37
+ <div class="flex flex-col md:flex-row justify-between items-center">
38
+ <div class="flex items-center mb-4 md:mb-0">
39
+ <i class="fab fa-youtube text-3xl mr-3"></i>
40
+ <h1 class="text-2xl font-bold">YouTube Dual Caption Viewer</h1>
41
+ </div>
42
+ <div class="flex items-center space-x-2">
43
+ <span class="hidden md:inline text-sm">Compare original and translated captions</span>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </header>
48
+
49
+ <main class="container mx-auto px-4 py-8">
50
+ <section class="bg-white rounded-lg shadow-md p-6 mb-8">
51
+ <h2 class="text-xl font-semibold mb-4 text-gray-800">Enter YouTube Video URL</h2>
52
+ <div class="flex flex-col md:flex-row gap-4">
53
+ <input
54
+ type="url"
55
+ id="youtubeUrl"
56
+ placeholder="https://www.youtube.com/watch?v=..."
57
+ class="flex-grow px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 outline-none transition"
58
+ required
59
+ >
60
+ <button
61
+ id="loadVideoBtn"
62
+ class="bg-red-600 hover:bg-red-700 text-white font-medium py-3 px-6 rounded-lg transition flex items-center justify-center"
63
+ >
64
+ <span id="btnText">Load Video</span>
65
+ <div id="loadingSpinner" class="hidden ml-2">
66
+ <div class="loading-spinner"></div>
67
+ </div>
68
+ </button>
69
+ </div>
70
+ <p class="mt-3 text-sm text-gray-600">Note: The video must have captions available for this tool to work.</p>
71
+ </section>
72
+
73
+ <div id="videoContainer" class="hidden bg-white rounded-lg shadow-md overflow-hidden mb-8">
74
+ <div class="aspect-w-16 aspect-h-9">
75
+ <iframe id="youtubePlayer" class="w-full h-96" frameborder="0" allowfullscreen></iframe>
76
+ </div>
77
+ </div>
78
+
79
+ <div id="captionSection" class="hidden">
80
+ <div class="bg-white rounded-lg shadow-md p-4 mb-8">
81
+ <div class="flex items-center justify-between mb-4">
82
+ <h3 class="text-lg font-semibold text-gray-800">Dual Captions</h3>
83
+ <div class="flex items-center space-x-4">
84
+ <span id="originalLanguage" class="px-2 py-1 bg-gray-100 rounded text-sm"></span>
85
+ <select id="languageSelect" class="px-2 py-1 border border-gray-300 rounded text-sm">
86
+ <option value="en">English</option>
87
+ <option value="es">Spanish</option>
88
+ <option value="fr">French</option>
89
+ <option value="de">German</option>
90
+ <option value="ja">Japanese</option>
91
+ <option value="ko">Korean</option>
92
+ <option value="zh">Chinese</option>
93
+ <option value="pt">Portuguese</option>
94
+ <option value="ru">Russian</option>
95
+ <option value="ar">Arabic</option>
96
+ </select>
97
+ </div>
98
+ </div>
99
+ <div id="mergedCaptions" class="caption-container h-96 overflow-y-auto p-1 bg-gray-50 rounded border border-gray-200">
100
+ <!-- Merged captions will be inserted here -->
101
+ </div>
102
+ </div>
103
+
104
+ <div class="bg-white rounded-lg shadow-md p-6">
105
+ <h3 class="text-lg font-semibold mb-4 text-gray-800">Caption Controls</h3>
106
+ <div class="flex flex-wrap gap-4">
107
+ <button id="syncCaptionsBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">
108
+ <i class="fas fa-sync-alt mr-2"></i> Sync with Video
109
+ </button>
110
+ <button id="highlightCurrentBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded">
111
+ <i class="fas fa-highlighter mr-2"></i> Highlight Current Line
112
+ </button>
113
+ <button id="fontSizePlusBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded">
114
+ <i class="fas fa-text-height mr-2"></i> Increase Font Size
115
+ </button>
116
+ <button id="fontSizeMinusBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded">
117
+ <i class="fas fa-text-height mr-2"></i> Decrease Font Size
118
+ </button>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <div id="savedCaptionsSection" class="hidden bg-white rounded-lg shadow-md p-6 mb-8">
124
+ <h3 class="text-lg font-semibold mb-4 text-gray-800">Saved Captions</h3>
125
+ <div id="savedCaptionsList" class="mb-4 p-3 bg-gray-50 rounded border border-gray-200 max-h-64 overflow-y-auto">
126
+ <!-- Saved captions will appear here -->
127
+ </div>
128
+ <div class="flex flex-wrap gap-4">
129
+ <button id="copySRT" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">
130
+ <i class="fas fa-copy mr-2"></i> Copy as SRT
131
+ </button>
132
+ <button id="copyExcel" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded">
133
+ <i class="fas fa-file-excel mr-2"></i> Copy as Excel
134
+ </button>
135
+ <button id="copyTSV" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded">
136
+ <i class="fas fa-file-alt mr-2"></i> Copy as TSV
137
+ </button>
138
+ <button id="clearSaved" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded">
139
+ <i class="fas fa-trash-alt mr-2"></i> Clear All
140
+ </button>
141
+ </div>
142
+ </div>
143
+
144
+ <div id="errorMessage" class="hidden bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-8 rounded">
145
+ <div class="flex items-center">
146
+ <i class="fas fa-exclamation-circle mr-3"></i>
147
+ <div>
148
+ <p id="errorText"></p>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </main>
153
+
154
+ <footer class="bg-gray-800 text-white py-8">
155
+ <div class="container mx-auto px-4">
156
+ <div class="flex flex-col md:flex-row justify-between items-center">
157
+ <div class="mb-4 md:mb-0">
158
+ <h3 class="text-lg font-semibold mb-2">YouTube Dual Caption Viewer</h3>
159
+ <p class="text-gray-400 text-sm">Enhance your language learning and accessibility experience</p>
160
+ </div>
161
+ <div class="flex space-x-4">
162
+ <a href="#" class="text-gray-400 hover:text-white transition">
163
+ <i class="fab fa-github text-xl"></i>
164
+ </a>
165
+ <a href="#" class="text-gray-400 hover:text-white transition">
166
+ <i class="fab fa-twitter text-xl"></i>
167
+ </a>
168
+ </div>
169
+ </div>
170
+ <div class="border-t border-gray-700 mt-6 pt-6 text-center text-gray-400 text-sm">
171
+ <p>© 2023 YouTube Dual Caption Viewer. This tool is not affiliated with YouTube or Google.</p>
172
+ </div>
173
+ </div>
174
+ </footer>
175
+
176
+ <script>
177
+ document.addEventListener('DOMContentLoaded', function() {
178
+ const youtubeUrlInput = document.getElementById('youtubeUrl');
179
+ const loadVideoBtn = document.getElementById('loadVideoBtn');
180
+ const btnText = document.getElementById('btnText');
181
+ const loadingSpinner = document.getElementById('loadingSpinner');
182
+ const videoContainer = document.getElementById('videoContainer');
183
+ const youtubePlayer = document.getElementById('youtubePlayer');
184
+ const captionSection = document.getElementById('captionSection');
185
+ const mergedCaptions = document.getElementById('mergedCaptions');
186
+ const originalLanguage = document.getElementById('originalLanguage');
187
+ const languageSelect = document.getElementById('languageSelect');
188
+ const errorMessage = document.getElementById('errorMessage');
189
+ const errorText = document.getElementById('errorText');
190
+
191
+ // Control buttons
192
+ const syncCaptionsBtn = document.getElementById('syncCaptionsBtn');
193
+ const highlightCurrentBtn = document.getElementById('highlightCurrentBtn');
194
+ const fontSizePlusBtn = document.getElementById('fontSizePlusBtn');
195
+ const fontSizeMinusBtn = document.getElementById('fontSizeMinusBtn');
196
+
197
+ let currentFontSize = 14;
198
+ let highlightEnabled = false;
199
+ let player;
200
+ let captions = [];
201
+ let translatedCaptionData = {};
202
+
203
+ // Mock data for demonstration
204
+ const mockCaptions = [
205
+ { start: 0, end: 5, text: "Hello everyone! Welcome to our channel. We're excited to have you here." },
206
+ { start: 5, end: 10, text: "Today's topic is language learning. Specifically, we'll discuss techniques and common challenges." },
207
+ { start: 10, end: 15, text: "Learning a new language can be challenging. However, it's incredibly rewarding. The benefits are numerous." },
208
+ { start: 15, end: 20, text: "Let's begin with immersion. Surround yourself with the language. Listen, read, and speak daily." },
209
+ { start: 20, end: 25, text: "First strategy: media consumption. Watch shows, listen to podcasts. Try to understand native speakers." },
210
+ { start: 25, end: 30, text: "Second approach: practice speaking. Find language partners. Don't worry about perfection - just communicate!" },
211
+ { start: 30, end: 35, text: "Third method: vocabulary building. Learn words in context. Use flashcards for memorization." },
212
+ { start: 35, end: 40, text: "Common mistake: focusing too much on grammar. While important, communication should come first. Fluency develops over time." },
213
+ { start: 40, end: 45, text: "Set achievable goals. For example: learn 10 words daily. Have a 5-minute conversation. Track your progress weekly." },
214
+ { start: 45, end: 50, text: "Celebrate milestones! Finished a book? Had a conversation? These victories matter. They keep you motivated." },
215
+ { start: 50, end: 55, text: "Consistency is key. Practice regularly, even if briefly. Patience is equally important - progress takes time." },
216
+ { start: 55, end: 60, text: "Remember: language learning opens doors. It connects cultures. It's a lifelong journey worth taking." }
217
+ ];
218
+
219
+ const mockTranslations = {
220
+ en: mockCaptions,
221
+ es: [
222
+ { start: 0, end: 5, text: "¡Hola a todos! Bienvenidos a nuestro canal. Estamos encantados de tenerlos aquí." },
223
+ { start: 5, end: 10, text: "El tema de hoy es aprender idiomas. Específicamente, hablaremos de técnicas y desafíos comunes." },
224
+ { start: 10, end: 15, text: "Aprender un nuevo idioma puede ser difícil. Sin embargo, es muy gratificante. Los beneficios son numerosos." },
225
+ { start: 15, end: 20, text: "Comencemos con la inmersión. Rodéate del idioma. Escucha, lee y habla diariamente." },
226
+ { start: 20, end: 25, text: "Primera estrategia: consumir medios. Ve programas, escucha podcasts. Intenta entender a hablantes nativos." },
227
+ { start: 25, end: 30, text: "Segundo enfoque: practicar hablando. Encuentra compañeros de idioma. No te preocupes por la perfección, ¡solo comunícate!" },
228
+ { start: 30, end: 35, text: "Tercer método: ampliar vocabulario. Aprende palabras en contexto. Usa tarjetas para memorizar." },
229
+ { start: 35, end: 40, text: "Error común: enfocarse demasiado en gramática. Aunque importante, la comunicación debe ser primero. La fluidez se desarrolla con el tiempo." },
230
+ { start: 40, end: 45, text: "Establece metas alcanzables. Por ejemplo: aprende 10 palabras diarias. Ten una conversación de 5 minutos. Registra tu progreso semanalmente." },
231
+ { start: 45, end: 50, text: "¡Celebra los logros! ¿Terminaste un libro? ¿Tuviste una conversación? Estas victorias importan. Te mantienen motivado." },
232
+ { start: 50, end: 55, text: "La constancia es clave. Practica regularmente, aunque sea poco. La paciencia es igual de importante - el progreso lleva tiempo." },
233
+ { start: 55, end: 60, text: "Recuerda: aprender idiomas abre puertas. Conecta culturas. Es un viaje que vale la pena emprender." }
234
+ ],
235
+ fr: [
236
+ { start: 0, end: 5, text: "Bonjour à tous et bienvenue sur notre chaîne." },
237
+ { start: 5, end: 10, text: "Aujourd'hui, nous allons parler de l'apprentissage des langues." },
238
+ { start: 10, end: 15, text: "Apprendre une nouvelle langue peut être difficile mais gratifiant." },
239
+ { start: 15, end: 20, text: "Parlons de quelques stratégies efficaces." },
240
+ { start: 20, end: 25, text: "Tout d'abord, immergez-vous autant que possible dans la langue." },
241
+ { start: 25, end: 30, text: "Regardez des films, écoutez de la musique et essayez de penser dans la langue." },
242
+ { start: 30, end: 35, text: "Deuxièmement, pratiquez en parlant avec des locuteurs natifs." },
243
+ { start: 35, end: 40, text: "N'ayez pas peur de faire des erreurs, cela fait partie du processus." },
244
+ { start: 40, end: 45, text: "Troisièmement, fixez-vous des objectifs réalistes et suivez vos progrès." },
245
+ { start: 45, end: 50, text: "Célébrez les petites victoires pour rester motivé." },
246
+ { start: 50, end: 55, text: "Enfin, soyez constant et patient avec vous-même." },
247
+ { start: 55, end: 60, text: "L'apprentissage d'une langue est un voyage, pas une destination." }
248
+ ]
249
+ };
250
+
251
+ // Language names for display
252
+ const languageNames = {
253
+ en: "English",
254
+ es: "Spanish",
255
+ fr: "French",
256
+ de: "German",
257
+ ja: "Japanese",
258
+ ko: "Korean",
259
+ zh: "Chinese",
260
+ pt: "Portuguese",
261
+ ru: "Russian",
262
+ ar: "Arabic"
263
+ };
264
+
265
+ // Initialize the page
266
+ function init() {
267
+ // Set up event listeners
268
+ loadVideoBtn.addEventListener('click', loadVideo);
269
+ languageSelect.addEventListener('change', updateTranslation);
270
+ syncCaptionsBtn.addEventListener('click', toggleSyncCaptions);
271
+ highlightCurrentBtn.addEventListener('click', toggleHighlightCurrent);
272
+ fontSizePlusBtn.addEventListener('click', increaseFontSize);
273
+ fontSizeMinusBtn.addEventListener('click', decreaseFontSize);
274
+
275
+ // New event listeners for saved captions
276
+ document.getElementById('copySRT').addEventListener('click', copyAsSRT);
277
+ document.getElementById('copyExcel').addEventListener('click', copyAsExcel);
278
+ document.getElementById('copyTSV').addEventListener('click', copyAsTSV);
279
+ document.getElementById('clearSaved').addEventListener('click', clearSavedCaptions);
280
+
281
+ // For demo purposes, we'll use mock data
282
+ // In a real implementation, you would:
283
+ // 1. Extract video ID from URL
284
+ // 2. Use YouTube API to get captions
285
+ // 3. Display them in the interface
286
+ }
287
+
288
+ // Load video from URL
289
+ function loadVideo() {
290
+ const url = youtubeUrlInput.value.trim();
291
+
292
+ if (!url) {
293
+ showError("Please enter a YouTube video URL");
294
+ return;
295
+ }
296
+
297
+ // Validate YouTube URL
298
+ const videoId = extractVideoId(url);
299
+ if (!videoId) {
300
+ showError("Please enter a valid YouTube video URL");
301
+ return;
302
+ }
303
+
304
+ // Show loading state
305
+ btnText.textContent = "Loading...";
306
+ loadingSpinner.classList.remove('hidden');
307
+ loadVideoBtn.disabled = true;
308
+
309
+ // In a real implementation, you would:
310
+ // 1. Check if captions are available for this video
311
+ // 2. Fetch the captions
312
+ // 3. Process them
313
+
314
+ // For demo purposes, we'll use a timeout to simulate loading
315
+ setTimeout(() => {
316
+ // Hide loading state
317
+ btnText.textContent = "Load Video";
318
+ loadingSpinner.classList.add('hidden');
319
+ loadVideoBtn.disabled = false;
320
+
321
+ // Show video and captions
322
+ showVideo(videoId);
323
+ processCaptions();
324
+
325
+ // Hide any previous error
326
+ hideError();
327
+ }, 1500);
328
+ }
329
+
330
+ // Extract video ID from URL
331
+ function extractVideoId(url) {
332
+ // This is a simplified version - real implementation would handle more URL formats
333
+ const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
334
+ const match = url.match(regExp);
335
+ return (match && match[2].length === 11) ? match[2] : null;
336
+ }
337
+
338
+ // Show video in player
339
+ function showVideo(videoId) {
340
+ youtubePlayer.src = `https://www.youtube.com/embed/${videoId}?enablejsapi=1`;
341
+ videoContainer.classList.remove('hidden');
342
+ captionSection.classList.remove('hidden');
343
+ }
344
+
345
+ // Process captions (mock data for demo)
346
+ function processCaptions() {
347
+ captions = mockCaptions;
348
+ translatedCaptionData = mockTranslations;
349
+
350
+ // Display original language
351
+ originalLanguage.textContent = `Language: ${languageNames['en']}`;
352
+
353
+ // Display merged captions
354
+ displayMergedCaptions();
355
+ }
356
+
357
+ // Display merged captions
358
+ function displayMergedCaptions() {
359
+ mergedCaptions.innerHTML = '';
360
+ const selectedLanguage = languageSelect.value;
361
+ const translations = translatedCaptionData[selectedLanguage] || captions;
362
+
363
+ captions.forEach((caption, index) => {
364
+ const captionElement = document.createElement('div');
365
+ captionElement.className = 'mb-2 p-2 rounded hover:bg-gray-100 transition cursor-pointer border-b border-gray-200';
366
+ captionElement.dataset.start = caption.start;
367
+ captionElement.dataset.end = caption.end;
368
+
369
+ // Original caption
370
+ const originalText = document.createElement('div');
371
+ originalText.className = 'text-gray-700 mb-1';
372
+ originalText.textContent = caption.text;
373
+
374
+ // Translated caption
375
+ const translatedText = document.createElement('div');
376
+ translatedText.className = 'text-gray-500 italic';
377
+ translatedText.textContent = translations[index]?.text || caption.text;
378
+
379
+ captionElement.appendChild(originalText);
380
+ captionElement.appendChild(translatedText);
381
+
382
+ // Click on caption to seek to that time
383
+ captionElement.addEventListener('click', () => {
384
+ if (player) {
385
+ player.seekTo(caption.start);
386
+ }
387
+ });
388
+
389
+ mergedCaptions.appendChild(captionElement);
390
+ });
391
+ }
392
+
393
+ // Update translation based on selected language
394
+ function updateTranslation() {
395
+ displayMergedCaptions();
396
+ }
397
+
398
+ // Toggle caption syncing with video
399
+ function toggleSyncCaptions() {
400
+ // In a real implementation, this would:
401
+ // 1. Track video playback position
402
+ // 2. Highlight the current caption in both columns
403
+ // 3. Auto-scroll to keep current caption visible
404
+
405
+ alert("Caption syncing would be implemented with YouTube Player API in a real application");
406
+ }
407
+
408
+ // Toggle highlighting of current caption
409
+ function toggleHighlightCurrent() {
410
+ highlightEnabled = !highlightEnabled;
411
+
412
+ if (highlightEnabled) {
413
+ highlightCurrentBtn.classList.add('bg-green-700');
414
+ highlightCurrentBtn.classList.remove('bg-green-600');
415
+ } else {
416
+ highlightCurrentBtn.classList.remove('bg-green-700');
417
+ highlightCurrentBtn.classList.add('bg-green-600');
418
+
419
+ // Remove all highlights
420
+ document.querySelectorAll('.highlight').forEach(el => {
421
+ el.classList.remove('highlight');
422
+ });
423
+ }
424
+ }
425
+
426
+ // Increase font size for captions
427
+ function increaseFontSize() {
428
+ currentFontSize += 1;
429
+ updateCaptionFontSize();
430
+ }
431
+
432
+ // Decrease font size for captions
433
+ function decreaseFontSize() {
434
+ if (currentFontSize > 12) {
435
+ currentFontSize -= 1;
436
+ updateCaptionFontSize();
437
+ }
438
+ }
439
+
440
+ // Update font size for caption containers
441
+ function updateCaptionFontSize() {
442
+ const captionContainers = document.querySelectorAll('.caption-container');
443
+ captionContainers.forEach(container => {
444
+ container.style.fontSize = `${currentFontSize}px`;
445
+ });
446
+ }
447
+
448
+ // Show error message
449
+ function showError(message) {
450
+ errorText.textContent = message;
451
+ errorMessage.classList.remove('hidden');
452
+ }
453
+
454
+ // Hide error message
455
+ function hideError() {
456
+ errorMessage.classList.add('hidden');
457
+ }
458
+
459
+ // Array to store saved captions
460
+ let savedCaptions = [];
461
+
462
+ // Show the saved captions section when first caption is saved
463
+ function showSavedCaptionsSection() {
464
+ document.getElementById('savedCaptionsSection').classList.remove('hidden');
465
+ }
466
+
467
+ // Save caption when clicked
468
+ function saveCaption(captionElement) {
469
+ const start = captionElement.dataset.start;
470
+ const end = captionElement.dataset.end;
471
+ const originalText = captionElement.querySelector('div:first-child').textContent;
472
+ const translatedText = captionElement.querySelector('div:last-child').textContent;
473
+
474
+ // Add to saved captions array
475
+ savedCaptions.push({
476
+ start,
477
+ end,
478
+ originalText,
479
+ translatedText
480
+ });
481
+
482
+ // Update saved captions display
483
+ updateSavedCaptionsDisplay();
484
+
485
+ // Show the section if it was hidden
486
+ showSavedCaptionsSection();
487
+ }
488
+
489
+ // Update the saved captions display
490
+ function updateSavedCaptionsDisplay() {
491
+ const container = document.getElementById('savedCaptionsList');
492
+ container.innerHTML = '';
493
+
494
+ savedCaptions.forEach((caption, index) => {
495
+ const div = document.createElement('div');
496
+ div.className = 'mb-3 p-2 bg-white rounded border border-gray-200';
497
+ div.innerHTML = `
498
+ <div class="flex justify-between text-sm text-gray-500 mb-1">
499
+ <span>${formatTime(caption.start)} → ${formatTime(caption.end)}</span>
500
+ <button class="text-red-500 hover:text-red-700" data-index="${index}">
501
+ <i class="fas fa-times"></i>
502
+ </button>
503
+ </div>
504
+ <div class="text-gray-700">${caption.originalText}</div>
505
+ <div class="text-gray-500 italic">${caption.translatedText}</div>
506
+ `;
507
+
508
+ // Add click handler for delete button
509
+ div.querySelector('button').addEventListener('click', (e) => {
510
+ e.stopPropagation();
511
+ savedCaptions.splice(index, 1);
512
+ updateSavedCaptionsDisplay();
513
+ });
514
+
515
+ container.appendChild(div);
516
+ });
517
+ }
518
+
519
+ // Format seconds into SRT time format (HH:MM:SS,mmm)
520
+ function formatTime(seconds) {
521
+ const date = new Date(0);
522
+ date.setSeconds(seconds);
523
+ const timeString = date.toISOString().substr(11, 12);
524
+ return timeString.replace('.', ',');
525
+ }
526
+
527
+ // Copy as SRT format
528
+ function copyAsSRT() {
529
+ let srtContent = '';
530
+ savedCaptions.forEach((caption, index) => {
531
+ srtContent += `${index + 1}\n`;
532
+ srtContent += `${formatTime(caption.start)} --> ${formatTime(caption.end)}\n`;
533
+ srtContent += `${caption.originalText}\n${caption.translatedText}\n\n`;
534
+ });
535
+ navigator.clipboard.writeText(srtContent.trim());
536
+ alert('Copied as SRT to clipboard!');
537
+ }
538
+
539
+ // Copy as Excel (CSV) format
540
+ function copyAsExcel() {
541
+ let csvContent = 'Start Time,End Time,Original Text,Translated Text\n';
542
+ savedCaptions.forEach(caption => {
543
+ csvContent += `"${formatTime(caption.start)}","${formatTime(caption.end)}","${caption.originalText.replace(/"/g, '""')}","${caption.translatedText.replace(/"/g, '""')}"\n`;
544
+ });
545
+ navigator.clipboard.writeText(csvContent);
546
+ alert('Copied as CSV to clipboard!');
547
+ }
548
+
549
+ // Copy as TSV format
550
+ function copyAsTSV() {
551
+ let tsvContent = 'Start Time\tEnd Time\tOriginal Text\tTranslated Text\n';
552
+ savedCaptions.forEach(caption => {
553
+ tsvContent += `${formatTime(caption.start)}\t${formatTime(caption.end)}\t${caption.originalText}\t${caption.translatedText}\n`;
554
+ });
555
+ navigator.clipboard.writeText(tsvContent);
556
+ alert('Copied as TSV to clipboard!');
557
+ }
558
+
559
+ // Clear all saved captions
560
+ function clearSavedCaptions() {
561
+ savedCaptions = [];
562
+ updateSavedCaptionsDisplay();
563
+ }
564
+
565
+ // Modify the caption click handler in displayMergedCaptions()
566
+ function displayMergedCaptions() {
567
+ mergedCaptions.innerHTML = '';
568
+ const selectedLanguage = languageSelect.value;
569
+ const translations = translatedCaptionData[selectedLanguage] || captions;
570
+
571
+ captions.forEach((caption, index) => {
572
+ const captionElement = document.createElement('div');
573
+ captionElement.className = 'mb-1 p-1 rounded hover:bg-gray-100 transition cursor-pointer border-b border-gray-200';
574
+ captionElement.dataset.start = caption.start;
575
+ captionElement.dataset.end = caption.end;
576
+
577
+ // Original caption
578
+ const originalText = document.createElement('div');
579
+ originalText.className = 'text-gray-700 mb-0.5';
580
+ originalText.textContent = caption.text;
581
+
582
+ // Translated caption
583
+ const translatedText = document.createElement('div');
584
+ translatedText.className = 'text-gray-500 italic';
585
+ translatedText.textContent = translations[index]?.text || caption.text;
586
+
587
+ captionElement.appendChild(originalText);
588
+ captionElement.appendChild(translatedText);
589
+
590
+ // Click on caption to seek to that time and save it
591
+ captionElement.addEventListener('click', () => {
592
+ if (player) {
593
+ player.seekTo(caption.start);
594
+ }
595
+ // Toggle highlight on the clicked caption
596
+ if (captionElement.classList.contains('bg-yellow-100')) {
597
+ captionElement.classList.remove('bg-yellow-100', 'border-yellow-300');
598
+ } else {
599
+ captionElement.classList.add('bg-yellow-100', 'border-yellow-300');
600
+ }
601
+
602
+ // Save the caption
603
+ saveCaption(captionElement);
604
+ });
605
+
606
+ mergedCaptions.appendChild(captionElement);
607
+ });
608
+ }
609
+
610
+ // Initialize the page
611
+ init();
612
+ });
613
+ </script>
614
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=dimchr/captions" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
615
+ </html>