UNPKG

peertube-plugin-transposer-connector

Version:

Transposer connector is a PeerTube language tool plugin to transcribe and translate with Whisper

887 lines (766 loc) 28.1 kB
async function register({ registerClientRoute, peertubeHelpers }) { const { notifier } = peertubeHelpers; function getInstanceBaseUrl() { return window.location.origin; } function getQueryParam(name) { var url = window.location.href; name = name.replace(/[\[\]]/g, "\\$&"); var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ""; return decodeURIComponent(results[2].replace(/\+/g, " ")); } //transcribe edit page registerClientRoute({ route: "/transposer/translation/edit", onMount: ({ rootEl }) => { rootEl.innerHTML = getPluginContainerHTML(); function saveButton(videoId, language, caption) { console.log(videoId); console.log(language); console.log(caption); const blob = new Blob([caption], { type: "text/vtt" }); const formData = new FormData(); formData.append("captionfile", blob, `${videoId}_${language}.vtt`); fetch( `${getInstanceBaseUrl()}/api/v1/videos/${videoId}/captions/${language}`, { method: "PUT", body: formData, headers: peertubeHelpers.getAuthHeader(), } ) .then((response) => { if (!response.ok) { throw new Error( `Failed to save changes: ${response.status} ${response.statusText}` ); } notifier.success("Changes saved successfully."); window.open( `${getInstanceBaseUrl()}/p/transposer/translate?videoId=${videoId}`, "_self" ); }) .catch((error) => { notifier.error(`Error saving changes: ${error}`); console.error("Error saving changes:", error); }); } //TODO: Abbrechen/Go Back: Changes in input //TODO: SQL Injection: LIB or regex, all inputs //Cross Side Scripting //only visible Charect0r //SQL Injection escape //lib => ??? function getPluginContainerHTML() { return ` <div id="plugincontainer"> <h1>Translation Edit Language</h1> <button id="saveTranslationEdit">Save changes</button> <button id="backButton">Back</button> <br> <br> <textarea id="textArea" style="width: 100%; height: 590px; overflow-y: auto;"></textarea> </div>`; } const backButton = rootEl.querySelector("#backButton"); backButton.addEventListener("click", () => { console.log("back button clicked"); const videoId = getQueryParam("videoId"); window.open( `${getInstanceBaseUrl()}/p/transposer/translate?videoId=${videoId}`, "_self" ); }); function getTranscribePage() { // Example: "http://example.com/?videoId=123" var videoId = getQueryParam("videoId"); var language = getQueryParam("lang"); if (videoId == null) { console.log("No Video Id in query param"); window.open(`${getInstanceBaseUrl()}/p/transposer`, "_self"); return; } if (language == null) { console.log("No language in query param"); return; } fetch(`${getInstanceBaseUrl()}/api/v1/videos/${videoId}/captions`, { method: "GET", headers: peertubeHelpers.getAuthHeader(), }) .then((captionResponse) => { if (!captionResponse.ok) { throw new Error( `Failed to fetch captions: ${captionResponse.status} ${captionResponse.statusText}` ); } return captionResponse.json(); }) .then((captionData) => { if (captionData.total == 0) { throw new Error("No captions available yet."); } const captions = captionData.data; let foundCaption = false; captions.forEach((caption) => { if (caption.language.id === language) { foundCaption = true; const captionPath = `${getInstanceBaseUrl()}${ caption.captionPath }`; fetch(captionPath) .then((vttResponse) => { if (!vttResponse.ok) { throw new Error( `Failed to fetch VTT file: ${vttResponse.status} ${vttResponse.statusText}` ); } return vttResponse.text(); }) .then((vttData) => { document.getElementById("textArea").value = vttData; }) .catch((vttError) => { console.error("Error fetching VTT file:", vttError); }); } }); if (!foundCaption) { throw new Error("Language not found"); } }); } const saveButtonElement = rootEl.querySelector("#saveTranslationEdit"); saveButtonElement.addEventListener("click", () => { const videoId = getQueryParam("videoId"); const language = getQueryParam("lang"); const caption = document.getElementById("textArea").value; if (!caption) { notifier.error("Caption text is empty."); return; } saveButton(videoId, language, caption); }); function getThemeStyles() { return ` #plugincontainer { max-width: 100%; padding: 20px; position: relative; } #plugincontainer h1 { color: var(--mainColor); } #plugincontainer #formcontainer { display: flex; flex-wrap: wrap; } #plugincontainer .modifyButton, #plugincontainer .saveButton, #plugincontainer .cancelButton, #plugincontainer .deleteButton { cursor: pointer; padding: 20px; } #plugincontainer input, #plugincontainer textarea, #plugincontainer select { display: block; font-size: 16px; padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px); width: 100%; max-width: 100%; color: var(--input-fg, #000); background-color: var(--input-bg, #fff); border: 1px solid var(--input-border-color, #ccc); border-radius: var(--input-border-radius, 4px); line-height: 24px; transition: background-color 0.3s ease; } #plugincontainer p { font-size: 18px; } #plugincontainer table { width: 100%; border-collapse: collapse; margin: 25px 0; font-size: 0.9em; min-width: 130px; min-height: 130px; } #plugincontainer table tr { text-align: left; } #plugincontainer table th, #plugincontainer table td { padding: 12px 15px; border-bottom: 1px solid var(--inputPlaceholderColor, #ccc); } #plugincontainer table th { background-color: var(--greyBackgroundColor, #f8f9fa); color: var(--mainForegroundColor); } /* General button styling */ #plugincontainer button { color: var(--on-primary, var(--mainForegroundColor)); background-color: var(--primary, var(--mainColor)); border: 1px solid var(--primary, var(--mainColor)); padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px); border-radius: 4px; cursor: pointer; transition: background-color 0.3s; vertical-align: top; } /* Top-level action buttons */ #plugincontainer > button { width: auto; min-width: 100px; margin-right: 10px; } /* Table buttons */ #plugincontainer td button { width: auto; min-width: 80px; margin: 0 5px; } #plugincontainer button:hover { background-color: var(--secondaryColor); } #plugincontainer button:disabled { background-color: var(--greyBackgroundColor, #cccccc); color: var(--greyForegroundColor, #666666); cursor: not-allowed; } /* Action buttons like in the video list */ #plugincontainer .action-button { position: relative; width: 56px; height: 56px; display: flex; flex-direction: column; align-items: center; justify-content: center; min-width: unset; padding: 8px; } #plugincontainer .action-button.success { background-color: var(--primary, var(--mainColor)); border-color: var(--primary, var(--mainColor)); } #plugincontainer .action-button.not-triggered { background-color: var(--greyBackgroundColor, #f8f9fa); border-color: var(--greyForegroundColor, #ccc); color: var(--greyForegroundColor, #666); } #plugincontainer .action-button svg { width: 24px; height: 24px; margin-bottom: 6px; fill: var(--on-primary, var(--mainForegroundColor)); stroke: var(--on-primary, var(--mainForegroundColor)); } #plugincontainer .action-button.not-triggered svg { fill: var(--greyForegroundColor, #666); stroke: var(--greyForegroundColor, #666); } /* Specialized button styles */ #plugincontainer .translate-field-btn { background-color: var(--greyBackgroundColor, #6c757d); font-size: 0.8em; width: auto; padding: 2px 5px; margin-left: 5px; min-width: unset; } #plugincontainer .translate-field-btn:hover { background-color: var(--secondaryColor, #5a6268); } #plugincontainer #triggerDetail { background-color: var(--primary, var(--mainColor)); border: 1px solid var(--primary, var(--mainColor)); border-radius: 4px; padding: 6px; display: flex; align-items: center; justify-content: center; } #plugincontainer #triggerDetail svg { fill: var(--on-primary, var(--mainForegroundColor)); stroke: var(--on-primary, var(--mainForegroundColor)); } #plugincontainer .edit-icon path { stroke: var(--on-primary, var(--mainForegroundColor)); } /* Rest of the styles remain the same */ #plugincontainer #pageButtonsContainer { margin: 15px 0; text-align: center; } #plugincontainer #confirmationBox { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: var(--primary, var(--mainColor)); padding: 20px; border-radius: 5px; } #plugincontainer #confirmationBox p { margin-bottom: 20px; color: var(--on-primary, var(--mainForegroundColor)); } #plugincontainer #confirmationBox button { margin-right: 10px; padding: 8px 16px; border: none; border-radius: 3px; background-color: var(--on-primary, var(--mainForegroundColor)); color: var(--primary, var(--mainColor)); cursor: pointer; } #plugincontainer .page-button { background-color: var(--primary, var(--mainColor)); min-width: unset; width: auto; } #plugincontainer .current-page-button { background-color: var(--secondaryColor); } /* Action buttons and Header Styles */ #plugincontainer .action-column-header { padding: 0; } #plugincontainer .action-headers-container { display: flex; justify-content: space-between; width: 100%; } #plugincontainer .action-header { flex: 1; text-align: center; font-size: 0.9em; font-weight: bold; padding: 8px 4px; white-space: nowrap; } #plugincontainer .action-buttons-row { display: flex; justify-content: space-between; width: 100%; } #plugincontainer .action-cell { flex: 1; display: flex; justify-content: center; align-items: center; padding: 4px; } #plugincontainer .action-button.label-xl { font-size: 90%; padding: var(--input-y-padding) calc(var(--input-x-padding) / 2) !important; } #plugincontainer .counter-label { font-size: 0.75em; margin-top: 4px; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Language notice styles */ #plugincontainer .language-notice { margin: 10px 0; padding: 10px; border-radius: 4px; } #plugincontainer .language-notice.success { background-color: #e6ffe6; border: 1px solid #b3ffb3; } #plugincontainer .language-notice.warning { background-color: #fff8e6; border: 1px solid #ffe0b3; } #plugincontainer .language-notice p { margin: 0; font-size: 14px; } /* Field container styles */ #plugincontainer .field-container { display: flex; align-items: center; width: 100%; } #plugincontainer .field-container input, #plugincontainer .field-container textarea { flex: 1; min-width: 200px; } /* Modal styles */ #plugincontainer .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; } #plugincontainer .modal-content { background-color: var(--backgroundPrimary, white); padding: 20px; border-radius: 5px; width: 400px; text-align: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #plugincontainer .modal-content h3 { margin-top: 0; color: var(--primary, var(--mainColor)); } #plugincontainer .modal-content button { margin: 10px; padding: 10px 15px; min-width: 100px; } /* For the translation status colors */ #plugincontainer input[style*="background-color: #e6ffe6"], #plugincontainer textarea[style*="background-color: #e6ffe6"] { background-color: #e6ffe6 !important; } #plugincontainer input[style*="background-color: #ffe6e6"], #plugincontainer textarea[style*="background-color: #ffe6e6"] { background-color: #ffe6e6 !important; } #plugincontainer input[style*="background-color: #fff8e6"], #plugincontainer textarea[style*="background-color: #fff8e6"] { background-color: #fff8e6 !important; } `; } function getThemeStyles() { return ` #plugincontainer { max-width: 100%; padding: 20px; position: relative; } #plugincontainer h1 { color: var(--mainColor); } #plugincontainer #formcontainer { display: flex; flex-wrap: wrap; } #plugincontainer .modifyButton, #plugincontainer .saveButton, #plugincontainer .cancelButton, #plugincontainer .deleteButton { cursor: pointer; padding: 20px; } #plugincontainer input, #plugincontainer textarea, #plugincontainer select { display: block; font-size: 16px; padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px); width: 100%; max-width: 100%; color: var(--input-fg, #000); background-color: var(--input-bg, #fff); border: 1px solid var(--input-border-color, #ccc); border-radius: var(--input-border-radius, 4px); line-height: 24px; transition: background-color 0.3s ease; } #plugincontainer p { font-size: 18px; } #plugincontainer table { width: 100%; border-collapse: collapse; margin: 25px 0; font-size: 0.9em; min-width: 130px; min-height: 130px; } #plugincontainer table tr { text-align: left; } #plugincontainer table th, #plugincontainer table td { padding: 12px 15px; border-bottom: 1px solid var(--inputPlaceholderColor, #ccc); } #plugincontainer table th { background-color: var(--greyBackgroundColor, #f8f9fa); color: var(--mainForegroundColor); } /* General button styling */ #plugincontainer button { color: var(--on-primary, var(--mainForegroundColor)); background-color: var(--primary, var(--mainColor)); border: 1px solid var(--primary, var(--mainColor)); padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px); border-radius: 4px; cursor: pointer; transition: background-color 0.3s; vertical-align: top; } /* Top-level action buttons */ #plugincontainer > button { width: auto; min-width: 100px; margin-right: 10px; } /* Table buttons */ #plugincontainer td button { width: auto; min-width: 80px; margin: 0 5px; } #plugincontainer button:hover { background-color: var(--secondaryColor); } #plugincontainer button:disabled { background-color: var(--greyBackgroundColor, #cccccc); color: var(--greyForegroundColor, #666666); cursor: not-allowed; } /* Action buttons like in the video list */ #plugincontainer .action-button { position: relative; width: 56px; height: 56px; display: flex; flex-direction: column; align-items: center; justify-content: center; min-width: unset; padding: 8px; } #plugincontainer .action-button.success { background-color: var(--primary, var(--mainColor)); border-color: var(--primary, var(--mainColor)); } #plugincontainer .action-button.not-triggered { background-color: var(--greyBackgroundColor, #f8f9fa); border-color: var(--greyForegroundColor, #ccc); color: var(--greyForegroundColor, #666); } #plugincontainer .action-button svg { width: 24px; height: 24px; margin-bottom: 6px; fill: var(--on-primary, var(--mainForegroundColor)); stroke: var(--on-primary, var(--mainForegroundColor)); } #plugincontainer .action-button.not-triggered svg { fill: var(--greyForegroundColor, #666); stroke: var(--greyForegroundColor, #666); } /* Specialized button styles */ #plugincontainer .translate-field-btn { background-color: var(--greyBackgroundColor, #6c757d); font-size: 0.8em; width: auto; padding: 2px 5px; margin-left: 5px; min-width: unset; } #plugincontainer .translate-field-btn:hover { background-color: var(--secondaryColor, #5a6268); } #plugincontainer #triggerDetail { background-color: var(--primary, var(--mainColor)); border: 1px solid var(--primary, var(--mainColor)); border-radius: 4px; padding: 6px; display: flex; align-items: center; justify-content: center; } #plugincontainer #triggerDetail svg { fill: var(--on-primary, var(--mainForegroundColor)); stroke: var(--on-primary, var(--mainForegroundColor)); } #plugincontainer .edit-icon path { stroke: var(--on-primary, var(--mainForegroundColor)); } /* Rest of the styles remain the same */ #plugincontainer #pageButtonsContainer { margin: 15px 0; text-align: center; } #plugincontainer #confirmationBox { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: var(--primary, var(--mainColor)); padding: 20px; border-radius: 5px; } #plugincontainer #confirmationBox p { margin-bottom: 20px; color: var(--on-primary, var(--mainForegroundColor)); } #plugincontainer #confirmationBox button { margin-right: 10px; padding: 8px 16px; border: none; border-radius: 3px; background-color: var(--on-primary, var(--mainForegroundColor)); color: var(--primary, var(--mainColor)); cursor: pointer; } #plugincontainer .page-button { background-color: var(--primary, var(--mainColor)); min-width: unset; width: auto; } #plugincontainer .current-page-button { background-color: var(--secondaryColor); } /* Action buttons and Header Styles */ #plugincontainer .action-column-header { padding: 0; } #plugincontainer .action-headers-container { display: flex; justify-content: space-between; width: 100%; } #plugincontainer .action-header { flex: 1; text-align: center; font-size: 0.9em; font-weight: bold; padding: 8px 4px; white-space: nowrap; } #plugincontainer .action-buttons-row { display: flex; justify-content: space-between; width: 100%; } #plugincontainer .action-cell { flex: 1; display: flex; justify-content: center; align-items: center; padding: 4px; } #plugincontainer .action-button.label-xl { font-size: 90%; padding: var(--input-y-padding) calc(var(--input-x-padding) / 2) !important; } #plugincontainer .counter-label { font-size: 0.75em; margin-top: 4px; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Language notice styles */ #plugincontainer .language-notice { margin: 10px 0; padding: 10px; border-radius: 4px; } #plugincontainer .language-notice.success { background-color: #e6ffe6; border: 1px solid #b3ffb3; } #plugincontainer .language-notice.warning { background-color: #fff8e6; border: 1px solid #ffe0b3; } #plugincontainer .language-notice p { margin: 0; font-size: 14px; } /* Field container styles */ #plugincontainer .field-container { display: flex; align-items: center; width: 100%; } #plugincontainer .field-container input, #plugincontainer .field-container textarea { flex: 1; min-width: 200px; } /* Modal styles */ #plugincontainer .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; } #plugincontainer .modal-content { background-color: var(--backgroundPrimary, white); padding: 20px; border-radius: 5px; width: 400px; text-align: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #plugincontainer .modal-content h3 { margin-top: 0; color: var(--primary, var(--mainColor)); } #plugincontainer .modal-content button { margin: 10px; padding: 10px 15px; min-width: 100px; } /* For the translation status colors */ #plugincontainer input[style*="background-color: #e6ffe6"], #plugincontainer textarea[style*="background-color: #e6ffe6"] { background-color: #e6ffe6 !important; } #plugincontainer input[style*="background-color: #ffe6e6"], #plugincontainer textarea[style*="background-color: #ffe6e6"] { background-color: #ffe6e6 !important; } #plugincontainer input[style*="background-color: #fff8e6"], #plugincontainer textarea[style*="background-color: #fff8e6"] { background-color: #fff8e6 !important; } `; } function initialize() { getTranscribePage(); const style = document.createElement("style"); style.innerHTML = getThemeStyles(); rootEl.appendChild(style); } initialize(); }, }); } export { register };