您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
SOOP 라이브 및 클립 창에서 화면 캡쳐시 플레이어 컨트롤러 부분을 보이지 않게 할 수 있도록 컨트롤러 비활성화/캡쳐 버튼을 추가합니다. (단축키: Alt+L 컨트롤러 활성화/비활성화, Alt+P 컨트롤러 자동으로 숨기고 캡쳐)
当前为
// ==UserScript== // @name SOOP 클립 및 라이브 깔끔하게 캡쳐 // @namespace http://tampermonkey.net/ // @version 1.52 // @license MIT // @description SOOP 라이브 및 클립 창에서 화면 캡쳐시 플레이어 컨트롤러 부분을 보이지 않게 할 수 있도록 컨트롤러 비활성화/캡쳐 버튼을 추가합니다. (단축키: Alt+L 컨트롤러 활성화/비활성화, Alt+P 컨트롤러 자동으로 숨기고 캡쳐) // @author Linseed, Gemini // @match https://stbbs.sooplive.co.kr/* // @match https://play.sooplive.co.kr/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_addValueChangeListener // @require https://github.com/PRO-2684/GM_config/releases/download/v1.2.1/config.min.js#md5=525526b8f0b6b8606cedf08c651163c2 // ==/UserScript== (function() { 'use strict'; const configDesc = { "$default": { autoClose: false }, saveMethod: { name: "Save as / 저장방식", type: "enum", options: ["Clipboard", "File", "Both"], value: 0 // 기본값을 clipboard로 설정 } } const config = new GM_config(configDesc, { immediate: false }); // --- 스크립트 설정 --- const CHECK_INTERVAL = 500; // 플레이어 요소를 찾기 위한 반복 확인 간격 (ms) /** * 필요한 DOM 요소들이 로드될 때까지 기다립니다. */ const waitForElements = setInterval(() => { const videoLayer = document.getElementById('videoLayer'); const ctrlBox = document.querySelector('.player_ctrlBox'); const titleElement = document.querySelector('.u_clip_title'); const nicknameElement = document.querySelector('.nickname'); const playerInfo = document.getElementById('player_info'); const viewCtrl = document.querySelector('.view_ctrl'); if (videoLayer && ctrlBox && (titleElement || nicknameElement)) { clearInterval(waitForElements); main(videoLayer, ctrlBox, titleElement, nicknameElement, playerInfo, viewCtrl); } }, CHECK_INTERVAL); /** * 스크립트의 메인 로직을 실행합니다. */ function main(videoLayer, ctrlBox, titleElement, nicknameElement, playerInfo, viewCtrl) { let isLocked = false; const container = document.createElement('div'); container.id = 'soop-script-container'; if (titleElement) { titleElement.after(container); } else if (nicknameElement) { nicknameElement.after(container); } const lockButton = document.createElement('button'); lockButton.id = 'lockBtn'; lockButton.textContent = '🔓'; lockButton.title = '컨트롤러 잠금/해제 (Alt + L)'; container.appendChild(lockButton); const captureButton = document.createElement('button'); captureButton.id = 'captureBtn'; captureButton.textContent = '📷'; captureButton.title = '현재 화면 캡쳐 (Alt + P)'; container.appendChild(captureButton); GM_addStyle(` #soop-script-container { display: inline-flex; gap: 8px; margin-left: 10px; vertical-align: middle; } #soop-script-container button { padding: 4px 8px; font-size: 16px; border: 1px solid #ccc; border-radius: 6px; background-color: rgba(240, 240, 240, 0.85); cursor: pointer; box-shadow: 0 1px 3px rgba(0,0,0,0.1); transition: all 0.2s ease-in-out; line-height: 1; } #soop-script-container button:hover { background-color: rgba(255, 255, 255, 1); transform: translateY(-1px); } `); const setLockState = (locked) => { isLocked = locked; lockButton.textContent = isLocked ? '🔒' : '🔓'; ctrlBox.style.display = isLocked ? 'none' : ''; if (playerInfo) playerInfo.style.display = isLocked ? 'none' : ''; if (viewCtrl) viewCtrl.style.display = isLocked ? 'none' : ''; }; lockButton.addEventListener('click', () => setLockState(!isLocked)); captureButton.addEventListener('click', async () => { const originalLockState = isLocked; if (!originalLockState) { setLockState(true); await new Promise(resolve => setTimeout(resolve, 100)); } try { const videoElement = videoLayer.querySelector('video'); if (!videoElement) throw new Error('플레이어에서 <video> 요소를 찾을 수 없습니다.'); const canvas = document.createElement('canvas'); canvas.width = videoElement.videoWidth; canvas.height = videoElement.videoHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); // --- 설정값에 따라 저장 방식 분기 --- const saveMethod = config.get('saveMethod'); const saveToClipboard = (canvasEl) => { return new Promise((resolve, reject) => { canvasEl.toBlob(async (blob) => { if (blob) { try { await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); resolve(); } catch (err) { reject(err); } } else { reject(new Error('Canvas toBlob returned null.')); } }, 'image/png'); }); }; const saveToFile = (canvasEl) => { const link = document.createElement('a'); const timestamp = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, ''); link.download = `soop-capture-${timestamp}.png`; link.href = canvasEl.toDataURL('image/png'); link.click(); }; if (saveMethod == 0 || saveMethod == 2) { try { await saveToClipboard(canvas); } catch(e) { console.error('클립보드 저장 실패:', e); alert('클립보드 저장에 실패했습니다. 브라우저 콘솔을 확인해주세요.'); } } if (saveMethod == 1 || saveMethod == 2) { saveToFile(canvas); } } catch (error) { console.error('스크립트 캡쳐 오류:', error); alert('화면 캡쳐에 실패했습니다. 비디오가 다른 도메인에서 재생되는 경우(CORS) 캡쳐가 불가능할 수 있습니다. 브라우저 콘솔을 확인해주세요.'); } finally { if (!originalLockState) { setLockState(false); } } }); document.addEventListener('keydown', (event) => { if (!event.altKey) return; switch (event.key.toLowerCase()) { case 'l': event.preventDefault(); lockButton.click(); break; case 'p': event.preventDefault(); captureButton.click(); break; } }); } })();