Alat Gratis - Tidak perlu akun

Penganalisis playlist M3U & IPTV gratis

Analisis playlist M3U, M3U8 atau IPTV Anda secara online. Rincian lengkap: jumlah saluran per grup, statistik kualitas (4K/HD/SD), distribusi protokol dan skor kesehatan metadata.

Seret & lepas file M3U Anda di sini

atau

.m3u dan .m3u8 didukung - Diproses secara lokal di browser Anda - Tidak pernah diunggah ke server kami

100% Privat

File Anda tidak pernah meninggalkan browser Anda. Semua pemrosesan berjalan secara lokal - tidak ada yang dikirim ke server kami.

Instan & Detail

Pemecahan kualitas, statistik protokol, skor kesehatan metadata, dan peringkat grup lengkap - semua dalam hitungan detik.

Dukungan File Besar

Menangani playlist kecil maupun besar - diuji pada file dengan lebih dari 50.000 saluran.

Ingin melakukan lebih banyak dengan playlist Anda?

Buat akun M3U Maker gratis untuk memfilter saluran, membuat playlist kustom, dan menjaganya tetap tersinkronisasi otomatis dengan file sumber.

(function() { var zone = document.getElementById('uploadZone'); var fileInput = document.getElementById('fileInput'); var loading = document.getElementById('loading'); var results = document.getElementById('results'); var resetBtn = document.getElementById('resetBtn'); var browseBtn = document.getElementById('browseBtn'); browseBtn.addEventListener('click', function(e) { e.stopPropagation(); fileInput.click(); }); zone.addEventListener('click', function() { fileInput.click(); }); zone.addEventListener('dragover', function(e) { e.preventDefault(); zone.classList.add('drag-over'); }); zone.addEventListener('dragleave', function() { zone.classList.remove('drag-over'); }); zone.addEventListener('drop', function(e) { e.preventDefault(); zone.classList.remove('drag-over'); if (e.dataTransfer.files[0]) processFile(e.dataTransfer.files[0]); }); fileInput.addEventListener('change', function() { if (fileInput.files[0]) processFile(fileInput.files[0]); }); resetBtn.addEventListener('click', function() { results.style.display = 'none'; loading.style.display = 'none'; zone.style.display = ''; fileInput.value = ''; }); function processFile(file) { zone.style.display = 'none'; loading.style.display = ''; document.getElementById('resultsFilename').textContent = file.name; var reader = new FileReader(); reader.onload = function(e) { setTimeout(function() { analyzeM3U(e.target.result); }, 30); }; reader.readAsText(file, 'UTF-8'); } function analyzeM3U(content) { var lines = content.split(/\r?\n/); var channels = 0, groups = {}, noGroup = 0; var quality = { '4K': 0, 'FHD': 0, 'HD': 0, 'SD': 0 }; var protocols = {}; var hasLogo = 0, hasGroup = 0, hasTvgId = 0, hasName = 0; var i = lines[0] && lines[0].trim().startsWith('#EXTM3U') ? 1 : 0; while (i < lines.length) { var line = lines[i].trim(); if (!line) { i++; continue; } if (line.startsWith('#EXTINF:')) { channels++; // Name var nameMatch = line.match(/,(.+)$/); var name = nameMatch ? nameMatch[1].trim() : ''; if (name) hasName++; // Group var groupMatch = line.match(/group-title="([^"]*)"/i); var group = groupMatch ? groupMatch[1].trim() : ''; if (group) { hasGroup++; groups[group] = (groups[group] || 0) + 1; } else { noGroup++; } // Logo if (/tvg-logo="[^"]+"/i.test(line)) hasLogo++; // tvg-id if (/tvg-id="[^"]+"/i.test(line)) hasTvgId++; // Quality from name var uname = name.toUpperCase(); if (/\b(4K|UHD|2160P)\b/.test(uname)) quality['4K']++; else if (/\b(FHD|1080[PI]|FULLHD)\b/.test(uname)) quality['FHD']++; else if (/\b(HD|720[PI])\b/.test(uname)) quality['HD']++; else quality['SD']++; // URL on next line i++; while (i < lines.length && !lines[i].trim()) i++; var url = lines[i] ? lines[i].trim() : ''; if (url && !url.startsWith('#')) { var protoMatch = url.match(/^([a-z][a-z0-9+\-.]*):\/\//i); var proto = protoMatch ? protoMatch[1].toLowerCase() : 'other'; protocols[proto] = (protocols[proto] || 0) + 1; } } i++; } showResults(channels, groups, noGroup, quality, protocols, hasLogo, hasGroup, hasTvgId, hasName); } function pct(val, total) { return total > 0 ? Math.round(val / total * 100) : 0; } function showResults(channels, groups, noGroup, quality, protocols, hasLogo, hasGroup, hasTvgId, hasName) { var groupCount = Object.keys(groups).length + (noGroup > 0 ? 1 : 0); // Summary stats var statsEl = document.getElementById('summaryStats'); statsEl.textContent = ''; [ { val: channels.toLocaleString(), label: 'Total channels' }, { val: groupCount, label: 'Groups' }, { val: pct(quality['4K'] + quality['FHD'] + quality['HD'], channels) + '%', label: 'HD or better' }, { val: pct(hasLogo, channels) + '%', label: 'Has logo' }, { val: pct(hasGroup, channels) + '%', label: 'Has group' } ].forEach(function(s) { var div = document.createElement('div'); div.className = 'tool-stat'; var val = document.createElement('span'); val.className = 'tool-stat-value'; val.textContent = s.val; var lbl = document.createElement('span'); lbl.className = 'tool-stat-label'; lbl.textContent = s.label; div.appendChild(val); div.appendChild(lbl); statsEl.appendChild(div); }); // Quality bars var qualityData = [ { label: '4K / UHD', count: quality['4K'], color: 'var(--accent-secondary)' }, { label: 'Full HD (1080p)', count: quality['FHD'], color: 'var(--accent-primary)' }, { label: 'HD (720p)', count: quality['HD'], color: '#00b894' }, { label: 'SD / Unknown', count: quality['SD'], color: 'var(--text-muted)' } ]; renderBars('qualityBars', qualityData, channels); // Protocol bars var protoData = Object.entries(protocols) .sort(function(a, b) { return b[1] - a[1]; }) .map(function(e) { return { label: e[0], count: e[1], color: 'var(--accent-primary)' }; }); renderBars('protocolBars', protoData, channels); // Metadata bars var metaData = [ { label: 'Has channel name', count: hasName, color: 'var(--accent-primary)' }, { label: 'Has group-title', count: hasGroup, color: 'var(--accent-primary)' }, { label: 'Has tvg-logo', count: hasLogo, color: 'var(--accent-primary)' }, { label: 'Has tvg-id', count: hasTvgId, color: 'var(--accent-primary)' } ]; renderBars('metaBars', metaData, channels); // Groups list var sortedGroups = Object.entries(groups).sort(function(a, b) { return b[1] - a[1]; }); if (noGroup > 0) sortedGroups.push(['(no group)', noGroup]); var maxCount = sortedGroups.length > 0 ? sortedGroups[0][1] : 1; document.getElementById('groupsCount').textContent = groupCount + ' groups'; var groupsList = document.getElementById('groupsList'); groupsList.textContent = ''; var showAll = false; var limit = 15; function renderGroups(all) { groupsList.textContent = ''; var toShow = all ? sortedGroups : sortedGroups.slice(0, limit); toShow.forEach(function(g) { var item = document.createElement('div'); item.className = 'analyzer-group-item'; var label = document.createElement('div'); label.className = 'analyzer-group-label'; var name = document.createElement('span'); name.className = 'analyzer-group-name'; name.textContent = g[0]; var count = document.createElement('span'); count.className = 'analyzer-group-count'; count.textContent = g[1].toLocaleString(); label.appendChild(name); label.appendChild(count); var barWrap = document.createElement('div'); barWrap.className = 'analyzer-group-bar-wrap'; var bar = document.createElement('div'); bar.className = 'analyzer-group-bar'; bar.style.width = '0%'; barWrap.appendChild(bar); setTimeout(function() { bar.style.width = Math.round(g[1] / maxCount * 100) + '%'; }, 50); item.appendChild(label); item.appendChild(barWrap); groupsList.appendChild(item); }); } renderGroups(false); var moreEl = document.getElementById('groupsMore'); if (sortedGroups.length > limit) { moreEl.style.display = ''; var remaining = sortedGroups.length - limit; var btn = document.createElement('button'); btn.className = 'btn btn-ghost btn-sm'; btn.textContent = 'Show all ' + sortedGroups.length + ' groups'; btn.addEventListener('click', function() { renderGroups(true); moreEl.style.display = 'none'; }); moreEl.textContent = ''; moreEl.appendChild(btn); } else { moreEl.style.display = 'none'; } loading.style.display = 'none'; results.style.display = ''; } function renderBars(containerId, data, total) { var container = document.getElementById(containerId); container.textContent = ''; var max = data.reduce(function(m, d) { return Math.max(m, d.count); }, 1); data.forEach(function(d) { if (d.count === 0) return; var item = document.createElement('div'); item.className = 'analyzer-bar-item'; var header = document.createElement('div'); header.className = 'analyzer-bar-header'; var lbl = document.createElement('span'); lbl.className = 'analyzer-bar-label'; lbl.textContent = d.label; var cnt = document.createElement('span'); cnt.className = 'analyzer-bar-count'; cnt.textContent = d.count.toLocaleString() + ' (' + pct(d.count, total) + '%)'; header.appendChild(lbl); header.appendChild(cnt); var barWrap = document.createElement('div'); barWrap.className = 'analyzer-bar-wrap'; var bar = document.createElement('div'); bar.className = 'analyzer-bar-fill'; bar.style.width = '0%'; bar.style.background = d.color; barWrap.appendChild(bar); setTimeout(function(b, w) { b.style.width = w + '%'; }.bind(null, bar, Math.round(d.count / max * 100)), 50); item.appendChild(header); item.appendChild(barWrap); container.appendChild(item); }); } })();