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
JavaScript
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 };