import { getFriendlyTitle } from '../title'; const canvas = document.createElement("canvas"); class Screenshot { constructor(video, videoTime) { this.url = ""; this.timeStamp = new Date().getTime(); this.video = video; this.videoTime = videoTime; // this.url = URL.createObjectURL(this.blob); this.createUrl(); } async createUrl() { canvas.width = this.video.videoWidth; canvas.height = this.video.videoHeight; const context = canvas.getContext("2d"); if (context === null) { throw new Error("视频截图失败: canvas 未创建或创建失败."); } context.drawImage(this.video, 0, 0); canvas.toBlob(blob => { if (blob === null) { throw new Error("视频截图失败: 创建 blob 失败."); } this.blob = blob; this.url = URL.createObjectURL(blob); }, "image/png"); } get filename() { return `${getFriendlyTitle()} @${this.videoTime.toString()}:${this.timeStamp.toString()}.png`; } get id() { return this.videoTime.toString() + this.timeStamp.toString(); } revoke() { URL.revokeObjectURL(this.url); } } export const takeScreenshot = (video) => { const time = video.currentTime; return new Screenshot(video, time); // return new Promise((resolve, reject) => // { // const context = canvas.getContext("2d"); // if (canvas === null || context === null) // { // reject("视频截图失败: canvas 未创建或创建失败."); // return; // } // context.drawImage(video, 0, 0); // canvas.toBlob(blob => // { // if (blob === null) // { // reject("视频截图失败: 创建 blob 失败."); // return; // } // resolve(new Screenshot(blob, time)); // }, "image/png"); // }); }; resources.applyStyle("videoScreenshotStyle"); document.body.insertAdjacentHTML("beforeend", /*html*/ `
`); Vue.component("video-screenshot", { props: { objectUrl: String, filename: String, }, template: /*html*/ `
`, methods: { discard() { this.$emit("discard"); }, save() { this.$el.querySelector(".link").click(); this.discard(); }, }, }); const screenShotsList = new Vue({ el: ".video-screenshot-container", data: { screenshots: [], batchFilename: getFriendlyTitle() + ".zip", }, methods: { discard(screenshot) { this.screenshots.splice(this.screenshots.indexOf(screenshot), 1); screenshot.revoke(); }, async saveAll() { const zip = new JSZip(); this.screenshots.forEach((it) => { zip.file(it.filename, it.blob, { date: new Date(it.timeStamp), }); }); const blob = await zip.generateAsync({ type: "blob" }); const link = this.$el.querySelector(".batch-link"); link.href = URL.createObjectURL(blob); link.click(); URL.revokeObjectURL(link.href); link.href = ""; this.discardAll(); }, discardAll() { this.screenshots.forEach((it) => it.revoke()); this.screenshots = []; }, }, computed: { showBatch() { return this.screenshots.length >= 2; }, }, }); Observer.videoChange(async () => { const video = await SpinQuery.select("#bofqi video"); const time = await SpinQuery.select(".bilibili-player-video-time"); if (time === null || document.querySelector(".video-take-screenshot")) { return; } time.insertAdjacentHTML("afterend", /*html*/ `
`); const screenshotButton = document.querySelector(".video-take-screenshot"); screenshotButton.addEventListener("click", () => { const screenshot = takeScreenshot(video); screenShotsList.screenshots.unshift(screenshot); }); document.addEventListener("keydown", e => { if (document.activeElement && ["input", "textarea"].includes(document.activeElement.nodeName.toLowerCase())) { return; } if (e.ctrlKey && e.altKey && e.key.toLowerCase() === "c") { e.stopPropagation(); e.preventDefault(); screenshotButton.click(); } }); }); export default { export: { takeScreenshot, screenShotsList, }, unload: () => document.querySelectorAll(".bilibili-player-video-control-bottom .video-take-screenshot,.video-screenshot-container") .forEach(it => it.setAttribute("style", "display: none !important")), reload: () => document.querySelectorAll(".bilibili-player-video-control-bottom .video-take-screenshot,.video-screenshot-container") .forEach(it => it.setAttribute("style", "display: flex !important")), };