一个隐藏视频的小玩意

GitHub:内容被隐藏
CNB:内容被隐藏
演示:内容被隐藏

测试的文件:一个隐藏视频的小玩意

原理是选择通过 copy /b 命令合并的图片+视频文件
过于简单,就不写使用过程了。
点击头像可以更换图片,可以拖拽视频到里面也可以点击上传!

一个隐藏视频的小玩意

  1. <!DOCTYPE html>
  2. <html lang=”zh-CN”>
  3. <head>
  4.     <meta charset=”UTF-8″>
  5.     <meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
  6.     <title>Copy /B</title>
  7.     <style>
  8.         body {
  9.             font-family: “Microsoft YaHei”, sans-serif;
  10.             max-width: 900px;
  11.             margin: 0 auto;
  12.             padding: 20px;
  13.             text-align: center;
  14.             background-color: #f5f5f5;
  15.         }
  16.         .container {
  17.         }
  18.         .tab-container {
  19.             display: flex;
  20.             margin-bottom: 20px;
  21.         }
  22.         .tab {
  23.             padding: 10px 20px;
  24.             cursor: pointer;
  25.             background-color: #e0e0e0;
  26.             border-radius: 5px;
  27.             margin-right: 5px;
  28.         }
  29.         .tab.active {
  30.             background-color: #4CAF50;
  31.             color: white;
  32.         }
  33.         .tab-content {
  34.             display: none;
  35.             padding: 20px;
  36.             border: 1px solid #ddd;
  37.             border-radius: 0 0 5px 5px;
  38.             background-color: white;
  39.         }
  40.         .tab-content.active {
  41.             display: block;
  42.             border: 2px dashed #ccc;
  43.             border-radius: 8px;
  44.         }
  45.         .media-preview {
  46.             max-width: 100%;
  47.             max-height: 300px;
  48.             margin: 15px auto;
  49.             border: 1px solid #ddd;
  50.             border-radius: 5px;
  51.             display: block;
  52.         }
  53.         .video-container {
  54.             position: relative;
  55.             width: 100%;
  56.             max-width: 800px;
  57.             margin: 15px auto;
  58.             background-color: #000;
  59.             border-radius: 5px;
  60.             overflow: hidden;
  61.             display: none;
  62.         }
  63.         .video-container video {
  64.             width: 100%;
  65.             height: auto;
  66.             max-height: 500px;
  67.             display: block;
  68.         }
  69.         .file-input-label, .btn {
  70.             display: inline-flex;
  71.             align-items: center;
  72.             justify-content: center;
  73.             padding: 10px 20px;
  74.             min-width: 160px;
  75.             box-sizing: border-box;
  76.             white-space: nowrap;
  77.             background-color: #4CAF50;
  78.             color: white;
  79.             border-radius: 4px;
  80.             cursor: pointer;
  81.             margin: 10px;
  82.             text-align: center;
  83.             border: none;
  84.             transition: background-color 0.3s;
  85.         }
  86.         .file-input-label:hover, .btn:hover {
  87.             background-color: #45a049;
  88.         }
  89.         .btn-secondary {
  90.             background-color: #2196F3;
  91.         }
  92.         .btn-secondary:hover {
  93.             background-color: #0b7dda;
  94.         }
  95.         .btn-danger {
  96.             background-color: #f44336;
  97.         }
  98.         .btn-danger:hover {
  99.             background-color: #da190b;
  100.         }
  101.         #status {
  102.             margin: 15px 0;
  103.             min-height: 20px;
  104.             color: #666;
  105.         }
  106.         .progress-container {
  107.             width: 100%;
  108.             background-color: #f1f1f1;
  109.             border-radius: 5px;
  110.             margin: 10px 0;
  111.             display: none;
  112.         }
  113.         .progress-bar {
  114.             height: 20px;
  115.             border-radius: 5px;
  116.             background-color: #4CAF50;
  117.             width: 0%;
  118.             transition: width 0.3s;
  119.         }
  120.         input[type=”text”] {
  121.             padding: 10px;
  122.             width: 80%;
  123.             margin: 10px 0;
  124.             border: 1px solid #ddd;
  125.             border-radius: 4px;
  126.         }
  127.         .hidden {
  128.             display: none;
  129.         }
  130.         .controls {
  131.             margin-top: 20px;
  132.         }
  133.         [url=home.php?mod=space&uid=945662]@media[/url] (max-width: 600px) {
  134.             .tab-container {
  135.                 flex-direction: column;
  136.             }
  137.             .tab {
  138.                 margin-right: 0;
  139.                 margin-bottom: 5px;
  140.                 border-radius: 5px;
  141.             }
  142.             input[type=”text”] {
  143.                 width: 95%;
  144.             }
  145.             .video-container {
  146.                 max-height: 300px;
  147.             }
  148.         }
  149.         #generate .file-input-label,
  150.         #generate #generateBtn {
  151.             width: 150px !important;        
  152.             min-width: 150px !important;   
  153.             height: 40px !important;         
  154.             padding: 0 !important;         
  155.             margin: 10px !important;         
  156.             display: inline-flex !important;
  157.             align-items: center !important;
  158.             justify-content: center !important;
  159.             box-sizing: border-box !important;
  160.             line-height: 44px !important;   
  161.             font-size: 16px !important;
  162.             white-space: nowrap !important;
  163.             vertical-align: middle !important;
  164.         }
  165.         /* 拖拽高亮样式(作用于整个面板区域) */
  166.         .tab-content.drag-accept { position: relative; }
  167.         .tab-content.drag-accept.dragover {
  168.             border-color: #4CAF50 !important;
  169.             box-shadow: 0 0 0 2px #4CAF50 inset;
  170.             background: #f0fff4;
  171.         }
  172.     </style>
  173.     <script src=”https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js”></script>
  174.     <script src=”https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js”></script>
  175. </head>
  176. <body>
  177.     <script>
  178.     document.body.innerHTML = ”;
  179.     // Create container
  180.     const container = document.createElement(‘div’);
  181.     container.className = ‘container’;
  182.     document.body.appendChild(container);
  183.     // Create heading
  184.     //const h1 = document.createElement(‘h1’);
  185.     //h1.textContent = ‘内容被隐藏播放器’;
  186.     //container.appendChild(h1);
  187.     // Create tab container
  188.     const tabContainer = document.createElement(‘div’);
  189.     tabContainer.className = ‘tab-container’;
  190.     container.appendChild(tabContainer);
  191.     // Create tabs
  192.     const tabs = [
  193.       { text: ‘文件生成’, tab: ‘generate’, active: true },
  194.       { text: ‘本地文件’, tab: ‘local’, active: false },
  195.       { text: ‘URL转换’, tab: ‘url’, active: false }
  196.     ];
  197.     tabs.forEach(tabData => {
  198.       const tab = document.createElement(‘div’);
  199.       tab.className = `tab ${tabData.active ? ‘active’ : ”}`;
  200.       tab.textContent = tabData.text;
  201.       tab.setAttribute(‘data-tab’, tabData.tab);
  202.       tabContainer.appendChild(tab);
  203.     });
  204.     // Create tab contents
  205.     const tabContents = [
  206.       {
  207.         id: ‘local’,
  208.         active: false,
  209.         title: ‘本地文件提取’,
  210.         description: ‘选择通过 copy /b 命令合并的图片+视频文件’,
  211.         content: `
  212.           <label for=”localFile” id=”localChooseLabel” class=”file-input-label”>选择本地文件</label>
  213.           <input type=”file” id=”localFile” accept=”.jpg,.jpeg,.png” class=”hidden”>
  214.            
  215.           <div id=”localStatus” class=”status”>等待选择文件…</div>
  216.           <div class=”progress-container” id=”localProgressContainer”>
  217.             <div id=”localProgress” class=”progress-bar”></div>
  218.           </div>
  219.            
  220.           <img id=”localPreview” class=”media-preview hidden”>
  221.           <div class=”video-container” id=”localVideoContainer”>
  222.             <video controls id=”localVideoPlayer”></video>
  223.           </div>
  224.            
  225.           <div class=”controls”>
  226.             <button id=”localDownloadBtn” class=”btn btn-secondary hidden”>下载视频</button>
  227.           </div>
  228.         `
  229.       },
  230.       {
  231.         id: ‘url’,
  232.         active: false,
  233.         title: ‘URL转换工具’,
  234.         description: ‘输入图片URL,提取内容被隐藏‘,
  235.         content: `
  236.           <input type=”text” id=”imageUrl” placeholder=”输入图片URL (如: [img]https://example.com/image.jpg[/img])”>
  237.            
  238.           <button id=”fetchUrlBtn” class=”btn”>提取视频</button>
  239.            
  240.           <div id=”urlStatus” class=”status”>等待输入URL…</div>
  241.           <div class=”progress-container” id=”urlProgressContainer”>
  242.             <div id=”urlProgress” class=”progress-bar”></div>
  243.           </div>
  244.            
  245.           <img id=”urlPreview” class=”media-preview hidden”>
  246.           <div class=”video-container” id=”urlVideoContainer”>
  247.             <video controls id=”urlVideoPlayer”></video>
  248.           </div>
  249.            
  250.           <div class=”controls”>
  251.             <button id=”urlDownloadBtn” class=”btn btn-secondary hidden”>下载视频</button>
  252.           </div>
  253.         `
  254.       },
  255.       {
  256.         id: ‘generate’,
  257.         active: true,
  258.         title: ‘文件生成工具’,
  259.         description: ‘选择视频文件,生成合并的图片文件’,
  260.         content: `
  261.           <div>
  262.             <img id=”defaultPreview” src=”./img/iii.jpg” class=”media-preview” title=”点击更换图片”>
  263.           </div>
  264.           <!– 隐藏的封面图片选择器 –>
  265.           <input type=”file” id=”coverImageFile” accept=”image/*” class=”hidden”>
  266.            
  267.           <label for=”videoFile” class=”file-input-label”>选择视频文件</label>
  268.           <input type=”file” id=”videoFile” accept=”video/*” class=”hidden”>
  269.            
  270.           <button id=”generateBtn” class=”btn”>生成合并图片</button>
  271.            
  272.           <div id=”generateStatus” class=”status”>等待选择视频文件…</div>
  273.           <div class=”progress-container” id=”generateProgressContainer”>
  274.             <div id=”generateProgress” class=”progress-bar”></div>
  275.           </div>
  276.            
  277.           <div class=”controls”>
  278.             <button id=”generateDownloadBtn” class=”btn btn-secondary hidden”>下载合并图片</button>
  279.           </div>
  280.         `
  281.       }
  282.     ];
  283.     tabContents.forEach(contentData => {
  284.       const content = document.createElement(‘div’);
  285.       content.id = contentData.id;
  286.       content.className = `tab-content ${contentData.active ? ‘active’ : ”}`;
  287.       
  288.       const h2 = document.createElement(‘h2’);
  289.       h2.textContent = contentData.title;
  290.       if (contentData.id === ‘local’) h2.id = ‘localTitle’;
  291.       if (contentData.id === ‘url’)   h2.id = ‘urlTitle’;
  292.       content.appendChild(h2);
  293.       
  294.       const p = document.createElement(‘p’);
  295.       p.textContent = contentData.description;
  296.       if (contentData.id === ‘local’) p.id = ‘localDesc’;
  297.       if (contentData.id === ‘url’)   p.id = ‘urlDesc’;
  298.       content.appendChild(p);
  299.       
  300.       content.innerHTML += contentData.content;
  301.       container.appendChild(content);
  302.     });
  303.     // Add tab switching functionality
  304.     const tabsElements = document.querySelectorAll(‘.tab’);
  305.     tabsElements.forEach(tab => {
  306.       tab.addEventListener(‘click’, () => {
  307.         // Remove active class from all tabs and contents
  308.         document.querySelectorAll(‘.tab’).forEach(t => t.classList.remove(‘active’));
  309.         document.querySelectorAll(‘.tab-content’).forEach(c => c.classList.remove(‘active’));
  310.          
  311.         // Add active class to clicked tab and corresponding content
  312.         tab.classList.add(‘active’);
  313.         const tabId = tab.getAttribute(‘data-tab’);
  314.         document.getElementById(tabId).classList.add(‘active’);
  315.       });
  316.     });
  317.         // 全局变量
  318.         let currentVideoBlob = null;
  319.         let currentGeneratedBlob = null;
  320.         let droppedVideoFile = null; // 拖拽到“文件生成工具”的视频文件
  321.         let customCoverImageBlob = null; // 用户自选的封面图片
  322.          
  323.         // 切换选项卡
  324.         document.querySelectorAll(‘.tab’).forEach(tab => {
  325.             tab.addEventListener(‘click’, () => {
  326.                 document.querySelectorAll(‘.tab’).forEach(t => t.classList.remove(‘active’));
  327.                 document.querySelectorAll(‘.tab-content’).forEach(c => c.classList.remove(‘active’));
  328.                  
  329.                 tab.classList.add(‘active’);
  330.                 document.getElementById(tab.dataset.tab).classList.add(‘active’);
  331.             });
  332.         });
  333.          
  334.         // 本地文件处理
  335.         document.getElementById(‘localFile’).addEventListener(‘change’, async function(e) {
  336.             const file = e.target.files[0];
  337.             if (!file) return;
  338.             
  339.             const statusEl = document.getElementById(‘localStatus’);
  340.             const progressContainer = document.getElementById(‘localProgressContainer’);
  341.             const progressEl = document.getElementById(‘localProgress’);
  342.             const videoContainer = document.getElementById(‘localVideoContainer’);
  343.             const videoPlayer = document.getElementById(‘localVideoPlayer’);
  344.             const previewImg = document.getElementById(‘localPreview’);
  345.             const downloadBtn = document.getElementById(‘localDownloadBtn’);
  346.             
  347.             // 重置状态
  348.             statusEl.textContent = “正在处理文件…”;
  349.             progressContainer.style.display = ‘block’;
  350.             progressEl.style.width = ‘0%’;
  351.             videoContainer.style.display = ‘none’;
  352.             downloadBtn.classList.add(‘hidden’);
  353.             currentVideoBlob = null;
  354.             
  355.             try {
  356.                 // 显示预览图片
  357.                 previewImg.src = URL.createObjectURL(file);
  358.                 previewImg.classList.remove(‘hidden’);
  359.                  
  360.                 // 1. 读取文件内容
  361.                 const arrayBuffer = await file.arrayBuffer();
  362.                 progressEl.style.width = ‘20%’;
  363.                  
  364.                 // 2. 查找ZIP文件起始位置
  365.                 statusEl.textContent = “正在查找隐藏内容…”;
  366.                 const uint8Array = new Uint8Array(arrayBuffer);
  367.                 let zipStart = -1;
  368.                  
  369.                 // 查找ZIP文件头 (PK\x03\x04)
  370.                 for (let i = 0; i < uint8Array.length – 4; i++) {
  371.                     if (uint8Array[i] === 0x50 && uint8Array[i+1] === 0x4B &&
  372.                         uint8Array[i+2] === 0x03 && uint8Array[i+3] === 0x04) {
  373.                         zipStart = i;
  374.                         break;
  375.                     }
  376.                 }
  377.                  
  378.                 if (zipStart === -1) throw new Error(“未检测到隐藏内容”);
  379.                 progressEl.style.width = ‘40%’;
  380.                  
  381.                 // 3. 提取ZIP部分
  382.                 statusEl.textContent = “正在提取隐藏数据…”;
  383.                 const zipData = arrayBuffer.slice(zipStart);
  384.                 progressEl.style.width = ‘60%’;
  385.                  
  386.                 // 4. 使用JSZip解析ZIP
  387.                 statusEl.textContent = “正在解析内容…”;
  388.                 const zip = await JSZip.loadAsync(zipData);
  389.                 progressEl.style.width = ‘80%’;
  390.                  
  391.                 // 5. 查找视频文件
  392.                 statusEl.textContent = “正在查找视频文件…”;
  393.                 let videoFile = null;
  394.                 const videoExtensions = [‘.mp4’, ‘.webm’, ‘.ogg’, ‘.mov’, ‘.avi’, ‘.mkv’];
  395.                  
  396.                 for (const [name, file] of Object.entries(zip.files)) {
  397.                     if (!file.dir && videoExtensions.some(ext => name.toLowerCase().endsWith(ext))) {
  398.                         videoFile = file;
  399.                         break;
  400.                     }
  401.                 }
  402.                  
  403.                 if (!videoFile) throw new Error(“未找到支持的视频文件”);
  404.                  
  405.                 // 6. 提取视频内容
  406.                 statusEl.textContent = “正在提取视频…”;
  407.                 const videoData = await videoFile.async(‘blob’);
  408.                 currentVideoBlob = videoData;
  409.                 progressEl.style.width = ‘100%’;
  410.                  
  411.                 // 7. 播放视频
  412.                 videoPlayer.src = URL.createObjectURL(videoData);
  413.                 videoContainer.style.display = ‘block’;
  414.                 downloadBtn.classList.remove(‘hidden’);
  415.                  
  416.                 statusEl.textContent = `已加载: ${videoFile.name}`;
  417.                  
  418.                 // 成功后:隐藏标题、描述、选择按钮与预览图,仅保留文件名与视频
  419.                 const lt = document.getElementById(‘localTitle’);
  420.                 const ld = document.getElementById(‘localDesc’);
  421.                 const ll = document.getElementById(‘localChooseLabel’);
  422.                 if (lt) lt.style.display = ‘none’;
  423.                 if (ld) ld.style.display = ‘none’;
  424.                 if (ll) ll.style.display = ‘none’;
  425.                 previewImg.classList.add(‘hidden’);
  426.                 // 自动播放
  427.                 videoPlayer.play().catch(e => {
  428.                     statusEl.textContent += ” (点击播放按钮开始播放)”;
  429.                 });
  430.                  
  431.             } catch (error) {
  432.                 console.error(“处理失败:”, error);
  433.                 statusEl.textContent = `错误: ${error.message}`;
  434.                 progressEl.style.width = ‘0%’;
  435.                 videoContainer.style.display = ‘none’;
  436.             } finally {
  437.                 setTimeout(() => {
  438.                     progressContainer.style.display = ‘none’;
  439.                 }, 500);
  440.             }
  441.         });
  442.          
  443.         // 本地图片拖拽上传(整个面板区域可拖拽)
  444.         (function() {
  445.             const dropEl = document.getElementById(‘local’);
  446.             if (!dropEl) return;
  447.             dropEl.classList.add(‘drag-accept’);
  448.             const isImage = (file) => {
  449.                 const name = (file.name || ”).toLowerCase();
  450.                 return file.type.startsWith(‘image/’) || name.endsWith(‘.jpg’) || name.endsWith(‘.jpeg’) || name.endsWith(‘.png’);
  451.             };
  452.             const prevent = (e) => { e.preventDefault(); e.stopPropagation(); };
  453.             [‘dragenter’,’dragover’].forEach(ev => {
  454.                 dropEl.addEventListener(ev, (e) => { prevent(e); dropEl.classList.add(‘dragover’); });
  455.             });
  456.             [‘dragleave’,’drop’].forEach(ev => {
  457.                 dropEl.addEventListener(ev, (e) => { prevent(e); dropEl.classList.remove(‘dragover’); });
  458.             });
  459.             dropEl.addEventListener(‘drop’, async (e) => {
  460.                 const file = e.dataTransfer?.files?.[0];
  461.                 if (!file) return;
  462.                 if (!isImage(file)) {
  463.                     alert(‘请拖入 JPG/PNG 图片文件’);
  464.                     return;
  465.                 }
  466.                 const statusEl = document.getElementById(‘localStatus’);
  467.                 const progressContainer = document.getElementById(‘localProgressContainer’);
  468.                 const progressEl = document.getElementById(‘localProgress’);
  469.                 const videoContainer = document.getElementById(‘localVideoContainer’);
  470.                 const videoPlayer = document.getElementById(‘localVideoPlayer’);
  471.                 const previewImg = document.getElementById(‘localPreview’);
  472.                 const downloadBtn = document.getElementById(‘localDownloadBtn’);
  473.                 currentVideoBlob = null;
  474.                 try {
  475.                     statusEl.textContent = “正在处理文件…”;
  476.                     progressContainer.style.display = ‘block’;
  477.                     progressEl.style.width = ‘0%’;
  478.                     videoContainer.style.display = ‘none’;
  479.                     downloadBtn.classList.add(‘hidden’);
  480.                     // 预览图片
  481.                     previewImg.src = URL.createObjectURL(file);
  482.                     previewImg.classList.remove(‘hidden’);
  483.                     // 读取内容
  484.                     const arrayBuffer = await file.arrayBuffer();
  485.                     progressEl.style.width = ‘20%’;
  486.                     // 查找 ZIP 头
  487.                     statusEl.textContent = “正在查找隐藏内容…”;
  488.                     const uint8Array = new Uint8Array(arrayBuffer);
  489.                     let zipStart = -1;
  490.                     for (let i = 0; i < uint8Array.length – 4; i++) {
  491.                         if (uint8Array[i] === 0x50 && uint8Array[i+1] === 0x4B && uint8Array[i+2] === 0x03 && uint8Array[i+3] === 0x04) {
  492.                             zipStart = i;
  493.                             break;
  494.                         }
  495.                     }
  496.                     if (zipStart === -1) throw new Error(“未检测到隐藏内容”);
  497.                     progressEl.style.width = ‘40%’;
  498.                     // 提取/解析 ZIP
  499.                     statusEl.textContent = “正在提取隐藏数据…”;
  500.                     const zipData = arrayBuffer.slice(zipStart);
  501.                     progressEl.style.width = ‘60%’;
  502.                     statusEl.textContent = “正在解析内容…”;
  503.                     const zip = await JSZip.loadAsync(zipData);
  504.                     progressEl.style.width = ‘80%’;
  505.                     // 查找视频
  506.                     statusEl.textContent = “正在查找视频文件…”;
  507.                     let videoFile = null;
  508.                     const videoExtensions = [‘.mp4’, ‘.webm’, ‘.ogg’, ‘.mov’, ‘.avi’, ‘.mkv’];
  509.                     for (const [name, zf] of Object.entries(zip.files)) {
  510.                         if (!zf.dir && videoExtensions.some(ext => name.toLowerCase().endsWith(ext))) {
  511.                             videoFile = zf;
  512.                             break;
  513.                         }
  514.                     }
  515.                     if (!videoFile) throw new Error(“未找到支持的视频文件”);
  516.                     // 提取视频
  517.                     statusEl.textContent = “正在提取视频…”;
  518.                     const videoData = await videoFile.async(‘blob’);
  519.                     currentVideoBlob = videoData;
  520.                     progressEl.style.width = ‘100%’;
  521.                     // 播放展示
  522.                     videoPlayer.src = URL.createObjectURL(videoData);
  523.                     videoContainer.style.display = ‘block’;
  524.                     downloadBtn.classList.remove(‘hidden’);
  525.                     statusEl.textContent = `已加载: ${videoFile.name}`;
  526.                     // 成功后:隐藏标题、描述、选择按钮与预览图,仅保留文件名与视频
  527.                     const lt = document.getElementById(‘localTitle’);
  528.                     const ld = document.getElementById(‘localDesc’);
  529.                     const ll = document.getElementById(‘localChooseLabel’);
  530.                     if (lt) lt.style.display = ‘none’;
  531.                     if (ld) ld.style.display = ‘none’;
  532.                     if (ll) ll.style.display = ‘none’;
  533.                     previewImg.classList.add(‘hidden’);
  534.                     videoPlayer.play().catch(() => {
  535.                         statusEl.textContent += ” (点击播放按钮开始播放)”;
  536.                     });
  537.                 } catch (err) {
  538.                     console.error(‘拖拽处理失败:’, err);
  539.                     statusEl.textContent = `错误: ${err.message}`;
  540.                     progressEl.style.width = ‘0%’;
  541.                     videoContainer.style.display = ‘none’;
  542.                 } finally {
  543.                     setTimeout(() => { progressContainer.style.display = ‘none’; }, 500);
  544.                 }
  545.             });
  546.         })();
  547.         // 本地文件下载
  548.         document.getElementById(‘localDownloadBtn’).addEventListener(‘click’, function() {
  549.             if (currentVideoBlob) {
  550.                 saveAs(currentVideoBlob, ‘extracted_video.mp4’);
  551.             }
  552.         });
  553.          
  554.         // URL转换处理
  555.         document.getElementById(‘fetchUrlBtn’).addEventListener(‘click’, async function() {
  556.             const imageUrl = document.getElementById(‘imageUrl’).value.trim();
  557.             
  558.             if (!imageUrl) {
  559.                 alert(“请输入图片URL”);
  560.                 return;
  561.             }
  562.             
  563.             const statusEl = document.getElementById(‘urlStatus’);
  564.             const progressContainer = document.getElementById(‘urlProgressContainer’);
  565.             const progressEl = document.getElementById(‘urlProgress’);
  566.             const videoContainer = document.getElementById(‘urlVideoContainer’);
  567.             const videoPlayer = document.getElementById(‘urlVideoPlayer’);
  568.             const previewImg = document.getElementById(‘urlPreview’);
  569.             const downloadBtn = document.getElementById(‘urlDownloadBtn’);
  570.             
  571.             // 重置状态
  572.             statusEl.textContent = “正在获取图片…”;
  573.             progressContainer.style.display = ‘block’;
  574.             progressEl.style.width = ‘0%’;
  575.             videoContainer.style.display = ‘none’;
  576.             downloadBtn.classList.add(‘hidden’);
  577.             currentVideoBlob = null;
  578.             
  579.             try {
  580.                 // 显示预览图片
  581.                 previewImg.src = imageUrl;
  582.                 previewImg.classList.remove(‘hidden’);
  583.                  
  584.                 // 1. 获取图片
  585.                 const response = await fetch(imageUrl);
  586.                 if (!response.ok) throw new Error(`获取图片失败: ${response.status}`);
  587.                  
  588.                 progressEl.style.width = ‘20%’;
  589.                  
  590.                 // 2. 读取图片内容
  591.                 const arrayBuffer = await response.arrayBuffer();
  592.                 progressEl.style.width = ‘40%’;
  593.                  
  594.                 // 3. 查找ZIP文件起始位置
  595.                 statusEl.textContent = “正在查找隐藏内容…”;
  596.                 const uint8Array = new Uint8Array(arrayBuffer);
  597.                 let zipStart = -1;
  598.                  
  599.                 for (let i = 0; i < uint8Array.length – 4; i++) {
  600.                     if (uint8Array[i] === 0x50 && uint8Array[i+1] === 0x4B &&
  601.                         uint8Array[i+2] === 0x03 && uint8Array[i+3] === 0x04) {
  602.                         zipStart = i;
  603.                         break;
  604.                     }
  605.                 }
  606.                  
  607.                 if (zipStart === -1) throw new Error(“未检测到隐藏内容”);
  608.                 progressEl.style.width = ‘60%’;
  609.                  
  610.                 // 4. 提取ZIP部分
  611.                 statusEl.textContent = “正在提取隐藏数据…”;
  612.                 const zipData = arrayBuffer.slice(zipStart);
  613.                 progressEl.style.width = ‘80%’;
  614.                  
  615.                 // 5. 使用JSZip解析ZIP
  616.                 statusEl.textContent = “正在解析内容…”;
  617.                 const zip = await JSZip.loadAsync(zipData);
  618.                  
  619.                 // 6. 查找视频文件
  620.                 statusEl.textContent = “正在查找视频文件…”;
  621.                 let videoFile = null;
  622.                 const videoExtensions = [‘.mp4’, ‘.webm’, ‘.ogg’, ‘.mov’, ‘.avi’, ‘.mkv’];
  623.                  
  624.                 for (const [name, file] of Object.entries(zip.files)) {
  625.                     if (!file.dir && videoExtensions.some(ext => name.toLowerCase().endsWith(ext))) {
  626.                         videoFile = file;
  627.                         break;
  628.                     }
  629.                 }
  630.                  
  631.                 if (!videoFile) throw new Error(“未找到支持的视频文件”);
  632.                  
  633.                 // 7. 提取视频内容
  634.                 statusEl.textContent = “正在提取视频…”;
  635.                 const videoData = await videoFile.async(‘blob’);
  636.                 currentVideoBlob = videoData;
  637.                 progressEl.style.width = ‘100%’;
  638.                  
  639.                 // 8. 播放视频
  640.                 videoPlayer.src = URL.createObjectURL(videoData);
  641.                 videoContainer.style.display = ‘block’;
  642.                  
  643.                 statusEl.textContent = `已加载: ${videoFile.name}`;
  644.                  
  645.                 // 成功后:仅保留文件名与视频,隐藏其它控件
  646.                 const ut = document.getElementById(‘urlTitle’);
  647.                 const ud = document.getElementById(‘urlDesc’);
  648.                 const urlInput = document.getElementById(‘imageUrl’);
  649.                 const fetchBtn = document.getElementById(‘fetchUrlBtn’);
  650.                 const urlPrev = document.getElementById(‘urlPreview’);
  651.                 const urlDl  = document.getElementById(‘urlDownloadBtn’);
  652.                 if (ut) ut.style.display = ‘none’;
  653.                 if (ud) ud.style.display = ‘none’;
  654.                 if (urlInput) urlInput.style.display = ‘none’;
  655.                 if (fetchBtn) fetchBtn.style.display = ‘none’;
  656.                 if (urlPrev) urlPrev.classList.add(‘hidden’);
  657.                 if (urlDl) urlDl.classList.add(‘hidden’);
  658.                 // 自动播放
  659.                 videoPlayer.play().catch(e => {
  660.                     statusEl.textContent += ” (点击播放按钮开始播放)”;
  661.                 });
  662.                  
  663.             } catch (error) {
  664.                 console.error(“处理失败:”, error);
  665.                 statusEl.textContent = `错误: ${error.message}`;
  666.                 progressEl.style.width = ‘0%’;
  667.                 videoContainer.style.display = ‘none’;
  668.             } finally {
  669.                 setTimeout(() => {
  670.                     progressContainer.style.display = ‘none’;
  671.                 }, 500);
  672.             }
  673.         });
  674.          
  675.         // URL文件下载
  676.         document.getElementById(‘urlDownloadBtn’).addEventListener(‘click’, function() {
  677.             if (currentVideoBlob) {
  678.                 saveAs(currentVideoBlob, ‘extracted_video.mp4’);
  679.             }
  680.         });
  681.          
  682.         // 点击生成面板图片以更换封面
  683.         (function() {
  684.             const previewImg = document.getElementById(‘defaultPreview’);
  685.             const coverInput = document.getElementById(‘coverImageFile’);
  686.             if (!previewImg || !coverInput) return;
  687.             previewImg.style.cursor = ‘pointer’;
  688.             previewImg.addEventListener(‘click’, () => {
  689.                 // 提示并打开文件选择
  690.                 coverInput.click();
  691.             });
  692.             coverInput.addEventListener(‘change’, async () => {
  693.                 const file = coverInput.files && coverInput.files[0];
  694.                 if (!file) return;
  695.                 // 保存自定义封面并更新预览
  696.                 customCoverImageBlob = file;
  697.                 try {
  698.                     const objectUrl = URL.createObjectURL(file);
  699.                     previewImg.src = objectUrl;
  700.                     const statusEl = document.getElementById(‘generateStatus’);
  701.                     if (statusEl) statusEl.textContent = `已选择封面图片:${file.name}`;
  702.                 } catch (_) {}
  703.             });
  704.         })();
  705.         // “文件生成工具”拖拽上传(整个面板区域可拖拽)
  706.         (function() {
  707.             const dropEl = document.getElementById(‘generate’);
  708.             if (!dropEl) return;
  709.             dropEl.classList.add(‘drag-accept’);
  710.             const isVideo = (file) => file.type.startsWith(‘video/’);
  711.             const prevent = (e) => { e.preventDefault(); e.stopPropagation(); };
  712.             [‘dragenter’,’dragover’].forEach(ev => {
  713.                 dropEl.addEventListener(ev, (e) => { prevent(e); dropEl.classList.add(‘dragover’); });
  714.             });
  715.             [‘dragleave’,’drop’].forEach(ev => {
  716.                 dropEl.addEventListener(ev, (e) => { prevent(e); dropEl.classList.remove(‘dragover’); });
  717.             });
  718.             dropEl.addEventListener(‘drop’, (e) => {
  719.                 const file = e.dataTransfer?.files?.[0];
  720.                 if (!file) return;
  721.                 if (!isVideo(file)) {
  722.                     alert(‘请拖入视频文件’);
  723.                     return;
  724.                 }
  725.                 droppedVideoFile = file;
  726.                 const statusEl = document.getElementById(‘generateStatus’);
  727.                 statusEl.textContent = `已选择(拖拽):${file.name}`;
  728.             });
  729.         })();
  730.         // 文件生成处理
  731.         document.getElementById(‘generateBtn’).addEventListener(‘click’, async function() {
  732.             const videoFileInput = document.getElementById(‘videoFile’);
  733.             const statusEl = document.getElementById(‘generateStatus’);
  734.             const progressEl = document.getElementById(‘generateProgress’);
  735.             const progressContainer = document.getElementById(‘generateProgressContainer’);
  736.             const downloadBtn = document.getElementById(‘generateDownloadBtn’);
  737.             const videoFile = videoFileInput.files[0] || droppedVideoFile;
  738.             if (!videoFile) {
  739.                 alert(“请选择或拖拽视频文件”);
  740.                 return;
  741.             }
  742.             try {
  743.                 statusEl.textContent = “正在准备生成文件…”;
  744.                 progressContainer.style.display = ‘block’;
  745.                 progressEl.style.width = ‘0%’;
  746.                 downloadBtn.classList.add(‘hidden’);
  747.                  
  748.                 // 1. 创建ZIP
  749.                 const zip = new JSZip();
  750.                 zip.file(videoFile.name, videoFile);
  751.                  
  752.                 // 2. 生成ZIP
  753.                 statusEl.textContent = “正在生成ZIP文件…”;
  754.                 const zipBlob = await zip.generateAsync({type: ‘blob’}, (metadata) => {
  755.                     progressEl.style.width = `${metadata.percent}%`;
  756.                 });
  757.                  
  758.                 // 3. 获取封面图片(优先使用用户选择的图片)
  759.                 let imageBlob;
  760.                 if (customCoverImageBlob) {
  761.                     statusEl.textContent = “已选择自定义图片,正在读取…”;
  762.                     imageBlob = customCoverImageBlob;
  763.                 } else {
  764.                     statusEl.textContent = “正在获取默认图片…”;
  765.                     const imageResp = await fetch(‘./img/iii.jpg’);
  766.                     imageBlob = await imageResp.blob();
  767.                 }
  768.                  
  769.                 // 4. 合并文件
  770.                 statusEl.textContent = “正在合并文件…”;
  771.                 const mergedBlob = new Blob([
  772.                     await imageBlob.arrayBuffer(),
  773.                     await zipBlob.arrayBuffer()
  774.                 ], { type: ‘image/jpeg’ });
  775.                 currentGeneratedBlob = mergedBlob;
  776.                 statusEl.textContent = “生成成功!”;
  777.                 downloadBtn.classList.remove(‘hidden’);
  778.             } catch (error) {
  779.                 console.error(“生成失败:”, error);
  780.                 statusEl.textContent = `错误: ${error.message}`;
  781.                 progressEl.style.width = ‘0%’;
  782.             } finally {
  783.                 setTimeout(() => {
  784.                     progressContainer.style.display = ‘none’;
  785.                 }, 500);
  786.             }
  787.         });
  788.          
  789.         // 生成文件下载
  790.         document.getElementById(‘generateDownloadBtn’).addEventListener(‘click’, function() {
  791.             if (currentGeneratedBlob) {
  792.                 saveAs(currentGeneratedBlob, ‘merged_image.jpg’);
  793.             }
  794.         });
  795.     // 全局拦截,避免把文件拖到页面其它区域时被浏览器直接打开
  796.     (function() {
  797.         const prevent = (e) => { e.preventDefault(); e.stopPropagation(); };
  798.         window.addEventListener(‘dragover’, prevent);
  799.         window.addEventListener(‘drop’, prevent);
  800.     })();
  801.     </script>
  802.     <style>
  803.         .corner-links {
  804.             position: fixed;
  805.             right: 20px;
  806.             bottom: 20px;
  807.             display: flex;
  808.             align-items: center;
  809.             z-index: 9999;
  810.         }
  811.         .corner-link {
  812.             display: inline-flex;
  813.             align-items: center;
  814.             gap: 8px;
  815.             padding: 8px 10px;
  816.             border-radius: 8px;
  817.             color: #212529;
  818.             text-decoration: none;
  819.             transition: all 0.3s ease;
  820.         }
  821.         .corner-link:hover {
  822.             transform: translateY(-2px);
  823.             box-shadow: 0 6px 12px rgba(0,0,0,0.15);
  824.         }
  825.         .corner-link img,
  826.         .corner-link svg {
  827.             width: 22px;
  828.             height: 22px;
  829.         }
  830.         .corner-link .label {
  831.             font-weight: 600;
  832.             font-size: 0.95rem;
  833.         }
  834.     </style>
  835.     <div class=”corner-links” aria-label=”页面固定链接”>
  836.         <a class=”corner-link” href=”https://github.com/IIIStudio/Copy-B” target=”_blank” rel=”noopener noreferrer” aria-label=”前往 GitHub 仓库”>
  837.             <!– 内联 GitHub 图标,避免外部资源依赖 –>
  838.             <svg viewBox=”0 0 24 24″ aria-hidden=”true” focusable=”false” xmlns=”http://www.w3.org/2000/svg”>
  839.                 <path fill=”#24292F” d=”M12 .5a12 12 0 0 0-3.79 23.41c.6.11.82-.26.82-.58v-2.02c-3.35.73-4.06-1.61-4.06-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.75.08-.74.08-.74 1.2.09 1.83 1.23 1.83 1.23 1.07 1.83 2.8 1.3 3.49.99.11-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.13-.3-.54-1.51.12-3.15 0 0 1.01-.32 3.3 1.23.96-.27 1.99-.4 3.01-.4s2.05.14 3.01.4c2.29-1.55 3.3-1.23 3.3-1.23.66 1.64.25 2.85.12 3.15.77.84 1.24 1.91 1.24 3.22 0 4.61-2.8 5.63-5.47 5.93.43.37.81 1.1.81 2.22v3.29c0 .32.22.7.83.58A12 12 0 0 0 12 .5Z”/>
  840.             </svg>
  841.             <span class=”label”>Copy-B</span>
  842.         </a>
  843.         <a class=”corner-link” href=”https://cnb.cool/IIIStudio/HTML/Copy-B/” target=”_blank” rel=”noopener noreferrer” aria-label=”前往 Copy-B 文档页面”>
  844.             <img src=”https://docs.cnb.cool/images/logo/svg/LogoColorfulIcon.svg” alt=”CNB Logo”>
  845.         </a>
  846.     </div>
  847. </body>
  848. </html>

复制代码

内容已被隐藏,请输入验证码查看

关注微信公众号(魔王科技),发送"验证码"获取

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索