597
文章
·
92038
阅读
597
文章
·
92038
阅读

有124人阅读过 一个TXT和M3U格式互转的工具
发布于2025/07/29 更新于2025/07/29
[ 教程仅保证更新时有效,请自行测试。]

快速实现TXT转M3U,或M3U转TXT格式直播源,支持DIYP分组格式

t2m.rar


在线测试:IPTV-M3U转换工具

image.png

代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>IPTV-M3U转换工具</title>
  <style>
    :root {
      --primary-color: #3498db;
      --secondary-color: #2ecc71;
      --danger-color: #e74c3c;
      --dark-color: #2c3e50;
      --light-color: #ecf0f1;
      --border-radius: 6px;
      --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
      --transition: all 0.3s ease;
    }
    
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: #f5f7fa;
      color: #333;
      line-height: 1.6;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
    }
    
    .header {
      background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
      color: white;
      padding: 1.5rem 0;
      text-align: center;
      box-shadow: var(--box-shadow);
      margin-bottom: 2rem;
    }
    
    .header h1 {
      font-size: 2rem;
      font-weight: 600;
      margin-bottom: 0.5rem;
    }
    
    .container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 0 1.5rem;
      flex: 1;
      width: 100%;
    }
    
    .card {
      background: white;
      border-radius: var(--border-radius);
      box-shadow: var(--box-shadow);
      padding: 1.5rem;
      margin-bottom: 2rem;
    }
    
    .card-title {
      font-size: 1.25rem;
      margin-bottom: 1rem;
      color: var(--dark-color);
      font-weight: 600;
      border-bottom: 1px solid #eee;
      padding-bottom: 0.5rem;
    }
    
    .button-group {
      display: flex;
      gap: 0.75rem;
      flex-wrap: wrap;
      margin-bottom: 1.5rem;
    }
    
    .button {
      padding: 0.75rem 1.25rem;
      border: none;
      border-radius: var(--border-radius);
      font-size: 0.95rem;
      font-weight: 500;
      cursor: pointer;
      transition: var(--transition);
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 0.5rem;
    }
    
    .button-primary {
      background-color: var(--primary-color);
      color: white;
    }
    
    .button-secondary {
      background-color: var(--secondary-color);
      color: white;
    }
    
    .button-danger {
      background-color: var(--danger-color);
      color: white;
    }
    
    .button-outline {
      background-color: transparent;
      border: 1px solid var(--primary-color);
      color: var(--primary-color);
    }
    
    .button:hover {
      transform: translateY(-2px);
      box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
    }
    
    .file-input {
      margin-bottom: 1.5rem;
    }
    
    .file-input label {
      display: block;
      margin-bottom: 0.5rem;
      font-weight: 500;
      color: var(--dark-color);
    }
    
    .file-input input[type="file"] {
      width: 100%;
      padding: 0.75rem;
      border: 1px solid #ddd;
      border-radius: var(--border-radius);
      background-color: white;
    }
    
    .preview-container {
      display: flex;
      flex-wrap: wrap;
      gap: 1.5rem;
      margin-top: 1.5rem;
    }
    
    .preview-column {
      flex: 1;
      min-width: 300px;
    }
    
    .preview-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 0.75rem;
    }
    
    .preview-title {
      font-weight: 600;
      color: var(--dark-color);
    }
    
    .preview-content {
      width: 100%;
      height: 400px;
      padding: 1rem;
      border: 1px solid #ddd;
      border-radius: var(--border-radius);
      background-color: white;
      font-family: 'Courier New', Courier, monospace;
      font-size: 0.9rem;
      resize: none;
      overflow-y: auto;
    }
    
    .footer {
      text-align: center;
      padding: 1.5rem;
      margin-top: 2rem;
      background-color: var(--dark-color);
      color: white;
    }
    
    .footer .copyright {
      font-size: 0.9rem;
    }
    
    @media (max-width: 768px) {
      .preview-container {
        flex-direction: column;
      }
      
      .preview-column {
        width: 100%;
      }
      
      .button-group {
        flex-direction: column;
      }
    }
    
    /* 动画效果 */
    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(10px); }
      to { opacity: 1; transform: translateY(0); }
    }
    
    .card {
      animation: fadeIn 0.5s ease-out;
    }
    
    /* 工具提示 */
    .tooltip {
      position: relative;
      display: inline-block;
    }
    
    .tooltip .tooltiptext {
      visibility: hidden;
      width: 200px;
      background-color: #333;
      color: #fff;
      text-align: center;
      border-radius: 6px;
      padding: 5px;
      position: absolute;
      z-index: 1;
      bottom: 125%;
      left: 50%;
      transform: translateX(-50%);
      opacity: 0;
      transition: opacity 0.3s;
      font-size: 0.8rem;
    }
    
    .tooltip:hover .tooltiptext {
      visibility: visible;
      opacity: 1;
    }
  </style>
</head>
<body>
  <header>
    <h1>IPTV格式转换工具</h1>
    <p>M3U/TXT/DIYP格式互转</p>
  </header>
  
  <div>
    <div>
      <h2>转换操作</h2>
      <div>
        <button class="button button-primary" onclick="convertToM3U()">
          <span>转换为M3U</span>
          <span>
            <span>将TXT/DIYP格式转换为M3U格式</span>
          </span>
        </button>
        <button class="button button-secondary" onclick="convertToDiyp()">
          <span>转换为DIYP(带分组)</span>
          <span>
            <span>将M3U格式转换为DIYP格式</span>
          </span>
        </button>
        <button class="button button-outline" onclick="convertToTXT()">
          <span>转换为TXT(不带分组)</span>
          <span>
            <span>将M3U格式转换为简单TXT格式</span>
          </span>
        </button>
      </div>
      
      <div>
        <label for="fileInput">选择文件 (支持.txt, .m3u, .m3u8)</label>
        <input type="file" id="fileInput" accept=".txt,.m3u,.m3u8" onchange="updatePreview('fileInput', 'previewBefore')">
      </div>
    </div>
    
    <div>
      <div id="container">
        <div>
          <h3>原始内容</h3>
          <button class="button button-danger" onclick="clearBefore()">清空</button>
        </div>
        <textarea id="previewBefore" placeholder="请上传文件或拖拽文件到此处..."></textarea>
      </div>
      
      <div>
        <div>
          <h3>转换结果</h3>
          <div>
            <button class="button button-danger" onclick="clearAfter()">清空</button>
            <button class="button button-primary" onclick="copyContent()">复制</button>
            <button class="button button-secondary" onclick="exportConvertedFile()">导出</button>
          </div>
        </div>
        <textarea id="previewAfter" placeholder="转换结果将显示在这里..."></textarea>
      </div>
    </div>
  </div>
  
  <footer>
    <div>&copy; 2024 群晖直播 Rights Reserved.</div>
  </footer>

  <script>
    // 保持原有的JavaScript功能不变
    function findLineNumber(text, searchText) {  
      var regex = new RegExp(searchText, 'g');  
      var lines = text.split('\n');  
      var lineNumber = 0;  
      
      for (var i = 0; i < lines.length; i++) {  
          if (lines[i].match(regex)) {  
              return lineNumber;  
          }  
          lineNumber++;  
      }  
      
      return -1;  
    }
    
    function TxtToM3U(txtPreview) {
      var m3uContent='';
      if(txtPreview == null || txtPreview=='') return '';
      var lines = txtPreview.split("\n");
      
      if (txtPreview.includes("#EXTM3U") || txtPreview.includes("#EXTINF")) {
        var errline=findLineNumber(txtPreview,"#EXTM3U");
        if(errline==-1){
          errline=findLineNumber(txtPreview,"#EXTINF");
        }
        m3uContent = "检测到"+ (errline+1) + "行包含特定字符EXTM3U或者EXTINF,请检查文本内容,可能已经是m3u格式";
        return m3uContent;
      }
      if(!txtPreview.includes(",")) {
        m3uContent = "文本内容不包含分隔','字符,无法转换";
        return m3uContent;
      }
      m3uContent = "#EXTM3U x-tvg-url=\"http://epg.51zmt.top:8000/e.xml,https://epg.112114.xyz/pp.xml\"\n";
      var tempUrls = [];
      var lastGroupName = '';
      lines.forEach(function(line) {
        if (line.trim() !== "") {
          if (line.includes("#genre#")) {
            var groupName = line.split(",")[0].trim();
            groupName = groupName.replace(/^\s*|\s*$/g, '');
            if (groupName != null && groupName !== "") {
              lastGroupName = groupName;
            }
          } else {
            if(line.includes(",")){
              var channelName = line.split(",")[0].trim();
              var channelUrl = line.split(",")[1].trim();

              if (channelName != '' && channelUrl != '' &&  !tempUrls.includes(channelUrl)) {
                if (lastGroupName !== "") {
                  m3uContent += "#EXTINF:-1 group-title=\"" + lastGroupName + "\"," + channelName + "\n";
                } else {
                  m3uContent += "#EXTINF:-1," + channelName + "\n";
                }
                m3uContent += channelUrl + "\n";
                tempUrls.push(channelUrl);
              }
            }
          }
        }
      });
      return m3uContent;
    }
    
    function convertToM3U() {
      var txtPreview = document.getElementById("previewBefore");
      var m3uPreview = document.getElementById("previewAfter");
      m3uPreview.value=TxtToM3U(txtPreview.value);
      convertedData = m3uPreview.value; 
    }
    
    function M3uToDiyp(m3uPreview) {
      var txtContent = "";
      if(m3uPreview == null || m3uPreview=='') return '';
      var lines = m3uPreview.split("\n");
      if (!m3uPreview.includes("#EXTM3U") && !m3uPreview.includes("#EXTINF")) {
        txtContent = "不是M3U格式,可能已经是DIYP/TXT格式,无法转换";
        return txtContent;
      }
      var lastgroupName = "";
      for (var i = 0; i < lines.length; i++) {
        var line = lines[i].trim();
        if (line.startsWith("#EXTINF")) {
          var groupName = getGroupName(line);
          if (groupName != null && groupName != '' && groupName != lastgroupName) {
            txtContent += groupName + ",#genre#\n";
            lastgroupName = groupName;
          }
          var name='频道名解析错误';
          var match = line.match(/tvg-name=\"([^\"]+)\"/);  
            
          if (match) {  
              name=match[1];
          } else {  
            var count = (line.match(/,/g) || []).length;
            if(count>1){
              name = line.split(",")[count].trim();
            }
            else{
              name = line.split(",")[1].trim();
            }
          }
          
          name=name.replace(/\s*/g, '');
          var url = lines[i + 1].trim();
          if(name!=''&&url!=''){
            txtContent += name + "," + url + "\n";
          }
        }
      }
      if (txtContent != '' && lastgroupName == '') {
        txtContent = "国际,#genre#\n" + txtContent;
      }
      return txtContent;
    }
    
    function convertToDiyp() {
      var m3uPreview = document.getElementById("previewBefore");
      var txtPreview = document.getElementById("previewAfter");
      txtPreview.value = M3uToDiyp(m3uPreview.value);
      convertedData = txtPreview.value;
    }

    function M3uToTXT(m3uPreview) {
      var txtContent = "";
      if(m3uPreview == null || m3uPreview=='') return '';
      var lines = m3uPreview.split("\n");
      if (!m3uPreview.includes("#EXTM3U") && !m3uPreview.includes("#EXTINF")) {
        txtContent = "不是M3U格式,可能已经是DIYP/TXT格式,无法转换";
        return txtContent;
      }

      for (var i = 0; i < lines.length; i++) {
        var line = lines[i].trim();

        if (line.startsWith("#EXTINF")) {
          var name='频道名解析错误';
          var count = (line.match(/,/g) || []).length;
          if(count>1){
            name = line.split(",")[count].trim();
          }
          else{
            name = line.split(",")[1].trim();
          }
          
          var url = lines[i + 1].trim();
          if(name!=''&&url!=''){
            txtContent += name + "," + url + "\n";
          }
        }
      }
      return txtContent;
    }

    function convertToTXT() {
      var m3uPreview = document.getElementById("previewBefore");
      var txtPreview = document.getElementById("previewAfter");
      txtPreview.value = M3uToTXT(m3uPreview.value);
      convertedData = txtPreview.value;
    }

    function updatePreview(inputId, previewId) {
      var fileInput = document.getElementById(inputId);
      var file = fileInput.files[0];
      var reader = new FileReader();

      reader.onload = function(e) {
        var contents = e.target.result;
        var preview = document.getElementById(previewId);
        preview.value = contents;
      };

      reader.readAsText(file);
    }
    
    function exportConvertedFile() {
      if (typeof convertedData == 'undefined' || convertedData == null || convertedData == '') {
        alert('没有可导出的内容!');
        return;
      }
      
      const blob = new Blob([convertedData], { type: 'text/plain' });
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      
      // 根据内容自动判断文件类型
      if (convertedData.includes("#EXTM3U")) {
        link.download = 'iptv_playlist.m3u';
      } else {
        link.download = 'iptv_channels.txt';
      }
      
      link.click();
      URL.revokeObjectURL(url);
    }

    function getGroupName(str) {
      const regex = /group-title\s*=\s*"(.*?)"/;
      const result = str.match(regex);

      if (result && result.length > 1) {
        const groupTitle = result[1];
        return groupTitle.replace(/^\s*|\s*$/g, '');
      }
    }

    function clearBefore() {
      var m3uPreview = document.getElementById("previewBefore");
      m3uPreview.value = '';
      var fileInput = document.getElementById("fileInput");
      fileInput.value = '';
    }

    function clearAfter() {
      var txtPreview = document.getElementById("previewAfter");
      txtPreview.value = '';
      convertedData = '';
    }
    
    function copyContent() {
      const m3uOutput = document.getElementById('previewAfter');
      m3uOutput.select();
      if(m3uOutput.value == '') {
        alert('没有内容可复制!');
        return;
      }
      document.execCommand('copy');
      alert('内容已复制到剪贴板!');
    }
    
    // 拖拽功能
    var box = document.getElementById('container');
    
    document.ondrop = function(e){
      e.preventDefault();
    }
    
    document.ondragover = function(e){
      e.preventDefault();
    }
 
    box.ondrop = function(e){
      var dataFile = e.dataTransfer.files[0];
      var fr = new FileReader();
      fr.readAsText(dataFile);
      fr.onload = function(){
        var data = fr.result;
        var ta = document.getElementById('previewBefore');
        ta.value = data;
      }
    }
    
    // Ctrl+S 快捷键保存
    window.addEventListener("keydown", function(e) {
      if((e.key=='s'||e.key=='S')&&(navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)){
        e.preventDefault();
        exportConvertedFile();
      }
    }, false);
  </script>
</body>
</html>


文章对你有帮助吗?
  • 一般[0]
  • 很赞[0]
  • 没用[0]
  • 垃圾[0]
  • 无语[0]
扫一扫,手机浏览手机访问本站