(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);
});
}
})();
We use cookiesEssential cookies keep the site running. We also use Google Analytics to understand how our site is used - only with your consent.
Cookie Policy