Thrimbletrimmer: Add JS cropping tool to advanced template options

pull/432/head
Dan Collins 2 weeks ago committed by Mike Lang
parent b866b5d21c
commit 1c5d37cc04

@ -18,6 +18,8 @@
content="345276493482-r84m2giavk10glnmqna0lbq8e1hdaus0.apps.googleusercontent.com" content="345276493482-r84m2giavk10glnmqna0lbq8e1hdaus0.apps.googleusercontent.com"
/> />
<script src="https://apis.google.com/js/platform.js?onload=onGLoad" async defer></script> <script src="https://apis.google.com/js/platform.js?onload=onGLoad" async defer></script>
<link rel="stylesheet" href="https://unpkg.com/jcrop/dist/jcrop.css">
<script src="https://unpkg.com/jcrop"></script>
</head> </head>
<body> <body>
<div id="errors"></div> <div id="errors"></div>
@ -261,6 +263,20 @@
Regions are given as pixel coordinates of the top-left and bottom-right corners. <br/> Regions are given as pixel coordinates of the top-left and bottom-right corners. <br/>
Note that if the regions are different sizes, the image will be stretched. <br/> Note that if the regions are different sizes, the image will be stretched. <br/>
<button id="video-info-thumbnail-template-source-image-update">
Update Source Images
</button>
<br/>
<div class="video-info-thumbnail-advanced-crop-flex-wrapper">
<div class="video-info-thumbnail-advanced-crop-flex-item">
<img
id="video-info-thumbnail-template-video-source-image"
class="hidden"
alt="Thumbnail preview image"
height="360" width="640"
/>
<br/>
Crop: Crop:
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-0" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-0" />
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-1" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-1" />
@ -268,7 +284,16 @@
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-2" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-2" />
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-3" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-crop-3" />
<br/> <br/>
</div>
<div class="video-info-thumbnail-advanced-crop-flex-item">
<img
id="video-info-thumbnail-template-overlay-image"
class="hidden"
alt="Thumbnail preview image"
height="360" width="640"
/>
<br/>
Location: Location:
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-0" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-0" />
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-1" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-1" />
@ -276,6 +301,8 @@
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-2" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-2" />
<input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-3" /> <input type="text" class="video-info-thumbnail-position" id="video-info-thumbnail-location-3" />
<br/> <br/>
</div>
</div>
</details> </details>
</div> </div>
<div <div

@ -15,6 +15,10 @@ const PAGE_STATE = {
CONFIRMING: 3, CONFIRMING: 3,
}; };
// References to Jcrop "stages" for the advanced thumbnail editor crop tool
var videoFrameStage;
var templateStage;
window.addEventListener("DOMContentLoaded", async (event) => { window.addEventListener("DOMContentLoaded", async (event) => {
commonPageSetup(); commonPageSetup();
globalLoadChatWorker.onmessage = (event) => { globalLoadChatWorker.onmessage = (event) => {
@ -259,6 +263,42 @@ window.addEventListener("DOMContentLoaded", async (event) => {
}); });
} }
document
.getElementById("video-info-thumbnail-template-source-image-update")
.addEventListener("click", async (_event) => {
const videoFrameImageElement = document.getElementById("video-info-thumbnail-template-video-source-image");
const timeEntryElement = document.getElementById("video-info-thumbnail-time");
const imageTime = wubloaderTimeFromVideoHumanTime(timeEntryElement.value);
if (imageTime === null) {
videoFrameImageElement.classList.add("hidden");
addError("Couldn't preview thumbnail; couldn't parse thumbnail frame timestamp");
return;
}
const imageTemplate = document.getElementById("video-info-thumbnail-template").value;
const videoFrameQuery = new URLSearchParams({
timestamp: imageTime,
});
videoFrameImageElement.src = `/frame/${globalStreamName}/source.png?${videoFrameQuery}`;
videoFrameImageElement.classList.remove("hidden");
const templateImageElement = document.getElementById("video-info-thumbnail-template-overlay-image");
templateImageElement.src = `/thrimshim/template/${imageTemplate}.png`;
templateImageElement.classList.remove("hidden");
createTemplateCropWidgets();
});
document.getElementById("video-info-thumbnail-crop-0").addEventListener("input", updateTemplateCropWidgets);
document.getElementById("video-info-thumbnail-crop-1").addEventListener("input", updateTemplateCropWidgets);
document.getElementById("video-info-thumbnail-crop-2").addEventListener("input", updateTemplateCropWidgets);
document.getElementById("video-info-thumbnail-crop-3").addEventListener("input", updateTemplateCropWidgets);
document.getElementById("video-info-thumbnail-location-0").addEventListener("input", updateTemplateCropWidgets);
document.getElementById("video-info-thumbnail-location-1").addEventListener("input", updateTemplateCropWidgets);
document.getElementById("video-info-thumbnail-location-2").addEventListener("input", updateTemplateCropWidgets);
document.getElementById("video-info-thumbnail-location-3").addEventListener("input", updateTemplateCropWidgets);
document document
.getElementById("video-info-thumbnail-template-preview-generate") .getElementById("video-info-thumbnail-template-preview-generate")
.addEventListener("click", async (_event) => { .addEventListener("click", async (_event) => {
@ -2156,3 +2196,70 @@ function canEditMetadata() {
function isNonVideoInput(element) { function isNonVideoInput(element) {
return element.id.startsWith("data-correction-force-reset"); return element.id.startsWith("data-correction-force-reset");
} }
/**
* Helper function to create the Jcrop widgets the first time the user chooses
* to load the advanced template cropping tool images in a given session.
*/
function createTemplateCropWidgets() {
if (videoFrameStage == null) {
videoFrameStage = Jcrop.attach('video-info-thumbnail-template-video-source-image');
videoFrameStage.listen('crop.update',function(widget,e){
const pos = widget.pos;
const fieldX1 = document.getElementById("video-info-thumbnail-crop-0");
const fieldY1 = document.getElementById("video-info-thumbnail-crop-1");
const fieldX2 = document.getElementById("video-info-thumbnail-crop-2");
const fieldY2 = document.getElementById("video-info-thumbnail-crop-3");
fieldX1.value = pos.x*2;
fieldY1.value = pos.y*2;
fieldX2.value = (pos.x+pos.w)*2;
fieldY2.value = (pos.y+pos.h)*2;
});
}
if (templateStage == null) {
templateStage = Jcrop.attach('video-info-thumbnail-template-overlay-image');
templateStage.listen('crop.change',function(widget,e){
const pos = widget.pos;
const fieldX1 = document.getElementById("video-info-thumbnail-location-0");
const fieldY1 = document.getElementById("video-info-thumbnail-location-1");
const fieldX2 = document.getElementById("video-info-thumbnail-location-2");
const fieldY2 = document.getElementById("video-info-thumbnail-location-3");
fieldX1.value = pos.x*2;
fieldY1.value = pos.y*2;
fieldX2.value = (pos.x+pos.w)*2;
fieldY2.value = (pos.y+pos.h)*2;
});
}
updateTemplateCropWidgets();
}
/**
* Helper function to update the Jcrop widgets (creating them if needed) based
* on the current values in the advanced template cropping text input fields.
*/
function updateTemplateCropWidgets() {
const videoFieldX1 = document.getElementById("video-info-thumbnail-crop-0");
const videoFieldY1 = document.getElementById("video-info-thumbnail-crop-1");
const videoFieldX2 = document.getElementById("video-info-thumbnail-crop-2");
const videoFieldY2 = document.getElementById("video-info-thumbnail-crop-3");
const videoFrameRect = Jcrop.Rect.create(videoFieldX1.value/2, videoFieldY1.value/2, (videoFieldX2.value-videoFieldX1.value)/2, (videoFieldY2.value-videoFieldY1.value)/2);
if (videoFrameStage.active == null) {
videoFrameStage.newWidget(videoFrameRect);
} else {
videoFrameStage.active.pos = videoFrameRect;
videoFrameStage.active.render();
}
const templateFieldX1 = document.getElementById("video-info-thumbnail-location-0");
const templateFieldY1 = document.getElementById("video-info-thumbnail-location-1");
const templateFieldX2 = document.getElementById("video-info-thumbnail-location-2");
const templateFieldY2 = document.getElementById("video-info-thumbnail-location-3");
const templateRect = Jcrop.Rect.create(templateFieldX1.value/2, templateFieldY1.value/2, (templateFieldX2.value-templateFieldX1.value)/2, (templateFieldY2.value-templateFieldY1.value)/2);
if (templateStage.active == null) {
templateStage.newWidget(templateRect);
} else {
templateStage.active.pos = templateRect;
templateStage.active.render();
}
}

@ -371,6 +371,18 @@ input.range-definition-chapter-marker-description {
max-width: 320px; max-width: 320px;
} }
#video-info-thumbnail-template-video-source-image {
max-width: 640px;
}
#video-info-thumbnail-template-overlay-image {
max-width: 640px;
}
.video-info-thumbnail-advanced-crop-flex-wrapper {
display: flex;
}
.submission-response-error { .submission-response-error {
white-space: pre-wrap; white-space: pre-wrap;
} }

Loading…
Cancel
Save