diff --git a/thrimbletrimmer/edit.html b/thrimbletrimmer/edit.html index 4166b18..1245d6b 100644 --- a/thrimbletrimmer/edit.html +++ b/thrimbletrimmer/edit.html @@ -18,6 +18,8 @@ content="345276493482-r84m2giavk10glnmqna0lbq8e1hdaus0.apps.googleusercontent.com" /> + +
@@ -261,21 +263,46 @@ Regions are given as pixel coordinates of the top-left and bottom-right corners.
Note that if the regions are different sizes, the image will be stretched.
- Crop: - - - to - - -
- - Location: - - - to - - +
+ +
+
+ +
+ Crop: + + + to + + +
+
+ +
+ +
+ Location: + + + to + + +
+
+
{ commonPageSetup(); 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 .getElementById("video-info-thumbnail-template-preview-generate") .addEventListener("click", async (_event) => { @@ -2156,3 +2196,70 @@ function canEditMetadata() { function isNonVideoInput(element) { 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(); + } +} \ No newline at end of file diff --git a/thrimbletrimmer/styles/thrimbletrimmer.css b/thrimbletrimmer/styles/thrimbletrimmer.css index 4452e50..e170b97 100644 --- a/thrimbletrimmer/styles/thrimbletrimmer.css +++ b/thrimbletrimmer/styles/thrimbletrimmer.css @@ -371,6 +371,18 @@ input.range-definition-chapter-marker-description { 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 { white-space: pre-wrap; }