您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
把動畫瘋內容片段轉成GIF
当前为
// ==UserScript== // @name 巴哈姆特動畫瘋GIF截圖工具 // @namespace 巴哈:aa24281024/GitHub:Mystic0428 // @version 1.1 // @description 把動畫瘋內容片段轉成GIF // @author 巴哈:aa24281024(Mystic)/GitHub:Mystic0428 // @match https://ani.gamer.com.tw/animeVideo.php?sn=* // @icon https://www.google.com/s2/favicons?sz=64&domain=gamer.com.tw // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const style = document.createElement('style'); style.textContent = ` .wrapper { --input-focus: #2d8cf0; --font-color: #323232; --font-color-sub: #666; --bg-color: #fff; --bg-color-alt: #666; --main-color: #323232; } .flip-card__popup { width: 1200px; height: auto; padding: 20px; display: none; position: fixed; left: 50%; top: 50%; flex-direction: column; justify-content: center; background: lightgrey; gap: 20px; border-radius: 5px; border: 2px solid var(--main-color); box-shadow: 4px 4px var(--main-color); backface-visibility: hidden; transform: translate(-50%, -50%); z-index:100; } .flip-card__form { display: flex; flex-direction: column; align-items: center; gap: 20px; } .flip-card__input { width: 250px; height: 40px; border-radius: 5px; border: 2px solid var(--main-color); background-color: var(--bg-color); box-shadow: 4px 4px var(--main-color); font-size: 15px; font-weight: 600; color: var(--font-color); padding: 5px 10px; outline: none; } .flip-card__input::placeholder { color: var(--font-color-sub); opacity: 0.8; } .flip-card__input:focus { border: 2px solid var(--input-focus); } .flip-card__btn { margin: 20px 0 0; width: 120px; height: 40px; border-radius: 5px; border: 2px solid var(--main-color); background-color: var(--bg-color); box-shadow: 4px 4px var(--main-color); font-size: 17px; font-weight: 600; color: var(--font-color); cursor: pointer; } .modal__header { padding: 1rem 0rem; border-bottom: 1px solid #231b1b; display: flex; align-items: center; justify-content: space-between; position: relative; z-index: 1; top: -20px; } .modal__title { font-size: 25px; font-weight: 900; color: var(--main-color); flex-grow: 1; text-align: center; } .close-button { appaerance: none; font: inherit; border: none; background: none; cursor: pointer; } .close-button--icon { width: 2.5rem; height: 2.5rem; background-color: transparent; border-radius: 0.25rem; position: absolute; right: 0; } .range-wrapper { border-radius: 10px; padding: 5px 25px 40px; box-shadow: 0 12px 35px rgba(0, 0, 0, 0.1); } .range-wrapper h2, .range-wrapper span { font-weight: 900; font-size: 15.5px; } .price-input { width: 100%; display: flex; margin: 30px 0 45px; } .price-input .field { display: flex; width: 100%; height: 45px; align-items: center; } .field input { width: 100%; height: 100%; outline: none; font-size: 19px; margin-left: 12px; border-radius: 5px; text-align: center; border: 1px solid #999; -moz-appearance: textfield; } input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; } .price-input .separator { width: 130px; display: flex; font-size: 19px; align-items: center; justify-content: center; } .slider { height: 5px; position: relative; background: #ddd; border-radius: 5px; } .slider .progress { height: 100%; left: 25%; right: 25%; position: absolute; border-radius: 5px; background: #17a2b8; } .range-input { position: relative; } .range-input input { position: absolute; width: 100%; height: 5px; top: -6px; left: -2px; background: none; pointer-events: none; -webkit-appearance: none; -moz-appearance: none; } input[type="range"]::-webkit-slider-thumb { height: 17px; width: 5px; border-radius: 80%; background: #17a2b8; pointer-events: auto; -webkit-appearance: none; box-shadow: 0 0 6px rgba(0, 0, 0, 0.05); } input[type="range"]::-moz-range-thumb { height: 17px; width: 17px; border: none; border-radius: 50%; background: #17a2b8; pointer-events: auto; -moz-appearance: none; box-shadow: 0 0 6px rgba(0, 0, 0, 0.05); } .d-flex { width: 90%; } .control-btn { margin: 20px 0 0; display: flex; justify-content: center; } .card { --bg-card: #27272a; --primary: #6d28d9; --primary-800: #4c1d95; --primary-shadow: #2e1065; --light: #d9d9d9; --zinc-800: #18181b; --bg-linear: linear-gradient(0deg, var(--primary) 50%, var(--light) 125%); position: relative; display: flex; flex-direction: column; gap: 0.75rem; padding: 1rem; background-color: var(--bg-card); border-radius: 1rem; } .image_container { overflow: hidden; cursor: pointer; position: relative; z-index: 5; width: 300px; height: 200px; background-color: var(--primary-800); border-radius: 0.5rem; } .image_container .image { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; height: 100%; fill: var(--light); } .title { display: flex; justify-content: center; align-items: center; width: 100%; font-weight: 600; color: var(--light); text-transform: capitalize; text-wrap: nowrap; text-overflow: ellipsis; } .cart-button { cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 0.25rem; padding: 0.5rem; width: 100%; background-image: var(--bg-linear); font-size: 0.75rem; font-weight: 500; color: var(--light); text-wrap: nowrap; border: 2px solid hsla(262, 83%, 58%, 0.5); border-radius: 0.5rem; box-shadow: inset 0 0 0.25rem 1px var(--light); } .imgs-container { display: flex; flex-direction: row; overflow-x: auto; gap: 20px; align-items: center; justify-content: center; width: 90%; padding-top: 10px; padding-bottom: 10px; } .imgs-container::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); border-radius: 10px; background-color: #F5F5F5; } .imgs-container::-webkit-scrollbar { width: 12px; } .imgs-container::-webkit-scrollbar-thumb { border-radius: 10px; -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); background-color: #555; } .content-container { width: 100%; display: flex; flex-direction: column; align-items: center; } .result-container { width: 90%; display: flex; flex-direction: column; align-items: center; box-shadow: 0 12px 35px rgba(0, 0, 0, 0.1); border-radius: 10px; } #progressBar { width: 40%; height: 25px; background-color: #e0e0e0; border-radius: 10px; position: relative; overflow: hidden; } #progress { height: 100%; background-color: #568358; width: 0%; border-radius: 10px; } #percentage { font-size: 14px; color: #333; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); white-space: nowrap; } #resolutionProgressBar { width: 40%; height: 25px; background-color: #e0e0e0; border-radius: 10px; position: relative; overflow: hidden; margin-right: 40px; } #resolutionProgress { height: 100%; background-color: #6e8f8f; width: 0%; border-radius: 10px; } #resolutionPercentage { font-size: 14px; color: #333; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); white-space: nowrap; } .progress-text { font-weight: 600; font-size: 19px; } .form-title { margin-bottom: 30px; text-align: center; } .form-title h2{ font-weight: 900; font-size: 24px; } .image-container-override { justify-content: normal; } `; document.head.appendChild(style); const popupHTML = ` <div class="wrapper flip-card__popup" id="popup"> <div class="modal__header"> <span class="modal__title">動畫瘋GIF截圖工具</span> <button class="close-button close-button--icon" id="closePopupBtn"> <svg width="24" viewBox="0 0 24 24" height="24" xmlns="http://www.w3.org/2000/svg"> <path fill="none" d="M0 0h24v24H0V0z"></path> <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"> </path> </svg> </button> </div> <form class="flip-card__form" action=""> <div class="d-flex"> <div class="range-wrapper"> <div class="form-title"> <h2>時間範圍</h2> </div> <div style="display: flex; flex-direction: row;align-items: center;justify-content: space-between;"> <span>解析進度:</span> <div id="resolutionProgressBar"> <div id="resolutionProgress"><span class="progress-text" id="resolutionPercentage">0%</span> </div> </div> <span>生成進度:</span> <div id="progressBar"> <div id="progress"><span class="progress-text" id="percentage">0%</span></div> </div> </div> <div class="price-input"> <div class="field"> <span>開始</span> <input type="text" class="input-min flip-card__input" value="00:00:00:000"> </div> <div class="separator">-</div> <div class="field"> <span>結束</span> <input type="text" class="input-max flip-card__input" value="00:00:15:000"> </div> </div> <div class="slider"> <div class="progress" style="left: 0%; right: 98.6437%;"></div> </div> <div class="range-input"> <input type="range" class="range-min" min="0" max="1420000" value="0" step="100"> <input type="range" class="range-max" min="0" max="1420000" value="15000" step="100"> </div> <div class="control-btn"> <button type="button" class="flip-card__btn" id="generateButton">生成</button> <button type="button" class="flip-card__btn" id="reset-btn">重置</button> </div> </div> </div> </form> <div class="content-container"> <div class="result-container"> <span class="modal__title" style="margin-top: 10px;">生成結果</span> <div class="imgs-container"> </div> <div style="height:30px;"></div> </div> </div> </div> `; let rangeInput, timeInput, range, imgsContainer, resetButton; let timeGap = 500; let timeRange = 15000; function handleTimeChange(e) { let startTime = timeToMilliseconds(timeInput[0].value) , endTime = timeToMilliseconds(timeInput[1].value); if (startTime === null || endTime === null) { if (e.target.classList.contains("input-min")) { timeInput[0].value = formatTime(rangeInput[0].value); } else { timeInput[1].value = formatTime(rangeInput[1].value); } return; } if (startTime < 0 || endTime > rangeInput[1].max || startTime > rangeInput[0].max) { if (e.target.classList.contains("input-min")) { timeInput[0].value = formatTime(rangeInput[0].value); } else { timeInput[1].value = formatTime(rangeInput[1].value); } return; } if (endTime - startTime <= 0) { if (e.target.classList.contains("input-min")) { rangeInput[0].value = startTime; if (startTime + timeRange > rangeInput[1].max) { rangeInput[1].value = rangeInput[1].max; timeInput[1].value = formatTime(rangeInput[1].max); } else { rangeInput[1].value = startTime + timeRange; timeInput[1].value = formatTime(startTime + timeRange); } } else { rangeInput[1].value = endTime; if (endTime - timeRange > rangeInput[0].min) { rangeInput[0].value = endTime - timeRange; timeInput[0].value = formatTime(rangeInput[0].value); } else { rangeInput[0].value = rangeInput[0].min; timeInput[0].value = formatTime(rangeInput[0].min); } rangeInput[1].value = endTime; } updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max); return; } if (endTime - startTime >= timeGap && endTime <= rangeInput[1].max) { if (endTime - startTime > timeRange) { if (e.target.classList.contains("input-min")) { rangeInput[0].value = startTime; rangeInput[1].value = startTime + timeRange; timeInput[1].value = formatTime(startTime + timeRange); } else { rangeInput[0].value = endTime - timeRange; rangeInput[1].value = endTime; timeInput[0].value = formatTime(endTime - timeRange); } updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max); } else { if (e.target.classList.contains("input-min")) { rangeInput[0].value = startTime; range.style.left = (startTime / rangeInput[0].max) * 100 + "%"; } else { rangeInput[1].value = endTime; range.style.right = 100 - (endTime / rangeInput[1].max) * 100 + "%"; } } } } function updatePercentage(rangeInput1, rangeInput2) { let leftPercentage = rangeInput1 * 100; let rightPercentage = 100 - rangeInput2 * 100; if (leftPercentage > 90) { leftPercentage -= 0.3; } if (rightPercentage > 90) { rightPercentage -= 0.3; } range.style.left = leftPercentage + "%"; range.style.right = rightPercentage + "%"; } function formatTime(milliseconds) { const hours = Math.floor(milliseconds / 3600000); const remainingMillisecondsAfterHours = milliseconds % 3600000; const minutes = Math.floor(remainingMillisecondsAfterHours / 60000); const remainingMillisecondsAfterMinutes = remainingMillisecondsAfterHours % 60000; const seconds = Math.floor(remainingMillisecondsAfterMinutes / 1000); const remainingMilliseconds = remainingMillisecondsAfterMinutes % 1000; const formattedHours = String(hours).padStart(2, '0'); const formattedMinutes = String(minutes).padStart(2, '0'); const formattedSeconds = String(seconds).padStart(2, '0'); const formattedMilliseconds = String(remainingMilliseconds).padStart(3, '0'); return `${formattedHours}:${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`; } function validateTimeFormat(time) { const regex = /^([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d):(\d{1,3})$/; if (!regex.test(time)) { return false; } const match = time.match(regex); if (!match) { return false; } const [, hours, minutes, seconds, milliseconds] = match; return parseInt(milliseconds) % 100 === 0; } function timeToMilliseconds(time) { if (!validateTimeFormat(time)) { return null; } const [, hours, minutes, seconds, milliseconds] = time.match(/^([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d):(\d{1,3})$/); return ( parseInt(hours) * 60 * 60 * 1000 + parseInt(minutes) * 60 * 1000 + parseInt(seconds) * 1000 + parseInt(milliseconds) ); } function resetTime() { if (gifRenderingInProgress || isParsing) { console.log('正在進行中'); return; } rangeInput[0].value = 0; rangeInput[1].value = 15000; startTime = 0; endTime = 15; timeInput[0].value = '00:00:00:000'; timeInput[1].value = '00:00:15:000'; updatePercentage(0 / rangeInput[0].max, 15000 / rangeInput[1].max); } document.body.insertAdjacentHTML('beforeend', popupHTML); // 關閉按鈕事件 document.getElementById('closePopupBtn').addEventListener('click', closePopup); // 設置生成按鈕的事件監聽器 document.getElementById('generateButton').addEventListener('click', function () { if (gifRenderingInProgress || isParsing) { console.log('正在進行中'); return; } resetProgress(); startTime = rangeInput[0].value / 1000; endTime = rangeInput[1].value / 1000; video.currentTime = startTime; video.playbackRate = 0.3; // 減慢影片播放速度至 0.3x,以免遺漏幀 video.muted = true; video.play(); captureFrames(); }); const popup = document.getElementById('popup'); const progressElement = document.getElementById('progress'); const percentage = document.getElementById('percentage'); const resolutionProgressElement = document.getElementById('resolutionProgress'); const resolutionPercentage = document.getElementById('resolutionPercentage'); let isParsing = false; function showPopup() { if (document.querySelector('.video-adHandler-background-blocker')) { return; } popup.style.display = 'flex'; window.scrollTo({ top: 0, behavior: 'smooth' }); } function closePopup() { if (isParsing) { return; } popup.style.display = 'none'; } document.addEventListener('keydown', function (event) { // 當按下 Shift + G if (event.shiftKey && event.code === 'KeyG') { showPopup(); } // 當按下 ESC if (event.key === 'Escape') { closePopup(); } }); window.onload = function () { rangeInput = document.querySelectorAll(".range-input input"), timeInput = document.querySelectorAll(".price-input input"), range = document.querySelector(".slider .progress"), imgsContainer = document.querySelector('.imgs-container'), resetButton = document.querySelector('#reset-btn'); //把GIF圖示插入到Control Bar const targetContainer = document.querySelector('.control-bar-rightbtn'); const newDiv = document.createElement('div'); newDiv.className = 'vjs-menu-button vjs-menu-button-popup vjs-control vjs-button vjs-visible-text vjs-res-button'; newDiv.innerHTML = `<button class="vjs-menu-button vjs-menu-button-popup vjs-button" type="button" aria-disabled="false"aria-haspopup="true" aria-expanded="false" title="GIF"> <span class="vjs-icon-placeholder" aria-hidden="true"></span> <span class="vjs-control-text" aria-live="polite">GIF</span> </button> `; if (targetContainer) { targetContainer.appendChild(newDiv); const button = newDiv.querySelector('button'); button.addEventListener('click', function () { showPopup(); }); } else { console.error('找不到 .control-bar-rightbtn 容器'); } timeInput.forEach((input) => { input.addEventListener("blur", (e) => { handleTimeChange(e); }); input.addEventListener("keydown", function (e) { if (event.keyCode == 13) { e.preventDefault(); handleTimeChange(e); } }); }); rangeInput.forEach((input) => { input.addEventListener("input", (e) => { let minVal = parseInt(rangeInput[0].value), maxVal = parseInt(rangeInput[1].value); if (maxVal - minVal <= timeGap) { if (e.target.className === "range-min") { rangeInput[0].value = maxVal - timeGap; timeInput[0].value = formatTime(maxVal - timeGap); timeInput[1].value = formatTime(maxVal); } else { rangeInput[1].value = minVal + timeGap; timeInput[0].value = formatTime(minVal); timeInput[1].value = formatTime(minVal + timeGap); } } else { if (maxVal - minVal > timeRange) { if (e.target.className === "range-min") { rangeInput[0].value = minVal; rangeInput[1].value = minVal + timeRange; timeInput[0].value = formatTime(minVal); timeInput[1].value = formatTime(minVal + timeRange); } else { rangeInput[0].value = maxVal - timeRange; rangeInput[1].value = maxVal; timeInput[0].value = formatTime(maxVal - timeRange); timeInput[1].value = formatTime(maxVal); } updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max); return; } timeInput[0].value = formatTime(minVal); timeInput[1].value = formatTime(maxVal); updatePercentage(minVal / rangeInput[0].max, maxVal / rangeInput[1].max); } }); }); imgsContainer.addEventListener('click', (e) => { if (e.target.closest('#delete-a')) { const card = e.target.closest('.card'); if (card) { card.remove(); } if (imgsContainer.querySelectorAll('.card').length < 3) { imgsContainer.classList.remove('image-container-override'); } } }); resetButton.addEventListener('click', (e) => { resetTime(); }); }; // 動態創建 <script> 元素並加載 gif.js var script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/gif.min.js'; document.head.appendChild(script); const videoResolutions = [ { width: 1920, height: 1080, label: "1080p" }, { width: 1280, height: 720, label: "720p" }, { width: 960, height: 540, label: "540p" }, { width: 640, height: 360, label: "360p" } ]; const video = document.getElementById('ani_video_html5_api'); let videoDuration = 1420; video.addEventListener('loadedmetadata', () => { if (rangeInput) { rangeInput.forEach((input) => { input.max = Math.floor(video.duration) * 1000; }); updatePercentage(rangeInput[0].value / rangeInput[0].max, rangeInput[1].value / rangeInput[1].max); } }); // 等待 gif.js 加載完成 script.onload = function () { // 檢查 GIF 類是否可用 window.gifList = new Map(); if (typeof GIF !== 'undefined') { let gifLoading = fetch('https://cdn.jsdelivr.net/npm/[email protected]/dist/gif.worker.js') .then((response) => { if (!response.ok) { throw new Error("Network response was not OK"); } window.workerBlob = response.blob(); return window.workerBlob; }).then(workerBlob => { for (let i = 0; i < 4; i++) { let gif = new GIF({ workers: 4, workerScript: URL.createObjectURL(workerBlob), quality: 0, repeat: 0, width: videoResolutions[i].width, height: videoResolutions[i].height, background: '#ffffff' }); gif.on('finished', function (blob) { const gifUrl = URL.createObjectURL(blob); let displayStartTime = formatTime(startTime * 1000); let displayEndTime = formatTime(endTime * 1000); let fileName = document.title.match(/(.+?\[\d+\])/); if (fileName) { fileName = fileName[0] + ' ' + displayStartTime + '-' + displayEndTime; } else { fileName = displayStartTime + '-' + displayEndTime; } const cardHTML = ` <div class="card"> <div class="image_container"> <img class="image" src="${gifUrl}" alt="Image Description" /> </div> <div class="title"> <span>${displayStartTime} - ${displayEndTime}</span> </div> <a href="${gifUrl}" download="${fileName}"> <button class="cart-button"> <span>下載</span> </button> </a> <a id="delete-a"> <button class="cart-button""> <span>刪除</span> </button> </a> </div> `; imgsContainer.innerHTML += cardHTML; window.gifList.get(videoResolutions[i].width).abort(); window.gifList.get(videoResolutions[i].width).frames = []; gifRenderingInProgress = false; if (imgsContainer.querySelectorAll('.card').length >= 3) { imgsContainer.classList.add('image-container-override'); imgsContainer.scrollLeft = imgsContainer.scrollWidth; } }); gif.on('progress', function (progress) { progressElement.style.width = `${Math.round(progress * 100)}%`; // 直接同步進度條 percentage.textContent = `${Math.round(progress * 100)}%`; // 更新百分比顯示 }); window.gifList.set(videoResolutions[i].width, gif); } }).catch(error => console.error("Error loading GIF worker:", error)); } else { console.log('Failed to find GIF class!'); } }; let startTime = 0; let endTime = 15; let gifRenderingInProgress = false; // 用來標記是否正在進行渲染 let lastExpectedDisplayTime = null; let frameDisplayDurations = []; let currentWidth=1920; function captureFrames() { isParsing = true; // 這個回調將每一幀都調用 function frameCallback(now, metadata) { // 確保捕捉只在設定的開始時間之後觸發 if (video.currentTime < startTime) { //如果影片尚未達到開始捕捉的時間,則繼續等待 video.requestVideoFrameCallback(frameCallback); return; } currentWidth= metadata.width; const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = metadata.width; canvas.height = metadata.height; context.drawImage(video, 0, 0, canvas.width, canvas.height); const imageURL = canvas.toDataURL('image/png'); const img = new Image(); img.src = imageURL; const currentExpectedDisplayTime = metadata.mediaTime * 1000; if (lastExpectedDisplayTime !== null) { // 計算前一幀顯示時長 const displayDuration = currentExpectedDisplayTime - lastExpectedDisplayTime; frameDisplayDurations.push(displayDuration); } // 更新上一幀的 expectedDisplayTime lastExpectedDisplayTime = currentExpectedDisplayTime; window.gifList.get(canvas.width).addFrame(img, { delay: 125 }); // 如果影片播放時間達到停止的時間,則停止捕捉 if (video.currentTime >= endTime || video.currentTime >= Math.floor(video.duration)) { generateGif(canvas.width); return; } const progress = (video.currentTime - startTime) / (endTime - startTime); resolutionProgressElement.style.width = `${Math.round(progress * 100)}%`; resolutionPercentage.textContent = `${Math.round(progress * 100)}%`; // 持續捕捉每一幀 video.requestVideoFrameCallback(frameCallback); } // 等待影片達到開始捕捉的時間,然後開始捕捉 video.requestVideoFrameCallback(frameCallback); } function resetProgress() { progressElement.style.width = `${0}%`; // 直接同步進度條 percentage.textContent = `${0}%`; // 更新百分比顯示 resolutionProgressElement.style.width = `${0}%`; resolutionPercentage.textContent = `${0}%`; } video.addEventListener('ended', () => { if(!window.gifList.get(currentWidth).running){ generateGif(currentWidth); } }); function generateGif(videoWidth){ frameDisplayDurations.push(frameDisplayDurations[frameDisplayDurations.length - 1]); const averageFrameDisplayDuration = frameDisplayDurations.reduce((total, duration) => total + duration, 0) / frameDisplayDurations.length; video.playbackRate = 1; //調整影片為正常撥放速率 isParsing = false; for (let i = 0; i < window.gifList.get(videoWidth).frames.length; i++) { window.gifList.get(videoWidth).frames[i].delay = averageFrameDisplayDuration; } resolutionProgressElement.style.width = `${100}%`; resolutionPercentage.textContent = `${100}%`; window.gifList.get(videoWidth).render(); gifRenderingInProgress = true; frameDisplayDurations = []; lastExpectedDisplayTime = null; video.muted = false; }; })();