您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download Panopto video captions easily.
// ==UserScript== // @name Panopto Caption Downloader // @namespace https://github.com/Joshua-Baca // @version 1.0 // @copyright 2024, Panopto Caption Downloader // @license MIT // @description Download Panopto video captions easily. // @icon https://www.panopto.com/wp-content/themes/panopto/library/images/favicons/favicon-96x96.png // @author Joshua-Baca // @match https://*.panopto.com/Panopto/Pages/Viewer.aspx?id=* // @match https://*.panopto.com/Panopto/Pages/Viewer.aspx?pid=* // @match https://*.panopto.eu/Panopto/Pages/Viewer.aspx?id=* // @match https://*.panopto.eu/Panopto/Pages/Viewer.aspx?pid=* // @grant GM_xmlhttpRequest // ==/UserScript== (function() { 'use strict'; // Defined the available languages for caption download. const languages = [ "English (United States)", "English (United Kingdom)", "Español (México) [Spanish]", "Español (España) [Spanish]", "Deutsch [German]", "Français [French]", "Nederlands [Dutch]", "ไทย [Thai]", "简体中文 [Simplified Chinese]", "繁體中文 [Traditional Chinese]", "한국어 [Korean]", "日本語 [Japanese]", "Русский [Russian]", "Português [Portuguese]", "Język polski [Polish]", "English (Australia)", "Dansk [Danish]", "Suomi [Finnish]", "Magyar [Hungarian]", "Norsk [Norwegian]", "Svenska [Swedish]", "Italiano [Italian]", "Cymraeg [Welsh]", "Default" ]; // Extract 'id' and 'pid' parameters from the URL to determine which video's captions to download. const urlParams = new URLSearchParams(window.location.search); const id = urlParams.get('id') || ''; const pid = urlParams.get('pid') || ''; // Construct the URL for downloading the captions based on the video's ID and selected language. const constructCaptionsUrl = (languageIndex) => { // Constructs the URL dynamically based on whether 'pid' or 'id' is present. let url = `https://${window.location.hostname}/Panopto/Pages/Transcription/GenerateSRT.ashx`; url += pid ? `?pid=${pid}` : `?id=${id}`; if (languageIndex != languages.length - 1) { url += `&language=${languageIndex}`; } return url; }; // Function to download the captions, with an option to remove timestamps. const downloadCaptions = (url, filename, removeTimestamps) => { // Makes a request to download the captions and optionally removes timestamps from the content. GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { let content = response.responseText; if (removeTimestamps) { content = content.replace(/^\d+\s+\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}\s+/gm, ''); } const blob = new Blob([content], { type: 'text/plain' }); const downloadUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = filename || 'download.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(downloadUrl); } }); }; // Displays a modal window for the user to interact with, including selecting language and downloading captions. const showModal = () => { // Checks if the modal already exists, if not, creates and configures it. let modal = document.getElementById('panoptoCaptionsModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'panoptoCaptionsModal'; modal.style = `position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 60%; height: 70%; background-color: white; z-index: 10000; overflow: auto; border-radius: 8px; padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: space-between; border: 2px solid black;`; // Instructions for the user. const instructionText = document.createElement('p'); instructionText.innerHTML = "If your language isn't showing up, select 'Default' as this may be how the captions were processed.<br>There are current issues with gathering captions from playlists, this is still work in progress. <br> Please select the language of the captions you want to download: "; instructionText.style = `text-align: center; width: 100%; margin-bottom: 20px;`; // Dropdown for language selection. const languageDropdown = document.createElement('select'); languages.forEach((lang, index) => { let option = new Option(lang, index.toString()); languageDropdown.options.add(option); }); // Label element associated with the checkbox input. const removeTimestampsLabel = document.createElement('label'); removeTimestampsLabel.htmlFor = 'removeTimestamps'; removeTimestampsLabel.textContent = "Click here to remove timestamps from download"; removeTimestampsLabel.style = `display: block; margin-top: 10px;`; // Checkbox for the user to choose whether to remove timestamps. const removeTimestampsCheckbox = document.createElement('input'); removeTimestampsCheckbox.type = 'checkbox'; removeTimestampsCheckbox.id = 'removeTimestamps'; removeTimestampsCheckbox.style = 'margin-bottom: 10px;'; /* Creates an iframe element. The iframe is used to display a preview of the captions within the modal, allows the user to see the captions before downloading.*/ const iframe = document.createElement('iframe'); iframe.style = `width: 100%; height: calc(100% - 60px); border: none; margin-top: 20px;`; // Button to initiate the download of captions. const downloadBtn = document.createElement('button'); downloadBtn.textContent = 'Download Captions'; downloadBtn.onclick = () => { const languageIndex = languageDropdown.value; const captionsUrl = constructCaptionsUrl(languageIndex); const videoTitleElement = document.getElementById('deliveryTitle'); const videoTitle = videoTitleElement ? videoTitleElement.innerText.trim() : 'DefaultTitle'; const sanitizedTitle = videoTitle.replace(/[^a-zA-Z0-9]/g, '_') + '.txt'; const removeTimestamps = removeTimestampsCheckbox.checked; downloadCaptions(captionsUrl, sanitizedTitle, removeTimestamps); }; // Close button to dismiss the modal. const closeButton = document.createElement('button'); closeButton.textContent = 'Close'; closeButton.onclick = () => { modal.style.display = 'none'; }; closeButton.style = `margin-top: 20px;`; modal.appendChild(instructionText); modal.appendChild(languageDropdown); modal.appendChild(removeTimestampsLabel); modal.appendChild(removeTimestampsCheckbox); modal.appendChild(downloadBtn); modal.appendChild(iframe); modal.appendChild(closeButton); document.body.appendChild(modal); iframe.src = constructCaptionsUrl(0); languageDropdown.onchange = () => { const selectedLanguageIndex = languageDropdown.value; iframe.src = constructCaptionsUrl(selectedLanguageIndex); }; } else { modal.style.display = 'flex'; } }; // Adds a button to the page that when clicked, shows the modal for downloading captions. const addButton = () => { // Checks if the button already exists, if not, creates and places it on the page. let button = document.getElementById('viewCaptionsBtn'); if (!button) { button = document.createElement('button'); button.id = 'viewCaptionsBtn'; button.textContent = 'View Captions'; button.style = `position: fixed; bottom: 20px; left: 20px; z-index: 1000;`; button.onclick = showModal; document.body.appendChild(button); } }; // Call addButton to ensure the button is added to the page when the script runs. addButton(); })();