Greasy Fork镜像 支持简体中文。

FTP Index to M3U8 Playlist Converter

Convert HTTP index directory to M3U8 playlist

  1. // ==UserScript==
  2. // @name FTP Index to M3U8 Playlist Converter
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.6.4
  5. // @description Convert HTTP index directory to M3U8 playlist
  6. // @author Beluga
  7. // @license MIT
  8. // @match http*://*.circleftp.net/*
  9. // @match http*://172.16.50.4/*
  10. // @match http*://172.16.50.7/*
  11. // @match http*://172.16.50.9/*
  12. // @match http*://172.16.50.12/*
  13. // @match http*://172.16.50.14/*
  14. // @match http*://*.ftpbd.net/*
  15. // @match http*://10.16.100.244/*
  16. // @match http*://freedrivemovie.com/*
  17. // @match http*://movies.discoveryftp.net/*
  18. // @match http*://172.19.178.62:48781/*
  19. // @match http*://cdn.dflix.live:5050/*
  20. // @match http*://fs.ebox.live/*
  21. // @grant none
  22. // ==/UserScript==
  23.  
  24. (function () {
  25. 'use strict';
  26.  
  27. const videoFormats = ['mp4', '3gp', 'mkv', 'wav', 'wmv', 'avi', 'flv', 'mov', 'ts'];
  28.  
  29. function cleanFileName(fileName) {
  30. return fileName.replace(/S(\d{1,2})E\d{1,3}/i, 'S$1').trim();
  31. }
  32.  
  33. function getPlaylistName(links) {
  34. if (links.length === 0) return "Playlist";
  35. let firstFileName = decodeURIComponent(links[0].split('/').pop());
  36. firstFileName = cleanFileName(firstFileName);
  37. return firstFileName.replace(/\.\w+$/, '') + ".m3u8";
  38. }
  39.  
  40. function createM3U8Content(links) {
  41. let content = "#EXTM3U\n";
  42. links.forEach(link => {
  43. let fileName = decodeURIComponent(link.split('/').pop()).replace(/\s+/g, '.');
  44. content += `#EXTINF:-1,${fileName}\n${link}\n`;
  45. });
  46. return content;
  47. }
  48.  
  49. function downloadM3U8(content, filename) {
  50. const userFilename = prompt("Saving the playlist as:", filename);
  51. if (!userFilename) return;
  52.  
  53. const blob = new Blob([content], { type: 'application/x-mpegURL' });
  54. const url = URL.createObjectURL(blob);
  55. const a = document.createElement('a');
  56. a.href = url;
  57. a.download = userFilename.endsWith('.m3u8') ? userFilename : userFilename + ".m3u8";
  58. document.body.appendChild(a);
  59. a.click();
  60. document.body.removeChild(a);
  61. URL.revokeObjectURL(url);
  62. }
  63.  
  64. function gatherVideoLinks() {
  65. const links = [];
  66. document.querySelectorAll('a').forEach(anchor => {
  67. try {
  68. const url = new URL(anchor.href, document.baseURI);
  69. const ext = url.pathname.split('.').pop().toLowerCase();
  70. if (videoFormats.includes(ext)) {
  71. links.push(url.href);
  72. }
  73. } catch (error) {
  74. console.warn(`Skipping invalid URL: ${anchor.href}`);
  75. }
  76. });
  77. return links.sort();
  78. }
  79.  
  80. function createDownloadButton() {
  81. let existingButton = document.getElementById("playlistDownloadBtn");
  82. if (existingButton) return existingButton; // Prevent duplicate buttons
  83.  
  84. const button = document.createElement('button');
  85. button.id = "playlistDownloadBtn";
  86. button.textContent = "Download Playlist";
  87. button.style.cssText = `
  88. position: fixed;
  89. top: 10px;
  90. right: 10px;
  91. padding: 8px 15px;
  92. font-size: 14px;
  93. font-weight: bold;
  94. color: white;
  95. background: linear-gradient(135deg, #ff6a00, #ee0979);
  96. border: none;
  97. cursor: pointer;
  98. text-align: center;
  99. border-radius: 6px;
  100. box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
  101. transition: all 0.2s ease-in-out;
  102. z-index: 1000;
  103. `;
  104. button.addEventListener('mouseover', () => button.style.background = 'linear-gradient(135deg, #ff4500, #d70270)');
  105. button.addEventListener('mouseout', () => button.style.background = 'linear-gradient(135deg, #ff6a00, #ee0979)');
  106. document.body.appendChild(button);
  107. return button;
  108. }
  109.  
  110. function generatePlaylist() {
  111. const links = gatherVideoLinks();
  112. if (links.length === 0) {
  113. alert('No video files found in this directory.');
  114. return;
  115. }
  116.  
  117. console.log(`Found ${links.length} video links.`);
  118. const button = document.getElementById("playlistDownloadBtn");
  119. if (button) {
  120. button.textContent = "Generating...";
  121. button.disabled = true;
  122. button.style.opacity = "0.6";
  123. }
  124.  
  125. setTimeout(() => {
  126. const filename = getPlaylistName(links);
  127. const content = createM3U8Content(links);
  128. downloadM3U8(content, filename);
  129. if (button) {
  130. button.textContent = "Download Playlist";
  131. button.disabled = false;
  132. button.style.opacity = "1";
  133. }
  134. }, 1000);
  135. }
  136.  
  137. function addDownloadFunctionality() {
  138. let downloadButton = createDownloadButton();
  139. if (!downloadButton) return;
  140.  
  141. downloadButton.addEventListener('click', (event) => {
  142. event.preventDefault();
  143. event.stopPropagation();
  144. generatePlaylist();
  145. });
  146.  
  147. document.addEventListener('keydown', (event) => {
  148. if (event.shiftKey && event.altKey && event.code === 'KeyD') {
  149. console.log("Shortcut Shift+Alt+D triggered");
  150. generatePlaylist();
  151. }
  152. });
  153. }
  154.  
  155. window.addEventListener('load', addDownloadFunctionality);
  156. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址