YouTube動画の埋め込み風画像を作成するUserScript
ChatGPT-4oに頼って作った
https://scrapbox.io/files/682487d29b7cb100de139f20.png https://www.youtube.com/watch?v=p9zKRVokNDA
共有ボタンを右クリックしてアラートが表示されたら出来ているはず
YouTubeのレイアウト変わってもプロパティが変わらなければおそらく使える
再生ページの言語が日本語設定であることを想定してます
動かなかったら自分で変えてくださいな
Tampermonkey
https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ja
code:Tampermonkey
// ==UserScript==
// @name Create an image that looks like a YouTube embed
// @namespace http://tampermonkey.net/
// @version 1.1
// @description YouTube動画の埋め込み風の画像を自動で作成する、共有ボタンを右クリックするとクリップボードにコピーされます
// @author ChatGPT-4o (Free)
// @match *://*.youtube.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const observer = new MutationObserver(() => {
const shareButton = document.querySelector('yt-button-view-model.ytd-menu-renderer buttonaria-label="共有"');
if (shareButton && !shareButton.dataset.clickHooked) {
shareButton.addEventListener('contextmenu', function (e) {
e.preventDefault();
const titleElement = document.querySelector('div#title.ytd-watch-metadata yt-formatted-string');
const titleText = titleElement ? titleElement.getAttribute('title') || '' : '';
const imgElement = document.querySelector('div#top-row img#img');
const imgSrcSmall = imgElement ? imgElement.getAttribute('src') : null;
const urlParams = new URLSearchParams(window.location.search);
const videoId = urlParams.get('v');
if (videoId) {
const thumbnailUrl = https://img.youtube.com/vi/${videoId}/sddefault.jpg;
const oldCanvas = document.getElementById('thumbnailCanvas');
if (oldCanvas) {
oldCanvas.remove();
}
const imgMain = new Image();
imgMain.crossOrigin = "anonymous";
imgMain.src = thumbnailUrl;
imgMain.onload = function () {
const cropTop = 60;
const cropBottom = 60;
const croppedHeight = imgMain.height - cropTop - cropBottom;
const canvas = document.createElement('canvas');
canvas.id = 'thumbnailCanvas';
const ctx = canvas.getContext('2d');
canvas.width = imgMain.width;
canvas.height = croppedHeight;
ctx.drawImage(imgMain, 0, cropTop, imgMain.width, croppedHeight, 0, 0, imgMain.width, croppedHeight);
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0.6)');
gradient.addColorStop(0.25, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
const drawLogo = () => {
const pathData1 = "M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z";
const pathData2 = "M 45,24 L 27,14 L 27,34 Z";
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const scaleFactor = 1;
ctx.save();
ctx.translate(centerX - 34, centerY - 24);
ctx.scale(scaleFactor, scaleFactor);
const path1 = new Path2D(pathData1);
const path2 = new Path2D(pathData2);
ctx.fillStyle = '#f03';
ctx.fill(path1);
ctx.fillStyle = '#fff';
ctx.fill(path2);
ctx.restore();
};
const drawTextWithEllipsis = (text, x, y, maxWidth) => {
const maxTextWidth = maxWidth || canvas.width - x - 10;
let textWidth = ctx.measureText(text).width;
if (textWidth > maxTextWidth) {
let truncatedText = text;
while (ctx.measureText(truncatedText + '...').width > maxTextWidth) {
truncatedText = truncatedText.slice(0, -1);
}
truncatedText += '...';
ctx.fillText(truncatedText, x, y);
} else {
ctx.fillText(text, x, y);
}
};
const drawCircularImageAndText = () => {
if (imgSrcSmall) {
const imgSmall = new Image();
imgSmall.crossOrigin = "anonymous";
imgSmall.src = imgSrcSmall;
imgSmall.onload = function () {
const x = 12;
const y = 12;
const size = 40;
ctx.save();
ctx.beginPath();
ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
ctx.drawImage(imgSmall, x, y, size, size);
ctx.restore();
ctx.font = '18px "YouTube Noto", Roboto, Arial, Helvetica, sans-serif';
ctx.fillStyle = 'white';
ctx.textBaseline = 'top';
drawTextWithEllipsis(titleText, x + size + 10, y + 12, 420);
copyCanvasToClipboard(canvas, titleText);
};
} else {
ctx.font = '18px "YouTube Noto", Roboto, Arial, Helvetica, sans-serif';
ctx.fillStyle = 'white';
ctx.textBaseline = 'top';
drawTextWithEllipsis(titleText, 10, 12, 420);
copyCanvasToClipboard(canvas, titleText);
}
};
drawLogo();
const container = document.querySelector('div#container');
if (container && container.parentNode) {
container.parentNode.insertBefore(canvas, container.nextSibling);
}
drawCircularImageAndText();
};
}
});
shareButton.dataset.clickHooked = 'true';
}
});
observer.observe(document.body, { childList: true, subtree: true });
async function copyCanvasToClipboard(canvas, titleText) {
try {
canvas.toBlob(async function (blob) {
const item = new ClipboardItem({ 'image/png': blob });
await navigator.clipboard.write(item);
alert('クリップボードにコピーしました!\n' + titleText);
}, 'image/png');
} catch (e) {
console.error(e);
}
}
})();
#UserScript #Tampermonkey #YouTube #Scrapbox