(function() {
var zone = document.getElementById('uploadZone');
var fileInput = document.getElementById('fileInput');
var results = document.getElementById('results');
var resetBtn = document.getElementById('resetBtn');
var browseBtn = document.getElementById('browseBtn');
var reprocessBtn = document.getElementById('reprocessBtn');
var downloadBtn = document.getElementById('downloadBtn');
var matchUrl = document.getElementById('matchUrl');
var matchName = document.getElementById('matchName');
var parsedChannels = [];
var originalHeader = '#EXTM3U';
var currentFilename = '';
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 = '';
parsedChannels = [];
});
reprocessBtn.addEventListener('click', function() { showResults(); });
downloadBtn.addEventListener('click', function() { downloadClean(); });
var loading = document.getElementById('loading');
function processFile(file) {
currentFilename = file.name;
zone.style.display = 'none';
loading.style.display = '';
var reader = new FileReader();
reader.onload = function(e) { parseM3U(e.target.result); };
reader.readAsText(file, 'UTF-8');
}
function parseM3U(content) {
var lines = content.split(/\r?\n/);
parsedChannels = [];
originalHeader = '#EXTM3U';
var i = 0;
if (lines[0] && lines[0].trim().startsWith('#EXTM3U')) {
originalHeader = lines[0].trim();
i = 1;
}
while (i < lines.length) {
var line = lines[i].trim();
if (!line) { i++; continue; }
if (line.startsWith('#EXTINF:')) {
var extinf = line;
var nameMatch = line.match(/,(.+)$/);
var name = nameMatch ? nameMatch[1].trim() : '';
i++;
while (i < lines.length && !lines[i].trim()) i++;
var url = lines[i] ? lines[i].trim() : '';
if (url && !url.startsWith('#')) {
parsedChannels.push({ extinf: extinf, name: name, url: url });
}
}
i++;
}
loading.style.display = 'none';
results.style.display = '';
document.getElementById('resultsFilename').textContent = currentFilename;
showResults();
}
function showResults() {
var byUrl = matchUrl.checked;
var byName = matchName.checked;
if (!byUrl && !byName) { matchUrl.checked = true; byUrl = true; }
var seenUrls = {}, seenNames = {};
var kept = [], dupes = [];
parsedChannels.forEach(function(ch) {
var isDupe = false;
var dupeType = '';
if (byUrl && ch.url && seenUrls[ch.url]) {
isDupe = true; dupeType = 'url';
}
if (byName && ch.name && seenNames[ch.name.toLowerCase()]) {
isDupe = true; dupeType = dupeType ? dupeType + '+name' : 'name';
}
if (isDupe) {
dupes.push({ ch: ch, type: dupeType });
} else {
kept.push(ch);
if (byUrl && ch.url) seenUrls[ch.url] = true;
if (byName && ch.name) seenNames[ch.name.toLowerCase()] = true;
}
});
// Stats
var statsRow = document.getElementById('statsRow');
statsRow.textContent = '';
[
{ val: parsedChannels.length.toLocaleString(), label: 'Total channels', cls: '' },
{ val: dupes.length, label: 'Duplicates found', cls: dupes.length > 0 ? 'tool-stat--warning' : '' },
{ val: kept.length.toLocaleString(), label: 'Unique channels', cls: kept.length > 0 ? 'tool-stat--success' : '' },
{ val: dupes.length > 0 ? Math.round(dupes.length / parsedChannels.length * 100) + '%' : '0%', label: 'Reduction', cls: '' }
].forEach(function(s) {
var div = document.createElement('div');
div.className = 'tool-stat ' + s.cls;
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);
statsRow.appendChild(div);
});
// Dupes list
var dupeSection = document.getElementById('dupeSection');
var nodupeMsg = document.getElementById('nodupeMsg');
var downloadBar = document.getElementById('downloadBar');
if (dupes.length > 0) {
document.getElementById('dupeGroupHeader').textContent = 'Duplicate channels to be removed (' + dupes.length + ')';
var dupeList = document.getElementById('dupeList');
dupeList.textContent = '';
dupes.slice(0, 100).forEach(function(d) {
var item = document.createElement('div');
item.className = 'tool-dupe-item';
var badge = document.createElement('span');
badge.className = 'tool-dupe-badge tool-dupe-badge--' + (d.type === 'url' ? 'url' : 'name');
badge.textContent = d.type === 'url' ? 'URL' : d.type === 'name' ? 'Name' : 'URL+Name';
item.appendChild(badge);
var info = document.createElement('div');
info.className = 'tool-dupe-info';
var nameEl = document.createElement('div');
nameEl.className = 'tool-dupe-name';
nameEl.textContent = d.ch.name || '(no name)';
var urlEl = document.createElement('div');
urlEl.className = 'tool-dupe-url';
urlEl.textContent = d.ch.url;
info.appendChild(nameEl);
info.appendChild(urlEl);
item.appendChild(info);
dupeList.appendChild(item);
});
if (dupes.length > 100) {
var more = document.createElement('div');
more.className = 'tool-issue-more';
more.textContent = '… and ' + (dupes.length - 100) + ' more duplicates';
dupeList.appendChild(more);
}
dupeSection.style.display = '';
nodupeMsg.style.display = 'none';
document.getElementById('downloadInfo').textContent = kept.length.toLocaleString() + ' channels, ' + dupes.length + ' duplicates removed';
downloadBar.style.display = '';
downloadBtn._kept = kept;
} else {
dupeSection.style.display = 'none';
nodupeMsg.style.display = '';
downloadBar.style.display = 'none';
}
}
function downloadClean() {
var kept = downloadBtn._kept;
if (!kept) return;
var lines = [originalHeader];
kept.forEach(function(ch) {
lines.push(ch.extinf);
lines.push(ch.url);
lines.push('');
});
var blob = new Blob([lines.join('\n')], { type: 'audio/x-mpegurl' });
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
var base = currentFilename.replace(/\.[^.]+$/, '');
a.download = base + '-cleaned.m3u';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
}
})();
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