index.html 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>直播源格式转换器</title>
  7. <script src="https://cdn.tailwindcss.com"></script>
  8. </head>
  9. <body class="bg-gray-100 min-h-screen flex items-center justify-center">
  10. <div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
  11. <h1 class="text-2xl font-bold mb-6 text-center">直播源格式转换器</h1>
  12. <div class="mb-8">
  13. <h2 class="text-xl font-semibold mb-4">M3U 转 TXT(保留分组信息)</h2>
  14. <form id="m3u-to-txt-form" class="space-y-4">
  15. <input type="file" id="m3u-file" accept=".m3u" required class="w-full p-2 border rounded">
  16. <button type="submit" class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 transition duration-200">转换</button>
  17. </form>
  18. </div>
  19. <div>
  20. <h2 class="text-xl font-semibold mb-4">TXT 转 M3U(保留分组信息)</h2>
  21. <form id="txt-to-m3u-form" class="space-y-4">
  22. <input type="file" id="txt-file" accept=".txt" required class="w-full p-2 border rounded">
  23. <input type="text" id="epg-url" placeholder="EPG URL (可选,默认为 http://epg.51zmt.top:8000/e.xml)" class="w-full p-2 border rounded">
  24. <button type="submit" class="w-full bg-green-500 text-white py-2 rounded hover:bg-green-600 transition duration-200">转换</button>
  25. </form>
  26. </div>
  27. </div>
  28. <script>
  29. function parseM3UToTXT(m3uContent) {
  30. const lines = m3uContent.split('\n');
  31. const channels = {};
  32. let currentGroup = '未分组';
  33. for (let i = 0; i < lines.length; i++) {
  34. const line = lines[i].trim();
  35. if (line.startsWith('#EXTINF:-1')) {
  36. const groupMatch = line.match(/group-title="([^"]*)"/);
  37. const nameMatch = line.match(/tvg-name="([^"]*)"/);
  38. const group = groupMatch ? groupMatch[1] : currentGroup;
  39. const name = nameMatch ? nameMatch[1] : line.split(',').pop();
  40. const url = lines[i + 1] ? lines[i + 1].trim() : '';
  41. if (!channels[group]) {
  42. channels[group] = [];
  43. }
  44. channels[group].push(`${name},${url}`);
  45. currentGroup = group;
  46. }
  47. }
  48. let txtContent = '';
  49. for (const [group, channelList] of Object.entries(channels)) {
  50. txtContent += `${group},#genre#\n`;
  51. txtContent += channelList.join('\n') + '\n\n';
  52. }
  53. return txtContent;
  54. }
  55. function convertTXTToM3U(txtContent, epgUrl) {
  56. const defaultEpgUrl = "http://epg.51zmt.top:8000/e.xml";
  57. const lines = txtContent.split('\n');
  58. let m3uContent = `#EXTM3U x-tvg-url="${epgUrl || defaultEpgUrl}"\n`;
  59. let currentGenre = '未分类';
  60. for (const line of lines) {
  61. const trimmedLine = line.trim();
  62. if (trimmedLine.endsWith(',#genre#')) {
  63. currentGenre = trimmedLine.replace(',#genre#', '');
  64. } else if (trimmedLine && !trimmedLine.startsWith('#')) {
  65. const [channelName, channelUrl] = trimmedLine.split(',');
  66. const tvgLogo = `https://livecdn.zbds.top/logo/${channelName}.png`;
  67. m3uContent += `#EXTINF:-1 group-title="${currentGenre}" tvg-name="${channelName}" tvg-logo="${tvgLogo}" epg-url="${epgUrl || defaultEpgUrl}",${channelName}\n`;
  68. m3uContent += `${channelUrl}\n`;
  69. }
  70. }
  71. return m3uContent;
  72. }
  73. function downloadFile(content, filename) {
  74. const blob = new Blob([content], { type: 'text/plain' });
  75. const url = URL.createObjectURL(blob);
  76. const a = document.createElement('a');
  77. a.href = url;
  78. a.download = filename;
  79. document.body.appendChild(a);
  80. a.click();
  81. document.body.removeChild(a);
  82. URL.revokeObjectURL(url);
  83. }
  84. function getCurrentDateTime() {
  85. const now = new Date();
  86. return now.getFullYear() +
  87. ('0' + (now.getMonth() + 1)).slice(-2) +
  88. ('0' + now.getDate()).slice(-2) +
  89. ('0' + now.getHours()).slice(-2) +
  90. ('0' + now.getMinutes()).slice(-2) +
  91. ('0' + now.getSeconds()).slice(-2);
  92. }
  93. function getFileNameWithoutExtension(filename) {
  94. return filename.split('.').slice(0, -1).join('.');
  95. }
  96. document.getElementById('m3u-to-txt-form').addEventListener('submit', function(e) {
  97. e.preventDefault();
  98. const file = document.getElementById('m3u-file').files[0];
  99. const reader = new FileReader();
  100. reader.onload = function(e) {
  101. const m3uContent = e.target.result;
  102. const txtContent = parseM3UToTXT(m3uContent);
  103. const originalName = getFileNameWithoutExtension(file.name);
  104. const newFileName = `${originalName}_${getCurrentDateTime()}.txt`;
  105. downloadFile(txtContent, newFileName);
  106. };
  107. reader.readAsText(file);
  108. });
  109. document.getElementById('txt-to-m3u-form').addEventListener('submit', function(e) {
  110. e.preventDefault();
  111. const file = document.getElementById('txt-file').files[0];
  112. const epgUrl = document.getElementById('epg-url').value.trim() || "http://epg.51zmt.top:8000/e.xml";
  113. const reader = new FileReader();
  114. reader.onload = function(e) {
  115. const txtContent = e.target.result;
  116. const m3uContent = convertTXTToM3U(txtContent, epgUrl);
  117. const originalName = getFileNameWithoutExtension(file.name);
  118. const newFileName = `${originalName}_${getCurrentDateTime()}.m3u`;
  119. downloadFile(m3uContent, newFileName);
  120. };
  121. reader.readAsText(file);
  122. });
  123. </script>
  124. </body>
  125. </html>