KEXEL commited on
Commit
295879d
·
verified ·
1 Parent(s): 643d7bb

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +361 -400
index.html CHANGED
@@ -3,478 +3,439 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>MP4 to M3U8 Converter</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
10
- <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
11
  <style>
12
  .dropzone {
13
- border: 2px dashed #6366f1;
14
- border-radius: 0.5rem;
15
  transition: all 0.3s ease;
16
  }
17
  .dropzone.active {
18
  border-color: #10b981;
19
- background-color: rgba(16, 185, 129, 0.05);
20
  }
21
  .progress-bar {
22
- height: 6px;
23
  transition: width 0.3s ease;
24
  }
25
- .fade-in {
26
- animation: fadeIn 0.5s ease-in-out;
 
27
  }
28
- @keyframes fadeIn {
29
- from { opacity: 0; transform: translateY(10px); }
30
- to { opacity: 1; transform: translateY(0); }
 
 
 
 
31
  }
32
  </style>
33
  </head>
34
  <body class="bg-gray-50 min-h-screen">
35
- <div class="container mx-auto px-4 py-12 max-w-4xl">
36
- <div class="text-center mb-12">
37
- <h1 class="text-4xl font-bold text-indigo-600 mb-3">MP4 to M3U8 Converter</h1>
38
- <p class="text-gray-600 text-lg">Convert your MP4 videos to HLS (M3U8) format for adaptive streaming</p>
39
- </div>
40
 
41
- <div class="bg-white rounded-xl shadow-lg overflow-hidden">
42
- <!-- Upload Section -->
43
- <div class="p-8">
44
- <div id="upload-section" class="dropzone p-12 text-center cursor-pointer">
45
- <div class="mx-auto w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mb-4">
46
- <i class="fas fa-cloud-upload-alt text-indigo-500 text-2xl"></i>
47
- </div>
48
- <h3 class="text-xl font-semibold text-gray-800 mb-2">Drag & Drop your MP4 file here</h3>
49
- <p class="text-gray-500 mb-4">or click to browse files</p>
50
- <input type="file" id="file-input" class="hidden" accept="video/mp4">
51
- <button id="browse-btn" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">
52
  Select File
53
  </button>
54
  </div>
 
55
 
56
- <!-- Settings Section -->
57
- <div id="settings-section" class="mt-8 hidden fade-in">
58
- <h3 class="text-lg font-medium text-gray-800 mb-4">Conversion Settings</h3>
59
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
 
60
  <div>
61
- <label class="block text-sm font-medium text-gray-700 mb-1">Quality Preset</label>
62
- <select id="quality-preset" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
63
- <option value="low">Low (480p)</option>
64
- <option value="medium" selected>Medium (720p)</option>
65
- <option value="high">High (1080p)</option>
66
- <option value="ultra">Ultra (4K)</option>
67
- </select>
68
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  <div>
70
- <label class="block text-sm font-medium text-gray-700 mb-1">Segment Duration (seconds)</label>
71
- <input type="number" id="segment-duration" value="10" min="2" max="20" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
72
  </div>
73
  <div>
74
- <label class="block text-sm font-medium text-gray-700 mb-1">Bitrate (kbps)</label>
75
- <input type="number" id="bitrate" value="2500" min="500" max="20000" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
76
  </div>
77
  <div>
78
- <label class="block text-sm font-medium text-gray-700 mb-1">Audio Quality</label>
79
- <select id="audio-quality" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
80
- <option value="low">Low (64kbps)</option>
81
- <option value="medium" selected>Medium (128kbps)</option>
82
- <option value="high">High (256kbps)</option>
 
 
 
83
  </select>
84
  </div>
85
  </div>
86
- <div class="mt-6">
87
- <button id="start-convert-btn" class="w-full py-3 px-4 bg-indigo-600 text-white font-medium rounded-md hover:bg-indigo-700 transition flex items-center justify-center">
88
- <i class="fas fa-cog mr-2 animate-spin hidden" id="convert-spinner"></i>
89
- Start Conversion
90
- </button>
91
- </div>
92
  </div>
93
  </div>
94
 
95
- <!-- Progress Section -->
96
- <div id="progress-section" class="hidden px-8 pb-8 fade-in">
97
- <h3 class="text-lg font-medium text-gray-800 mb-4">Conversion Progress</h3>
98
- <div class="mb-2 flex justify-between">
99
- <span id="status-text" class="text-sm font-medium text-gray-700">Initializing...</span>
100
- <span id="progress-percent" class="text-sm font-medium text-indigo-600">0%</span>
101
  </div>
102
  <div class="w-full bg-gray-200 rounded-full h-2.5">
103
- <div id="progress-bar" class="progress-bar bg-indigo-600 rounded-full" style="width: 0%"></div>
104
- </div>
105
- <div class="mt-4 text-sm text-gray-500" id="time-info">
106
- <span id="time-elapsed">00:00</span> / <span id="time-remaining">--:--</span>
107
  </div>
 
108
  </div>
109
 
110
- <!-- Result Section -->
111
- <div id="result-section" class="hidden p-8 fade-in">
112
- <div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
113
- <div class="flex items-center">
114
- <div class="flex-shrink-0">
115
- <i class="fas fa-check-circle text-green-500 text-2xl"></i>
 
 
 
 
 
 
 
 
 
 
 
 
116
  </div>
117
- <div class="ml-3">
118
- <h3 class="text-lg font-medium text-green-800">Conversion Complete!</h3>
119
- <p class="text-green-600">Your M3U8 playlist and segments are ready to download.</p>
 
120
  </div>
121
  </div>
122
  </div>
123
-
124
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
125
- <div class="border border-gray-200 rounded-lg p-4">
126
- <h4 class="font-medium text-gray-800 mb-2">Output Files</h4>
127
- <ul class="text-sm text-gray-600 space-y-1">
128
- <li><i class="fas fa-file-alt mr-2 text-indigo-500"></i> playlist.m3u8</li>
129
- <li><i class="fas fa-file-video mr-2 text-indigo-500"></i> segment_1.ts</li>
130
- <li><i class="fas fa-file-video mr-2 text-indigo-500"></i> segment_2.ts</li>
131
- <li><i class="fas fa-file-video mr-2 text-indigo-500"></i> segment_3.ts</li>
132
- <li><i class="fas fa-ellipsis-h mr-2 text-indigo-500"></i> ...and more</li>
133
- </ul>
134
- </div>
135
- <div class="border border-gray-200 rounded-lg p-4">
136
- <h4 class="font-medium text-gray-800 mb-2">Conversion Details</h4>
137
- <ul class="text-sm text-gray-600 space-y-1">
138
- <li><i class="fas fa-clock mr-2 text-indigo-500"></i> Duration: <span id="output-duration">--:--</span></li>
139
- <li><i class="fas fa-tachometer-alt mr-2 text-indigo-500"></i> Bitrate: <span id="output-bitrate">---</span> kbps</li>
140
- <li><i class="fas fa-expand mr-2 text-indigo-500"></i> Resolution: <span id="output-resolution">---</span></li>
141
- <li><i class="fas fa-file-archive mr-2 text-indigo-500"></i> Total Size: <span id="output-size">---</span> MB</li>
142
- </ul>
143
- </div>
144
- </div>
145
-
146
- <div class="flex flex-col sm:flex-row gap-3">
147
- <button id="download-btn" class="flex-1 py-2 px-4 bg-indigo-600 text-white font-medium rounded-md hover:bg-indigo-700 transition flex items-center justify-center">
148
- <i class="fas fa-download mr-2"></i> Download All Files (ZIP)
149
- </button>
150
- <button id="copy-m3u8-btn" class="flex-1 py-2 px-4 bg-gray-100 text-gray-700 font-medium rounded-md hover:bg-gray-200 transition flex items-center justify-center">
151
- <i class="fas fa-copy mr-2"></i> Copy M3U8 Content
152
- </button>
153
- <button id="new-conversion-btn" class="flex-1 py-2 px-4 bg-white border border-gray-300 text-gray-700 font-medium rounded-md hover:bg-gray-50 transition flex items-center justify-center">
154
- <i class="fas fa-redo mr-2"></i> New Conversion
155
- </button>
156
- </div>
157
  </div>
158
- </div>
159
 
160
- <!-- Info Section -->
161
- <div class="mt-12 bg-indigo-50 rounded-xl p-6">
162
- <h2 class="text-xl font-semibold text-indigo-800 mb-3">About M3U8 Format</h2>
163
- <div class="text-gray-700 space-y-3">
164
- <p>M3U8 is the file format for HTTP Live Streaming (HLS), a popular adaptive streaming protocol developed by Apple. It breaks videos into small segments (TS files) and provides a playlist (M3U8) that clients can use to stream the content.</p>
165
- <p><strong>Key benefits:</strong></p>
166
- <ul class="list-disc pl-5 space-y-1">
167
- <li>Adaptive bitrate streaming for better quality on different network conditions</li>
168
- <li>Compatible with most modern devices and browsers</li>
169
- <li>Supports encryption and DRM for secure content delivery</li>
170
- <li>Efficient for live streaming and video-on-demand</li>
171
- </ul>
172
- </div>
173
- </div>
174
  </div>
175
 
176
  <script>
177
- // DOM Elements
178
- const fileInput = document.getElementById('file-input');
179
- const browseBtn = document.getElementById('browse-btn');
180
- const uploadSection = document.getElementById('upload-section');
181
- const settingsSection = document.getElementById('settings-section');
182
- const progressSection = document.getElementById('progress-section');
183
- const resultSection = document.getElementById('result-section');
184
- const startConvertBtn = document.getElementById('start-convert-btn');
185
- const convertSpinner = document.getElementById('convert-spinner');
186
- const progressBar = document.getElementById('progress-bar');
187
- const progressPercent = document.getElementById('progress-percent');
188
- const statusText = document.getElementById('status-text');
189
- const timeElapsed = document.getElementById('time-elapsed');
190
- const timeRemaining = document.getElementById('time-remaining');
191
- const downloadBtn = document.getElementById('download-btn');
192
- const copyM3u8Btn = document.getElementById('copy-m3u8-btn');
193
- const newConversionBtn = document.getElementById('new-conversion-btn');
194
- const outputDuration = document.getElementById('output-duration');
195
- const outputBitrate = document.getElementById('output-bitrate');
196
- const outputResolution = document.getElementById('output-resolution');
197
- const outputSize = document.getElementById('output-size');
 
198
 
199
- // Variables
200
- let selectedFile = null;
201
- let conversionInProgress = false;
202
- let outputFileName = '';
 
 
 
 
 
 
 
203
 
204
- // Event Listeners
205
- browseBtn.addEventListener('click', () => fileInput.click());
206
- fileInput.addEventListener('change', handleFileSelect);
207
- uploadSection.addEventListener('dragover', handleDragOver);
208
- uploadSection.addEventListener('dragleave', handleDragLeave);
209
- uploadSection.addEventListener('drop', handleDrop);
210
- startConvertBtn.addEventListener('click', startConversion);
211
- newConversionBtn.addEventListener('click', resetConverter);
212
- downloadBtn.addEventListener('click', downloadZipFile);
213
- copyM3u8Btn.addEventListener('click', copyM3u8Content);
214
 
215
- // Functions
216
- function handleFileSelect(e) {
217
- const file = e.target.files[0];
218
- if (file && file.type === 'video/mp4') {
219
- processSelectedFile(file);
220
- } else {
221
- showError('Please select a valid MP4 video file.');
222
- }
223
- }
224
 
225
- function handleDragOver(e) {
226
- e.preventDefault();
227
- uploadSection.classList.add('active');
228
- }
229
 
230
- function handleDragLeave() {
231
- uploadSection.classList.remove('active');
232
- }
 
 
233
 
234
- function handleDrop(e) {
235
- e.preventDefault();
236
- uploadSection.classList.remove('active');
237
-
238
- const file = e.dataTransfer.files[0];
239
- if (file && file.type === 'video/mp4') {
240
- processSelectedFile(file);
241
- } else {
242
- showError('Please drop a valid MP4 video file.');
243
- }
244
- }
245
 
246
- function processSelectedFile(file) {
247
- selectedFile = file;
248
- outputFileName = file.name.replace(/\.[^/.]+$/, ""); // Remove extension
249
-
250
- // Show file info
251
- uploadSection.innerHTML = `
252
- <div class="flex items-center justify-center">
253
- <div class="mr-4 p-2 bg-indigo-100 rounded-lg">
254
- <i class="fas fa-file-video text-indigo-600 text-2xl"></i>
255
- </div>
256
- <div class="text-left">
257
- <h4 class="font-medium text-gray-800">${file.name}</h4>
258
- <p class="text-sm text-gray-500">${formatFileSize(file.size)} • ${file.type}</p>
259
- </div>
260
- </div>
261
- <button id="change-file-btn" class="mt-4 px-4 py-1 text-sm text-indigo-600 hover:text-indigo-800 transition">
262
- Change File
263
- </button>
264
- `;
265
-
266
- document.getElementById('change-file-btn').addEventListener('click', resetFileSelection);
267
-
268
- // Show settings section
269
- settingsSection.classList.remove('hidden');
270
- }
271
 
272
- function resetFileSelection() {
273
- selectedFile = null;
274
- outputFileName = '';
275
- fileInput.value = '';
276
- uploadSection.innerHTML = `
277
- <div class="mx-auto w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mb-4">
278
- <i class="fas fa-cloud-upload-alt text-indigo-500 text-2xl"></i>
279
- </div>
280
- <h3 class="text-xl font-semibold text-gray-800 mb-2">Drag & Drop your MP4 file here</h3>
281
- <p class="text-gray-500 mb-4">or click to browse files</p>
282
- <button id="browse-btn" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">
283
- Select File
284
- </button>
285
- `;
286
-
287
- document.getElementById('browse-btn').addEventListener('click', () => fileInput.click());
288
- settingsSection.classList.add('hidden');
289
- progressSection.classList.add('hidden');
290
- resultSection.classList.add('hidden');
291
- }
292
 
293
- function startConversion() {
294
- if (!selectedFile || conversionInProgress) return;
295
-
296
- conversionInProgress = true;
297
- convertSpinner.classList.remove('hidden');
298
- startConvertBtn.disabled = true;
299
-
300
- // Hide settings, show progress
301
- settingsSection.classList.add('hidden');
302
- progressSection.classList.remove('hidden');
303
-
304
- // Get conversion settings
305
- const qualityPreset = document.getElementById('quality-preset').value;
306
- const segmentDuration = document.getElementById('segment-duration').value;
307
- const bitrate = document.getElementById('bitrate').value;
308
- const audioQuality = document.getElementById('audio-quality').value;
309
-
310
- // Simulate conversion progress (in a real app, this would be actual conversion)
311
- let progress = 0;
312
- const totalSteps = 100;
313
- const conversionTime = Math.max(5000, selectedFile.size / 500000); // Simulate time based on file size
314
-
315
- const interval = setInterval(() => {
316
- progress += 1;
317
- const percent = Math.min(progress, 100);
318
-
319
- updateProgress(percent, conversionTime, totalSteps);
320
-
321
- if (progress >= totalSteps) {
322
- clearInterval(interval);
323
- conversionComplete();
324
- }
325
- }, conversionTime / totalSteps);
326
- }
327
 
328
- function updateProgress(percent, totalTime, totalSteps) {
329
- progressBar.style.width = `${percent}%`;
330
- progressPercent.textContent = `${percent}%`;
331
-
332
- const elapsedTime = Math.floor((percent / 100) * totalTime / 1000);
333
- const remainingTime = Math.floor((totalTime / 1000) - elapsedTime);
334
-
335
- timeElapsed.textContent = formatTime(elapsedTime);
336
- timeRemaining.textContent = formatTime(remainingTime);
337
-
338
- // Update status text based on progress
339
- if (percent < 20) {
340
- statusText.textContent = "Initializing conversion...";
341
- } else if (percent < 50) {
342
- statusText.textContent = "Encoding video segments...";
343
- } else if (percent < 80) {
344
- statusText.textContent = "Processing audio tracks...";
345
- } else {
346
- statusText.textContent = "Finalizing playlist...";
347
  }
348
- }
349
 
350
- function conversionComplete() {
351
- conversionInProgress = false;
352
- convertSpinner.classList.add('hidden');
353
- startConvertBtn.disabled = false;
354
-
355
- // Hide progress, show result
356
- progressSection.classList.add('hidden');
357
- resultSection.classList.remove('hidden');
358
-
359
- // Set output details (simulated)
360
- const duration = Math.floor(Math.random() * 300) + 60; // 1-6 minutes
361
- const bitrate = document.getElementById('bitrate').value;
362
- const quality = document.getElementById('quality-preset').value;
363
-
364
- outputDuration.textContent = formatTime(duration);
365
- outputBitrate.textContent = bitrate;
366
-
367
- let resolution = "";
368
- switch(quality) {
369
- case "low": resolution = "854×480"; break;
370
- case "medium": resolution = "1280×720"; break;
371
- case "high": resolution = "1920×1080"; break;
372
- case "ultra": resolution = "3840×2160"; break;
373
  }
374
- outputResolution.textContent = resolution;
375
-
376
- const sizeMB = Math.floor(selectedFile.size / (1024 * 1024) * 0.8); // Simulate 20% compression
377
- outputSize.textContent = sizeMB;
378
- }
379
 
380
- function downloadZipFile() {
381
- if (!outputFileName) return;
382
-
383
- // Create a loading state for the download button
384
- const originalText = downloadBtn.innerHTML;
385
- downloadBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Preparing download...';
386
- downloadBtn.disabled = true;
387
-
388
- // Create a new JSZip instance
389
- const zip = new JSZip();
390
-
391
- // Generate M3U8 playlist content
392
- const m3u8Content = generateM3U8Content();
393
-
394
- // Add the M3U8 file to the ZIP
395
- zip.file(`${outputFileName}.m3u8`, m3u8Content);
396
-
397
- // Generate and add TS segment files (simulated)
398
- const segmentCount = Math.floor(Math.random() * 10) + 3; // 3-12 segments
399
- for (let i = 1; i <= segmentCount; i++) {
400
- // Create a simulated TS file content (in a real app, this would be actual video segments)
401
- const tsContent = `Simulated TS segment file #${i} for ${outputFileName}`;
402
- zip.file(`segment_${i}.ts`, tsContent);
403
- }
404
-
405
- // Generate the ZIP file
406
- zip.generateAsync({ type: 'blob' }).then(function(content) {
407
- // Save the ZIP file
408
- saveAs(content, `${outputFileName}_hls.zip`);
409
 
410
- // Restore the download button
411
- downloadBtn.innerHTML = originalText;
412
- downloadBtn.disabled = false;
 
 
 
 
 
 
 
 
 
 
 
413
  });
414
- }
415
 
416
- function generateM3U8Content() {
417
- const segmentDuration = document.getElementById('segment-duration').value;
418
- const bitrate = document.getElementById('bitrate').value;
419
- const audioQuality = document.getElementById('audio-quality').value;
420
-
421
- let m3u8Content = `#EXTM3U
422
- #EXT-X-VERSION:3
423
- #EXT-X-TARGETDURATION:${segmentDuration}
424
- #EXT-X-MEDIA-SEQUENCE:0
425
- #EXT-X-PLAYLIST-TYPE:VOD
426
- #EXT-X-INDEPENDENT-SEGMENTS\n`;
427
-
428
- // Add video quality info
429
- m3u8Content += `#EXT-X-STREAM-INF:BANDWIDTH=${bitrate}000,CODECS="avc1.64001f,mp4a.40.2",RESOLUTION=1280x720\n`;
430
-
431
- // Add segments
432
- const segmentCount = Math.floor(Math.random() * 10) + 3; // 3-12 segments
433
- for (let i = 1; i <= segmentCount; i++) {
434
- const duration = i === segmentCount ? (segmentDuration / 2).toFixed(1) : segmentDuration;
435
- m3u8Content += `#EXTINF:${duration},\nsegment_${i}.ts\n`;
436
  }
437
-
438
- m3u8Content += '#EXT-X-ENDLIST';
439
-
440
- return m3u8Content;
441
- }
442
 
443
- function copyM3u8Content() {
444
- const m3u8Content = generateM3U8Content();
445
-
446
- navigator.clipboard.writeText(m3u8Content).then(() => {
447
- const originalText = copyM3u8Btn.innerHTML;
448
- copyM3u8Btn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!';
449
- setTimeout(() => {
450
- copyM3u8Btn.innerHTML = originalText;
451
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  });
453
- }
454
 
455
- function resetConverter() {
456
- resetFileSelection();
457
- resultSection.classList.add('hidden');
458
- }
459
 
460
- function showError(message) {
461
- alert(message); // In a real app, you'd show a nicer error message
462
- }
 
 
 
 
 
 
 
463
 
464
- // Helper functions
465
- function formatFileSize(bytes) {
466
- if (bytes === 0) return '0 Bytes';
467
- const k = 1024;
468
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
469
- const i = Math.floor(Math.log(bytes) / Math.log(k));
470
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
471
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
 
473
- function formatTime(seconds) {
474
- const mins = Math.floor(seconds / 60);
475
- const secs = seconds % 60;
476
- return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
477
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
  </script>
479
  </body>
480
- </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>MP4 to TS Converter | Real Video Conversion</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <script src="https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script>
 
10
  <style>
11
  .dropzone {
12
+ border: 2px dashed #3b82f6;
 
13
  transition: all 0.3s ease;
14
  }
15
  .dropzone.active {
16
  border-color: #10b981;
17
+ background-color: #f0fdf4;
18
  }
19
  .progress-bar {
 
20
  transition: width 0.3s ease;
21
  }
22
+ #videoPreview {
23
+ max-width: 100%;
24
+ max-height: 200px;
25
  }
26
+ .settings-panel {
27
+ transition: all 0.3s ease;
28
+ max-height: 0;
29
+ overflow: hidden;
30
+ }
31
+ .settings-panel.open {
32
+ max-height: 500px;
33
  }
34
  </style>
35
  </head>
36
  <body class="bg-gray-50 min-h-screen">
37
+ <div class="container mx-auto px-4 py-8">
38
+ <header class="text-center mb-12">
39
+ <h1 class="text-4xl font-bold text-blue-600 mb-2">MP4 to TS Converter</h1>
40
+ <p class="text-gray-600 max-w-2xl mx-auto">Convert your MP4 videos to TS (MPEG Transport Stream) format using browser-based FFmpeg</p>
41
+ </header>
42
 
43
+ <main class="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6">
44
+ <!-- File Drop Zone -->
45
+ <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6">
46
+ <div class="flex flex-col items-center justify-center">
47
+ <i class="fas fa-file-video text-5xl text-blue-500 mb-4"></i>
48
+ <h2 class="text-xl font-semibold text-gray-700 mb-2">Drag & Drop MP4 File Here</h2>
49
+ <p class="text-gray-500 mb-4">or click to browse your files</p>
50
+ <input type="file" id="fileInput" class="hidden" accept="video/mp4">
51
+ <button id="browseBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition">
 
 
52
  Select File
53
  </button>
54
  </div>
55
+ </div>
56
 
57
+ <!-- File Info -->
58
+ <div id="fileInfo" class="hidden mb-6">
59
+ <div class="flex items-center justify-between bg-gray-50 p-4 rounded-lg">
60
+ <div class="flex items-center">
61
+ <i class="fas fa-file-video text-2xl text-blue-500 mr-3"></i>
62
  <div>
63
+ <h3 id="fileName" class="font-medium text-gray-800"></h3>
64
+ <p id="fileSize" class="text-sm text-gray-500"></p>
 
 
 
 
 
65
  </div>
66
+ </div>
67
+ <button id="removeFile" class="text-red-500 hover:text-red-700">
68
+ <i class="fas fa-times"></i>
69
+ </button>
70
+ </div>
71
+ </div>
72
+
73
+ <!-- Video Preview -->
74
+ <div id="videoPreviewContainer" class="hidden mb-6">
75
+ <h3 class="text-lg font-medium text-gray-700 mb-3">Video Preview</h3>
76
+ <div class="flex justify-center bg-gray-100 rounded-lg p-4">
77
+ <video id="videoPreview" controls class="rounded"></video>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- Conversion Settings -->
82
+ <div class="mb-6">
83
+ <div class="flex items-center justify-between cursor-pointer" id="settingsToggle">
84
+ <h3 class="text-lg font-medium text-gray-700">Conversion Settings</h3>
85
+ <i class="fas fa-chevron-down text-gray-500 transition-transform" id="settingsIcon"></i>
86
+ </div>
87
+ <div class="settings-panel mt-3" id="settingsPanel">
88
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
89
  <div>
90
+ <label class="block text-sm font-medium text-gray-700 mb-1">Video Bitrate (kbps)</label>
91
+ <input type="number" id="videoBitrate" value="2000" min="500" max="20000" class="w-full p-2 border border-gray-300 rounded">
92
  </div>
93
  <div>
94
+ <label class="block text-sm font-medium text-gray-700 mb-1">Audio Bitrate (kbps)</label>
95
+ <input type="number" id="audioBitrate" value="128" min="64" max="320" class="w-full p-2 border border-gray-300 rounded">
96
  </div>
97
  <div>
98
+ <label class="block text-sm font-medium text-gray-700 mb-1">Segment Duration (seconds)</label>
99
+ <input type="number" id="segmentDuration" value="10" min="2" max="60" class="w-full p-2 border border-gray-300 rounded">
100
+ </div>
101
+ <div>
102
+ <label class="block text-sm font-medium text-gray-700 mb-1">Output Format</label>
103
+ <select id="outputFormat" class="w-full p-2 border border-gray-300 rounded">
104
+ <option value="ts">TS (Transport Stream)</option>
105
+ <option value="m3u8">HLS (M3U8 Playlist)</option>
106
  </select>
107
  </div>
108
  </div>
 
 
 
 
 
 
109
  </div>
110
  </div>
111
 
112
+ <!-- Progress Bar -->
113
+ <div id="progressContainer" class="hidden mb-6">
114
+ <div class="flex justify-between mb-1">
115
+ <span class="text-sm font-medium text-gray-700">Conversion Progress</span>
116
+ <span id="progressPercent" class="text-sm font-medium text-gray-700">0%</span>
 
117
  </div>
118
  <div class="w-full bg-gray-200 rounded-full h-2.5">
119
+ <div id="progressBar" class="progress-bar bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
 
 
 
120
  </div>
121
+ <div id="statusMessage" class="text-sm text-gray-500 mt-2"></div>
122
  </div>
123
 
124
+ <!-- Convert Button -->
125
+ <div class="text-center">
126
+ <button id="convertBtn" class="bg-green-500 hover:bg-green-600 text-white font-medium py-3 px-8 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed" disabled>
127
+ <i class="fas fa-cog animate-spin hidden mr-2" id="convertSpinner"></i>
128
+ Convert to TS
129
+ </button>
130
+ </div>
131
+
132
+ <!-- Download Section -->
133
+ <div id="downloadSection" class="hidden mt-8">
134
+ <div class="bg-green-50 border border-green-200 rounded-lg p-4">
135
+ <div class="flex items-center justify-between">
136
+ <div class="flex items-center">
137
+ <i class="fas fa-check-circle text-2xl text-green-500 mr-3"></i>
138
+ <div>
139
+ <h3 class="font-medium text-green-800">Conversion Complete!</h3>
140
+ <p class="text-sm text-green-600">Your file is ready to download</p>
141
+ </div>
142
  </div>
143
+ <div>
144
+ <button id="downloadBtn" class="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-6 rounded-lg transition">
145
+ <i class="fas fa-download mr-2"></i> Download
146
+ </button>
147
  </div>
148
  </div>
149
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  </div>
151
+ </main>
152
 
153
+ <footer class="mt-12 text-center text-gray-500 text-sm">
154
+ <p>MP4 to TS Converter - Uses FFmpeg.js for in-browser conversion. No files are uploaded to any server.</p>
155
+ <p class="mt-2">Note: First conversion may take longer as FFmpeg needs to load (~30MB).</p>
156
+ <p class="mt-2">© 2023 Video Converter Tool. All rights reserved.</p>
157
+ </footer>
 
 
 
 
 
 
 
 
 
158
  </div>
159
 
160
  <script>
161
+ document.addEventListener('DOMContentLoaded', function() {
162
+ // Elements
163
+ const dropzone = document.getElementById('dropzone');
164
+ const fileInput = document.getElementById('fileInput');
165
+ const browseBtn = document.getElementById('browseBtn');
166
+ const fileInfo = document.getElementById('fileInfo');
167
+ const fileName = document.getElementById('fileName');
168
+ const fileSize = document.getElementById('fileSize');
169
+ const removeFile = document.getElementById('removeFile');
170
+ const videoPreviewContainer = document.getElementById('videoPreviewContainer');
171
+ const videoPreview = document.getElementById('videoPreview');
172
+ const convertBtn = document.getElementById('convertBtn');
173
+ const convertSpinner = document.getElementById('convertSpinner');
174
+ const progressContainer = document.getElementById('progressContainer');
175
+ const progressBar = document.getElementById('progressBar');
176
+ const progressPercent = document.getElementById('progressPercent');
177
+ const statusMessage = document.getElementById('statusMessage');
178
+ const downloadSection = document.getElementById('downloadSection');
179
+ const downloadBtn = document.getElementById('downloadBtn');
180
+ const settingsToggle = document.getElementById('settingsToggle');
181
+ const settingsPanel = document.getElementById('settingsPanel');
182
+ const settingsIcon = document.getElementById('settingsIcon');
183
 
184
+ // FFmpeg setup
185
+ const { createFFmpeg, fetchFile } = FFmpeg;
186
+ const ffmpeg = createFFmpeg({
187
+ log: true,
188
+ progress: ({ ratio }) => {
189
+ // 0 to 1 => 0 to 100
190
+ const percent = Math.round(ratio * 100);
191
+ updateProgress(percent);
192
+ statusMessage.textContent = `Processing: ${percent}% complete`;
193
+ }
194
+ });
195
 
196
+ let selectedFile = null;
197
+ let convertedBlob = null;
 
 
 
 
 
 
 
 
198
 
199
+ // Toggle settings panel
200
+ settingsToggle.addEventListener('click', function() {
201
+ settingsPanel.classList.toggle('open');
202
+ settingsIcon.classList.toggle('transform');
203
+ settingsIcon.classList.toggle('rotate-180');
204
+ });
 
 
 
205
 
206
+ // Handle file selection via browse button
207
+ browseBtn.addEventListener('click', function() {
208
+ fileInput.click();
209
+ });
210
 
211
+ fileInput.addEventListener('change', function(e) {
212
+ if (e.target.files.length > 0) {
213
+ handleFileSelection(e.target.files[0]);
214
+ }
215
+ });
216
 
217
+ // Handle drag and drop
218
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
219
+ dropzone.addEventListener(eventName, preventDefaults, false);
220
+ });
 
 
 
 
 
 
 
221
 
222
+ function preventDefaults(e) {
223
+ e.preventDefault();
224
+ e.stopPropagation();
225
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ ['dragenter', 'dragover'].forEach(eventName => {
228
+ dropzone.addEventListener(eventName, highlight, false);
229
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
+ ['dragleave', 'drop'].forEach(eventName => {
232
+ dropzone.addEventListener(eventName, unhighlight, false);
233
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
+ function highlight() {
236
+ dropzone.classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  }
 
238
 
239
+ function unhighlight() {
240
+ dropzone.classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  }
 
 
 
 
 
242
 
243
+ dropzone.addEventListener('drop', function(e) {
244
+ const dt = e.dataTransfer;
245
+ const file = dt.files[0];
246
+ if (file) {
247
+ handleFileSelection(file);
248
+ }
249
+ });
250
+
251
+ // Handle file selection
252
+ function handleFileSelection(file) {
253
+ if (!file.type.match('video/mp4') && !file.name.match(/\.mp4$/i)) {
254
+ alert('Please select an MP4 video file.');
255
+ return;
256
+ }
257
+
258
+ selectedFile = file;
259
+
260
+ // Display file info
261
+ fileName.textContent = file.name;
262
+ fileSize.textContent = formatFileSize(file.size);
263
+ fileInfo.classList.remove('hidden');
 
 
 
 
 
 
 
 
264
 
265
+ // Enable convert button
266
+ convertBtn.disabled = false;
267
+
268
+ // Show video preview
269
+ videoPreview.src = URL.createObjectURL(file);
270
+ videoPreviewContainer.classList.remove('hidden');
271
+
272
+ // Hide dropzone
273
+ dropzone.classList.add('hidden');
274
+ }
275
+
276
+ // Remove file
277
+ removeFile.addEventListener('click', function() {
278
+ resetConverter();
279
  });
 
280
 
281
+ // Format file size
282
+ function formatFileSize(bytes) {
283
+ if (bytes === 0) return '0 Bytes';
284
+ const k = 1024;
285
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
286
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
287
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  }
 
 
 
 
 
289
 
290
+ // Convert button click
291
+ convertBtn.addEventListener('click', async function() {
292
+ if (!selectedFile) return;
293
+
294
+ // Show progress
295
+ progressContainer.classList.remove('hidden');
296
+ statusMessage.textContent = "Loading FFmpeg...";
297
+
298
+ // Disable convert button and show spinner
299
+ convertBtn.disabled = true;
300
+ convertSpinner.classList.remove('hidden');
301
+
302
+ try {
303
+ // Load FFmpeg if not already loaded
304
+ if (!ffmpeg.isLoaded()) {
305
+ await ffmpeg.load();
306
+ }
307
+
308
+ statusMessage.textContent = "Reading input file...";
309
+
310
+ // Write the file to FFmpeg's virtual file system
311
+ const inputName = 'input.mp4';
312
+ ffmpeg.FS('writeFile', inputName, await fetchFile(selectedFile));
313
+
314
+ // Get conversion settings
315
+ const videoBitrate = document.getElementById('videoBitrate').value;
316
+ const audioBitrate = document.getElementById('audioBitrate').value;
317
+ const segmentDuration = document.getElementById('segmentDuration').value;
318
+ const outputFormat = document.getElementById('outputFormat').value;
319
+
320
+ // Run FFmpeg command
321
+ statusMessage.textContent = "Converting video...";
322
+
323
+ let outputName, command;
324
+
325
+ if (outputFormat === 'm3u8') {
326
+ outputName = 'output.m3u8';
327
+ command = [
328
+ '-i', inputName,
329
+ '-c:v', 'libx264',
330
+ '-b:v', `${videoBitrate}k`,
331
+ '-c:a', 'aac',
332
+ '-b:a', `${audioBitrate}k`,
333
+ '-hls_time', segmentDuration,
334
+ '-hls_playlist_type', 'vod',
335
+ '-f', 'hls',
336
+ outputName
337
+ ];
338
+ } else {
339
+ outputName = 'output.ts';
340
+ command = [
341
+ '-i', inputName,
342
+ '-c:v', 'libx264',
343
+ '-b:v', `${videoBitrate}k`,
344
+ '-c:a', 'aac',
345
+ '-b:a', `${audioBitrate}k`,
346
+ '-f', 'mpegts',
347
+ outputName
348
+ ];
349
+ }
350
+
351
+ await ffmpeg.run(...command);
352
+
353
+ // Read the result
354
+ statusMessage.textContent = "Finalizing output...";
355
+ const data = ffmpeg.FS('readFile', outputName);
356
+
357
+ // Create download blob
358
+ convertedBlob = new Blob([data.buffer], { type: outputFormat === 'm3u8' ? 'application/x-mpegURL' : 'video/mp2t' });
359
+
360
+ // Complete the process
361
+ conversionComplete();
362
+
363
+ } catch (error) {
364
+ console.error('Conversion error:', error);
365
+ statusMessage.textContent = `Error: ${error.message}`;
366
+ convertSpinner.classList.add('hidden');
367
+ convertBtn.disabled = false;
368
+ }
369
  });
 
370
 
371
+ function updateProgress(percent) {
372
+ progressBar.style.width = percent + '%';
373
+ progressPercent.textContent = Math.round(percent) + '%';
374
+ }
375
 
376
+ function conversionComplete() {
377
+ // Hide progress and spinner
378
+ convertSpinner.classList.add('hidden');
379
+
380
+ // Show download section
381
+ downloadSection.classList.remove('hidden');
382
+
383
+ // Update status
384
+ statusMessage.textContent = "Conversion completed successfully!";
385
+ }
386
 
387
+ // Download button
388
+ downloadBtn.addEventListener('click', function() {
389
+ if (!convertedBlob) return;
390
+
391
+ const url = URL.createObjectURL(convertedBlob);
392
+ const a = document.createElement('a');
393
+ a.href = url;
394
+
395
+ const outputFormat = document.getElementById('outputFormat').value;
396
+ const originalName = selectedFile.name.replace(/\.[^/.]+$/, "");
397
+
398
+ if (outputFormat === 'm3u8') {
399
+ a.download = `${originalName}.m3u8`;
400
+ } else {
401
+ a.download = `${originalName}.ts`;
402
+ }
403
+
404
+ document.body.appendChild(a);
405
+ a.click();
406
+ document.body.removeChild(a);
407
+ URL.revokeObjectURL(url);
408
+ });
409
 
410
+ // Reset converter to initial state
411
+ function resetConverter() {
412
+ selectedFile = null;
413
+ convertedBlob = null;
414
+
415
+ // Reset UI
416
+ fileInfo.classList.add('hidden');
417
+ videoPreviewContainer.classList.add('hidden');
418
+ progressContainer.classList.add('hidden');
419
+ downloadSection.classList.add('hidden');
420
+ convertBtn.disabled = true;
421
+ convertSpinner.classList.add('hidden');
422
+ progressBar.style.width = '0%';
423
+ progressPercent.textContent = '0%';
424
+ statusMessage.textContent = '';
425
+
426
+ // Show dropzone again
427
+ dropzone.classList.remove('hidden');
428
+
429
+ // Reset file input
430
+ fileInput.value = '';
431
+
432
+ // Revoke object URLs
433
+ if (videoPreview.src) {
434
+ URL.revokeObjectURL(videoPreview.src);
435
+ videoPreview.src = '';
436
+ }
437
+ }
438
+ });
439
  </script>
440
  </body>
441
+ </html>