Bezpłatne narzędzie - Nie wymaga konta

Darmowe narzędzie do usuwania duplikatów M3U i IPTV

Wyczyść swoją playlistę M3U, M3U8 lub IPTV online, usuwając zduplikowane kanały w kilka sekund. Dopasowanie po nazwie, URL lub obu. Pobierz czysty plik natychmiast.

Przeciągnij i upuść plik M3U tutaj

lub

.m3u i .m3u8 obsługiwane - Przetwarzane lokalnie w przeglądarce - Nigdy nie przesyłane na nasze serwery

100% prywatny

Twój plik nigdy nie opuszcza przeglądarki. Całe przetwarzanie odbywa się lokalnie.

Elastyczne dopasowanie

Dopasowuje duplikaty po adresie URL strumienia, nazwie kanału lub obu jednocześnie. Zachowuje pierwsze wystąpienie.

Natychmiastowe pobieranie

Pobierz oczyszczony plik M3U od razu. Bez czekania, bez e-maila, bez rejestracji.

Chcesz zrobić więcej ze swoją playlistą?

Utwórz bezpłatne konto M3U Maker, aby filtrować kanały i tworzyć niestandardowe playlisty.

(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); } })();