597
文章
·
125087
阅读
597
文章
·
125087
阅读

有130人阅读过 一个M3U直播源合并和分组整理工具
发布于2025/07/23 更新于2025/07/23
[ 教程仅保证更新时有效,请自行测试。]

可以把多个M3U文件合并为一个,对分组名称进行修改,整理,输出为合并后的文件。

文件下载:M3U.rar

文件是一个单一的html文件,可本地浏览器打开使用。

在线效果:M3U分组整理

image.png

image.png

image.png

image.png

网页代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>M3U分组整理</title>
    <style>
        :root {
            --primary-color: #3498db;
            --primary-hover: #2980b9;
            --success-color: #2ecc71;
            --success-hover: #27ae60;
            --danger-color: #e74c3c;
            --danger-hover: #c0392b;
            --warning-color: #f39c12;
            --warning-hover: #d35400;
            --text-color: #333;
            --light-text: #7f8c8d;
            --bg-color: #f5f5f5;
            --card-bg: #fff;
            --border-color: #e0e0e0;
            --highlight-bg: #f0f8ff;
            --shadow: 0 4px 12px rgba(0,0,0,0.08);
        }
        
        body {
            font-family: 'Segoe UI', 'Microsoft YaHei', system-ui, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
            color: var(--text-color);
            background-color: var(--bg-color);
        }
        
        h1 {
            text-align: center;
            color: #2c3e50;
            margin-bottom: 30px;
            font-weight: 600;
            font-size: 2rem;
        }
        
        .container {
            background-color: var(--card-bg);
            padding: 30px;
            border-radius: 12px;
            box-shadow: var(--shadow);
            transition: all 0.3s ease;
        }
        
        .section-title {
            display: block;
            margin: 25px 0 15px;
            color: var(--primary-color);
            font-weight: 600;
            font-size: 1.1rem;
        }
        
        .section-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin: 25px 0 15px;
        }
        
        .section-title-with-button {
            display: flex;
            align-items: center;
            color: var(--primary-color);
            font-weight: 600;
            font-size: 1.1rem;
        }
        
        textarea {
            min-width: 90%;
            height: 200px;
            margin-bottom: 20px;
            padding: 15px;
            border: 1px solid var(--border-color);
            border-radius: 8px;
            font-size: 14px;
            resize: vertical;
            transition: border 0.3s;
            font-family: Consolas, Monaco, monospace;
        }
        
        textarea:focus {
            border-color: var(--primary-color);
            outline: none;
            box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
        }
        
        .btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background-color: var(--primary-color);
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 15px;
            font-weight: 500;
            margin-right: 12px;
            margin-bottom: 12px;
            transition: all 0.3s;
            min-width: 120px;
line-height: 1.2;
        }
        
        .btn:hover {
            background-color: var(--primary-hover);
            transform: translateY(-1px);
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }
        
        .btn:active {
            transform: translateY(0);
        }
        
        .btn i {
            margin-right: 8px;
        }
        
        .btn-sm {
            padding: 8px 15px;
            font-size: 14px;
            min-width: auto;
        }
        
        .btn-success {
            background-color: var(--success-color);
        }
        
        .btn-success:hover {
            background-color: var(--success-hover);
        }
        
        .btn-danger {
            background-color: var(--danger-color);
        }
        
        .btn-danger:hover {
            background-color: var(--danger-hover);
        }
        
        .btn-warning {
            background-color: var(--warning-color);
        }
        
        .btn-warning:hover {
            background-color: var(--warning-hover);
        }
        
        .editor-container {
            margin-top: 30px;
            display: none;
            animation: fadeIn 0.5s ease;
        }
        
        .group-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 20px;
            margin: 25px 0;
        }
        
        .group-item {
            padding: 18px;
            border: 1px solid var(--border-color);
            border-radius: 8px;
            background-color: #f9f9f9;
            transition: all 0.3s;
        }
        
        .group-item:hover {
            border-color: var(--primary-color);
            box-shadow: 0 2px 8px rgba(0,0,0,0.05);
        }
        
        .group-item input {
            padding: 10px 12px;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            font-size: 14px;
            transition: all 0.3s;
        }
        
        .group-item input:focus {
            border-color: var(--primary-color);
            box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
            outline: none;
        }
        
        .original-value {
            color: var(--light-text);
            font-size: 13px;
            margin-top: 8px;
            font-style: italic;
        }
        
        .actions {
            margin-top: 30px;
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 15px;
        }
        
#result {
margin-top: 30px;
padding: 25px;
background-color: var(--highlight-bg);
border: 1px solid #d6e9ff;
border-radius: 8px;
display: none;
font-family: Consolas, Monaco, monospace;
font-size: 13px;
line-height: 1.5;
word-break: break-all;
overflow-wrap: break-word;
}

#result pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
}

        .stats {
            margin: 15px;
            padding: 15px;
            background-color: #e8f4f8;
            border-radius: 8px;
            font-size: 15px;
            text-align: center;
        }
        
        .stats strong {
            color: var(--primary-color);
            font-weight: 600;
        }
        
        .file-input-wrapper {
            position: relative;
            display: inline-block;
            margin-bottom: 20px;
        }
        
        .file-input-wrapper input[type="file"] {
            position: absolute;
            left: 0;
            top: 0;
            opacity: 0;
            width: 100%;
            height: 100%;
            cursor: pointer;
        }
        
        .file-input-label {
            display: inline-flex;
            align-items: center;
            padding: 12px 20px;
            background-color: var(--warning-color);
            color: white;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.3s;
        }
        
        .file-input-label:hover {
            background-color: var(--warning-hover);
            transform: translateY(-1px);
        }
        
        .file-input-label i {
            margin-right: 8px;
        }
        
        .file-name {
            margin-left: 15px;
            color: var(--light-text);
            font-size: 14px;
        }
        
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        @media (max-width: 768px) {
            .container {
                padding: 20px;
            }
            
            .group-grid {
                grid-template-columns: 1fr;
            }
            
            .actions {
                flex-direction: column;
                align-items: center;
            }
            
            .btn {
                width: 100%;
                margin-right: 0;
            }
            
            .section-header {
                flex-direction: column;
                align-items: flex-start;
                gap: 10px;
            }
        }
    </style>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
    <div>
        <h1><i class="fas fa-tv"></i> M3U融合</h1>

        <div>
            <button>
                <i class="fas fa-upload"></i> 选择文件(支持同时上传多个文件进行合并)
                <span id="fileName">未选择文件</span>
            </button>
            <input type="file" id="fileInput" accept=".m3u,.txt" multiple>
        </div>

        <textarea id="m3uContent" placeholder="将M3U文件内容粘贴到这里..."></textarea>
        
        <div style="text-align: center;">
            <button id="parseBtn">
                <i class="fas fa-search"></i> 解析分组标题
            </button>
        </div>

        <div id="editorContainer">
            <h2><i class="fas fa-edit"></i> 编辑分组标题</h2>
            <div id="stats"></div>
            <div id="groupsList"></div>

            <div>
                <button id="applyBtn" class="btn btn-success">
                    <i class="fas fa-check"></i> 应用更改
                </button>
                <button id="resetBtn" class="btn btn-danger">
                    <i class="fas fa-undo"></i> 重置所有修改
                </button>
            </div>
        </div>

        <div id="result"></div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function () {
            const m3uContent = document.getElementById('m3uContent');
            const parseBtn = document.getElementById('parseBtn');
            const fileInput = document.getElementById('fileInput');
            const fileName = document.getElementById('fileName');
            const editorContainer = document.getElementById('editorContainer');
            const groupsList = document.getElementById('groupsList');
            const applyBtn = document.getElementById('applyBtn');
            const resetBtn = document.getElementById('resetBtn');
            const resultDiv = document.getElementById('result');
            const statsDiv = document.getElementById('stats');

            let originalContent = '';
            let groupTitles = [];

            fileInput.addEventListener('change', function () {
const files = Array.from(fileInput.files);
if (files.length === 0) {
fileName.textContent = '未选择文件';
return;
}

fileName.textContent = files.map(f => f.name).join(', ');

const readers = files.map(file => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => resolve(e.target.result);
reader.onerror = e => reject(e);
reader.readAsText(file, 'UTF-8');
});
});

Promise.all(readers)
.then(contents => {
const mergedContent = contents.join('\n\n');
m3uContent.value = mergedContent;
originalContent = mergedContent;
parseBtn.click(); // 自动解析
})
.catch(err => {
alert('读取文件失败:' + err.message);
});
});


            parseBtn.addEventListener('click', function () {
                originalContent = m3uContent.value.trim();
                if (!originalContent) {
                    alert('请上传或粘贴 M3U 文件内容');
                    return;
                }

                groupTitles = extractGroupTitles(originalContent);

                if (groupTitles.length === 0) {
                    alert('未找到任何 group-title');
                    return;
                }

                statsDiv.innerHTML = `共找到 <strong>${groupTitles.length}</strong> 个分组标题`;
                displayGroupEditor(groupTitles);
                editorContainer.style.display = 'block';
                
                // 滚动到编辑器
                editorContainer.scrollIntoView({ behavior: 'smooth' });
            });

            applyBtn.addEventListener('click', function () {
                const modifiedContent = applyGroupTitleChanges(originalContent, groupTitles);
                const changesCount = countChanges();
                
                // 创建结果区域的HTML
                const resultHTML = `
                    <div>
                        <div>
                            <i class="fas fa-file-alt"></i> 修改结果预览
                        </div>
                        <button id="resultDownloadBtn">
                            <i class="fas fa-download"></i> 下载M3U文件
                        </button>
                    </div>
                    <div>修改已完成,共更新了 <strong>${changesCount}</strong> 个分组标题</div>
                    <pre>${modifiedContent}</pre>
                `;
                
                resultDiv.innerHTML = resultHTML;
                resultDiv.style.display = 'block';
                m3uContent.value = modifiedContent;
                
                // 为结果区域中的下载按钮添加事件监听
                document.getElementById('resultDownloadBtn').addEventListener('click', function() {
                    const defaultName = fileInput.files[0] ? 
                        fileInput.files[0].name.replace(/\.[^/.]+$/, "") + '_modified.m3u' : 
                        '修改后的播放列表.m3u';
                    downloadFile(defaultName, modifiedContent);
                });
                
                // 滚动到结果
                resultDiv.scrollIntoView({ behavior: 'smooth' });
            });

            resetBtn.addEventListener('click', function () {
                if (confirm('确定要重置所有修改吗?所有编辑将被丢弃。')) {
                    displayGroupEditor(groupTitles);
                    resultDiv.style.display = 'none';
                }
            });

            function extractGroupTitles(content) {
                const lines = content.split('\n');
                const groups = new Set();

                for (let line of lines) {
                    line = line.trim();
                    if (line.startsWith('#EXTINF:-1')) {
                        const match = line.match(/group-title="([^"]+)"/);
                        if (match && match[1]) {
                            groups.add(match[1]);
                        }
                    }
                }

                return Array.from(groups).sort();
            }

            function displayGroupEditor(groups) {
                groupsList.innerHTML = '';

                groups.forEach(group => {
                    const groupItem = document.createElement('div');
                    groupItem.className = 'group-item';

                    const input = document.createElement('input');
                    input.type = 'text';
                    input.value = group;
                    input.dataset.original = group;
                    input.placeholder = '输入新的分组名称';

                    const originalSpan = document.createElement('div');
                    originalSpan.className = 'original-value';
                    originalSpan.textContent = `原始值: ${group}`;

                    groupItem.appendChild(input);
                    groupItem.appendChild(originalSpan);
                    groupsList.appendChild(groupItem);
                });
            }

            function applyGroupTitleChanges(content, groups) {
let modified = content;

const inputs = groupsList.querySelectorAll('input');

inputs.forEach(input => {
const orig = input.dataset.original;
const curr = input.value.trim();
if (orig !== curr && curr !== '') {
const re = new RegExp(`group-title="${orig}"`, 'g');
modified = modified.replace(re, `group-title="${curr}"`);
}
});

// 清理所有以 #EXTM3U 开头的行,只保留第一个
const lines = modified.split('\n');
let extm3uFound = false;
const cleanedLines = [];

for (let line of lines) {
const trimmedLine = line.replace(/^\uFEFF/, '').trim();
if (/^#EXTM3U/i.test(trimmedLine)) {
if (!extm3uFound) {
cleanedLines.push(trimmedLine); // 保留第一个
extm3uFound = true;
}
// 跳过其他
} else {
cleanedLines.push(line);
}
}

return cleanedLines.join('\n');
}

            function countChanges() {
                let count = 0;
                groupsList.querySelectorAll('input').forEach(input => {
                    if (input.value.trim() !== input.dataset.original && input.value.trim() !== '') {
                        count++;
                    }
                });
                return count;
            }

            function downloadFile(filename, content) {
                const element = document.createElement('a');
                element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content));
                element.setAttribute('download', filename);
                element.style.display = 'none';
                document.body.appendChild(element);
                element.click();
                document.body.removeChild(element);
            }
        });
    </script>
</body>
</html>


文章对你有帮助吗?
  • 一般[0]
  • 很赞[0]
  • 没用[0]
  • 垃圾[0]
  • 无语[0]

继续阅读:

扫一扫,手机浏览手机访问本站