Skip to content

Instantly share code, notes, and snippets.

@Rplus
Last active Feb 8, 2021
Embed
What would you like to do?
巴哈姆特動畫瘋影片擷圖小工具

巴哈姆特動畫瘋影片擷圖小工具

在動畫瘋的頁面可針對影片(不含彈幕)擷圖。

程式安裝連結:
https://greasyfork.org/zh-TW/scripts/420561

0. 安裝前置:

這 script 是依靠 Tampermonkey extension 運作的
請確認已安裝好相應套件

1. 使用

影片載入後,

  1. 點擊影片控制列上的擷圖鈕即可自動儲存擷圖
  2. 也可 Alt + S鍵 擷圖 (Mac 請改用 Option + S)
  3. 逐幀微調影片: 往前 Alt + - & 往後 Alt + +

圖片檔名會是: 影片標題_時-分-秒.jpg

PUI PUI 天竺鼠車車 [4]_01-03-32.jpg

2. 設定

若需要調整存檔類型(jpg/png),以及壓縮率 可於瀏覽器右上角點擊切換 與 輸入壓縮率數值




🔖 Bookmarklet 形式

不想安裝的額外套件的,也可直接用 bookmarklet 形式
將擷圖程式放在書籤列,點擊書籤時進行擷圖並存檔

因為 Gist 不能將 javascript 塞進連結裡,請自行依下列步驟處理

  1. 請先將下面連結拖至書籤列 動畫瘋擷圖
  2. 滑鼠右鍵編輯剛剛的書籤,將網址那欄改為下面(bookmarklet)的程式碼
  3. 要擷圖時點一下書籤就可以存檔了

ps: 若需要調整擷圖存檔類型或壓縮率
請自行調整最上面那區塊的 option 數值

javascript: (function() {
let option = {
fileExt: 'jpg', /* png or jpg */
mimeType: 'image/jpeg', /* image/png or image/jpeg */
compressRatio: 0.95, /* 0 ~ 1 (1:best, 0: worst) */
};
let video = getVideo();
let title = document.querySelector('h1')?.textContent || document.title;
if (!video) {
alert('影片尚未準備好');
return;
}
screenshot(video, title);
function screenshot(video, title) {
const currentTimeStr = new Date(video.currentTime * 1000).toISOString().slice(11, 19).replace(/\:/g, '-');
const fn = title + '_' + currentTimeStr + '.' + option.fileExt;
saveImage(getImgDataUrl(video), fn);
}
function getVideo() {
return document.getElementById('ani_video_html5_api') || document.querySelector('video');
}
function getImgDataUrl(videoEl, scale = window.devicePixelRatio || 1) {
const canvas = document.createElement('canvas');
canvas.width = videoEl.videoWidth * scale;
canvas.height = videoEl.videoHeight * scale;
canvas.getContext('2d').drawImage(videoEl, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL(option.mimeType, option.compressRatio);
}
function saveImage(imgSrc, filename) {
var link = window.document.createElement('a');
link.href = imgSrc;
link.target = '_img';
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
})();
// ==UserScript==
// @name 巴哈姆特動畫瘋影片擷圖小工具
// @namespace http://tampermonkey.net/
// @version 2.0
// @description video screenshot on ani.gamer.com.tw
// @author Rplus
// @match https://ani.gamer.com.tw/animeVideo.php?sn=*
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
let video = getVideo();
let title = '';
let option = {
fileExt: 'jpg',
mimeType: 'image/jpeg',
compressRatio: 0.95,
};
unsafeWindow.addEventListener('load', () => {
video = getVideo();
if (video) {
init();
} else {
console.log('load video GG, wait 5s');
delayInit();
}
});
function delayInit() {
setTimeout(() => {
if (!unsafeWindow.videojs) {
delayInit();
} else {
init();
}
}, 1000);
}
function init() {
video = getVideo();
title = document.querySelector('h1')?.textContent || document.title;
console.log('load');
if (!video) { return; }
document.addEventListener('keydown', handleKeyDown);
injectScreenshotBtn();
}
function handleKeyDown(e) {
if (!e.altKey) { return; }
switch (e.code) {
case 'KeyS':
screenshot(video, title);
break;
case 'Equal':
shiftVideoFrame(1);
break;
case 'Minus':
shiftVideoFrame(-1);
break;
default:
break;
}
}
function shiftVideoFrame(dir = 1) {
video.currentTime += (dir / 60);
}
function injectScreenshotBtn() {
const bar = document.querySelector('.control-bar-rightbtn');
if (!bar) { return; }
const btn = document.createElement('div');
btn.className = 'vjs-button vjs-control vjs-playback-rate';
btn.innerHTML = `<div class="vjs-playback-rate-value">擷圖</div>`
btn.addEventListener('click', () => screenshot(video, title));
bar.appendChild(btn);
}
function screenshot(video, title) {
const currentTimeStr = new Date(video.currentTime * 1000).toISOString().slice(11, 19).replace(/\:/g, '-');
const fn = title + '_' + currentTimeStr + '.' + option.fileExt;
saveImage(getImgDataUrl(video), fn);
}
function getVideo() {
return document.getElementById('ani_video_html5_api') || document.querySelector('video');
}
function getImgDataUrl(videoEl, scale = unsafeWindow.devicePixelRatio || 1) {
const canvas = document.createElement('canvas');
canvas.width = videoEl.videoWidth * scale;
canvas.height = videoEl.videoHeight * scale;
canvas.getContext('2d').drawImage(videoEl, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL(option.mimeType, option.compressRatio);
}
function saveImage(imgSrc, filename) {
var link = document.createElement('a');
link.href = imgSrc;
link.target = '_img';
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
GM_registerMenuCommand('存檔格式設定為 JPG', updateConfig('jpg'), 'J');
GM_registerMenuCommand('存檔格式設定為 PNG', updateConfig('png'), 'P');
GM_registerMenuCommand('調整圖片壓縮率', udpateCompressRatio, 'C');
function updateConfig(type) {
return () => {
switch (type) {
case 'jpg':
option.fileExt = 'jpg';
option.mimeType = 'image/jpeg';
break;
case 'png':
option.fileExt = 'png';
option.mimeType = 'image/png';
break;
default:
break;
}
};
}
function udpateCompressRatio() {
let value = unsafeWindow.prompt(`調整圖片壓縮率(0: 最差,1: 最佳)`, option.compressRatio);
if (!isNaN(value)) {
value = Number(value)
if (value <= 1 && value >= 0) {
option.compressRatio = value;
} else {
unsafeWindow.alert(`輸入錯誤壓縮率: ${value}。\n請使用合格數字區間`);
}
} else {
unsafeWindow.alert(`輸入錯誤壓縮率: ${value}。\n請使用數字格式`);
}
}
})();
@penut85420
Copy link

penut85420 commented Jan 26, 2021

想問為什麼是存 jpg 而不是 png 呢?

@Rplus
Copy link
Author

Rplus commented Jan 26, 2021

png 也可以呀,但擷圖檔案會變肥很多哩

jpg 在固定寬高下,檔案大小會在一個較接近的數值
png 就會因為像素複雜度而有較大的起伏,但總歸會大於 jpg
但通常人眼看不太出來差異

jpg png
185kb 1000kb
145kb 839kb

@penut85420
Copy link

penut85420 commented Jan 26, 2021

@Rplus 感謝解釋,雖然瞭解 jpg 相比 png 是較為輕量的選擇,即便在「動畫截圖」這個情境下 png 的優勢較不明顯,但我想有些使用者還是比較偏好 png 格式。雖然動手修 code 不算太難,但是否能提供使用者選擇 png 的選項? 不知道能不能做到提供參數設定,或者再維護一份專門存 png 的 gist

@Rplus
Copy link
Author

Rplus commented Jan 27, 2021

更新個可以簡單調整存檔的設定方式

bookmarklet 因為是單次執行,
若需要調整請自行修改最上面那塊的 option 設定後再更新書籤裡的網址~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment