mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-14 08:26:16 -08:00
* feat: support batch video upscaling with folder upload and directory save - Add multi-file selection and entire folder upload (webkitdirectory) - Support drag & drop of both files and folders via FileSystemEntry API - Add save directory picker using File System Access API (showDirectoryPicker) - Process videos sequentially with per-file progress and status tracking - Add right-side result list with click-to-compare functionality - Add per-video duration and total batch duration display - Add retry button for failed items with error detail display - Remove auto-play on comparison viewer load - Fix worker error handling: handle "error" command to reject stale promises - Fix event listener accumulation in video comparison (AbortController) - Fix XSS risk by escaping HTML in all innerHTML injections - Fix URL revocation timing in download to prevent large file truncation - Extract writeToSaveDirectory and truncateName helpers to reduce duplication - Add Usage Guide panel explaining single/multiple/folder upload options Files changed: docs/upscaler/index.html (+1110/-181) docs/upscaler/upscaler.js (+110) * fix: prevent auto-play and resource consumption during upscaling - Set preload="metadata" on preview and comparison video elements - Explicitly pause preview video after setting src in showVideoPreview - Remove preview video seek sync from batch processing progress callbacks (was causing continuous decode during upscaling, wasting GPU resources) - Remove preview video seek sync from retry progress callback - All videos now require manual play; no background resource usage * fix: improve upscaled video quality with higher bitrate and codec support - Raise base bitrate from 2.5 Mbps (25e5) to 8 Mbps (8e6) for standard and firefox processors, preventing quality loss when source videos have high bitrates (e.g. 720p @ 16.7 Mbps was producing 1440p @ 10 Mbps, smaller than the original file) - Double the bitrate for mediabunny processor (X.n1 * 2) to account for the 4x pixel increase from 2x upscaling - Add H.264 Level 5.1 (avc1.640033) and Level 5.2 (avc1.640034) codecs to the fallback lists, since Level 4.0 (avc1.640028, ~24.6 Mbps) cannot support the higher bitrates for 1440p+ outputs - Add latencyMode: "quality" to VideoEncoder configs for both standard and firefox processors, prioritizing encoding quality over latency (suitable for offline processing) * feat: add video data comparison panel for original vs upscaled metrics - Add a comparison data table below the file info bar showing 6 metrics: Resolution, File Size, Duration, Est. Bitrate, Pixels, Process Time - Display per-metric change percentage with color-coded indicators: green (↑) for improvements, red (↓) for regressions, gray for neutral - Estimated bitrate calculated from file size × 8 / video duration - Table renders after both original and upscaled video metadata loads - Auto-hide panel on reset * fix: make video data comparison panel full-width and responsive - Move comparison data table from inside the left column to the full-width area below the left/right layout, so it is no longer constrained by the comparison viewer column width - Remove table-fixed colgroup and overflow-x-auto that caused horizontal scrolling on smaller screens - Table now uses w-full auto-layout, displaying all 4 columns (Metric, Original, Upscaled, Change) without requiring horizontal scrolling --------- Co-authored-by: rionzhao <rionzhao@tencent.com>
125 lines
4.2 KiB
JavaScript
125 lines
4.2 KiB
JavaScript
/*!
|
|
* artplayer-plugin-vtt-thumbnail.js v1.1.0
|
|
* Github: https://github.com/zhw2590582/ArtPlayer
|
|
* (c) 2017-2026 Harvey Zhao
|
|
* Released under the MIT License.
|
|
*/
|
|
function padEnd(str, targetLength, padString) {
|
|
if (str.length > targetLength) {
|
|
return String(str);
|
|
} else {
|
|
targetLength = targetLength - str.length;
|
|
if (targetLength > padString.length) {
|
|
padString += padString.repeat(targetLength / padString.length);
|
|
}
|
|
return String(str) + padString.slice(0, targetLength);
|
|
}
|
|
}
|
|
function t2d(time) {
|
|
const arr = time.split(".");
|
|
const left = arr[0].split(":") || [];
|
|
const right = padEnd(arr[1] || "0", 3, "0");
|
|
const ms = Number(right) / 1e3;
|
|
const h = Number(left[left.length - 3] || 0) * 3600;
|
|
const m = Number(left[left.length - 2] || 0) * 60;
|
|
const s = Number(left[left.length - 1] || 0);
|
|
return h + m + s + ms;
|
|
}
|
|
async function getVttArray(vttUrl = "") {
|
|
const vttString = await (await fetch(vttUrl)).text();
|
|
const lines = vttString.split(/[\n\r]/g).filter((item) => item.trim());
|
|
const vttArray = [];
|
|
for (let i = 1; i < lines.length; i += 2) {
|
|
const time = lines[i];
|
|
const text = lines[i + 1];
|
|
if (!text.trim())
|
|
continue;
|
|
const timeReg = /((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/;
|
|
const timeMatch = time.match(timeReg);
|
|
const textReg = /(.*)#(\w{4})=(.*)/;
|
|
const textMatch = text.match(textReg);
|
|
const start = Math.floor(t2d(timeMatch[1]));
|
|
const end = Math.floor(t2d(timeMatch[2]));
|
|
let url = textMatch[1];
|
|
const isAbsoluteUrl = /^\/|(?:https?|ftp|file):\/\//i.test(url);
|
|
if (!isAbsoluteUrl) {
|
|
const urlArr = vttUrl.split("/");
|
|
urlArr.pop();
|
|
urlArr.push(url);
|
|
url = urlArr.join("/");
|
|
}
|
|
const result = { start, end, url };
|
|
const keys = textMatch[2].split("");
|
|
const values = textMatch[3].split(",");
|
|
for (let j = 0; j < keys.length; j++) {
|
|
result[keys[j]] = values[j];
|
|
}
|
|
vttArray.push(result);
|
|
}
|
|
return vttArray;
|
|
}
|
|
function artplayerPluginVttThumbnail(option) {
|
|
return async (art) => {
|
|
const {
|
|
constructor: {
|
|
utils: { setStyle, isMobile, addClass }
|
|
},
|
|
template: { $progress }
|
|
} = art;
|
|
let timer = null;
|
|
const thumbnails = await getVttArray(option.vtt);
|
|
function showThumbnails($control, find, width) {
|
|
setStyle($control, "backgroundImage", `url(${find.url})`);
|
|
setStyle($control, "height", `${find.h}px`);
|
|
setStyle($control, "width", `${find.w}px`);
|
|
setStyle($control, "backgroundPosition", `-${find.x}px -${find.y}px`);
|
|
if (width <= find.w / 2) {
|
|
setStyle($control, "left", 0);
|
|
} else if (width > $progress.clientWidth - find.w / 2) {
|
|
setStyle($control, "left", `${$progress.clientWidth - find.w}px`);
|
|
} else {
|
|
setStyle($control, "left", `${width - find.w / 2}px`);
|
|
}
|
|
}
|
|
art.controls.add({
|
|
name: "vtt-thumbnail",
|
|
position: "top",
|
|
index: 20,
|
|
style: option.style || {},
|
|
mounted($control) {
|
|
addClass($control, "art-control-thumbnails");
|
|
art.on("setBar", async (type, percentage, event) => {
|
|
const isMobileDroging = type === "played" && event && isMobile;
|
|
if (type === "hover" || isMobileDroging) {
|
|
const width = $progress.clientWidth * percentage;
|
|
const second = percentage * art.duration;
|
|
setStyle($control, "display", "flex");
|
|
const find = thumbnails.find((item) => second >= item.start && second <= item.end);
|
|
if (!find)
|
|
return setStyle($control, "display", "none");
|
|
if (width > 0 && width < $progress.clientWidth) {
|
|
showThumbnails($control, find, width);
|
|
} else {
|
|
if (!isMobile) {
|
|
setStyle($control, "display", "none");
|
|
}
|
|
}
|
|
if (isMobileDroging) {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
setStyle($control, "display", "none");
|
|
}, 500);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return {
|
|
name: "artplayerPluginVttThumbnail"
|
|
};
|
|
};
|
|
}
|
|
export {
|
|
artplayerPluginVttThumbnail as default
|
|
};
|