thrimbletrimmer: Add support for advanced crop options

- On load + change of template, set from template defaults
- On load, set from video if not null
- Show only in template mode
- Use when previewing image
- Send when submitting
pull/418/head
Mike Lang 1 month ago committed by Mike Lang
parent d4d2bbcad4
commit f46481af0c

@ -252,6 +252,31 @@
id="video-info-thumbnail-time-play" id="video-info-thumbnail-time-play"
/> />
</div> </div>
<div class="video-info-thumbnail-mode-options" id="video-info-thumbnail-position-options">
<details>
<summary>Advanced Templating Options</summary>
Crop specifies the region of the video frame to capture. <br/>
Location specifies the region within the template image where the cropped image will be placed. <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/>
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-1" />
to
<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" />
<br/>
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-1" />
to
<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" />
<br/>
</details>
</div>
<div <div
class="hidden video-info-thumbnail-mode-options" class="hidden video-info-thumbnail-mode-options"
id="video-info-thumbnail-custom-options" id="video-info-thumbnail-custom-options"

@ -2,6 +2,7 @@ var googleUser = null;
var videoInfo; var videoInfo;
var currentRange = 1; var currentRange = 1;
let knownTransitions = []; let knownTransitions = [];
let thumbnailTemplates = {};
let globalPageState = 0; let globalPageState = 0;
const CHAPTER_MARKER_DELIMITER = "\n==========\n"; const CHAPTER_MARKER_DELIMITER = "\n==========\n";
@ -227,6 +228,9 @@ window.addEventListener("DOMContentLoaded", async (event) => {
validateVideoDescription(); validateVideoDescription();
handleFieldChange(event); handleFieldChange(event);
}); });
document
.getElementById("video-info-thumbnail-template")
.addEventListener("change", thumbnailTemplateChanged);
document document
.getElementById("video-info-thumbnail-mode") .getElementById("video-info-thumbnail-mode")
.addEventListener("change", updateThumbnailInputState); .addEventListener("change", updateThumbnailInputState);
@ -267,27 +271,50 @@ window.addEventListener("DOMContentLoaded", async (event) => {
return; return;
} }
const imageTemplate = document.getElementById("video-info-thumbnail-template").value; const imageTemplate = document.getElementById("video-info-thumbnail-template").value;
imageElement.src = `/thumbnail/${globalStreamName}/source.png?timestamp=${imageTime}&template=${imageTemplate}`; const [crop, loc] = getTemplatePosition();
const queryParts = [
`timestamp=${imageTime}`,
`template=${imageTemplate}`,
`crop=${crop.join(",")}`,
`location=${loc.join(",")}`,
];
imageElement.src = `/thumbnail/${globalStreamName}/source.png?${queryParts.join("&")}`;
imageElement.classList.remove("hidden"); imageElement.classList.remove("hidden");
}); });
const thumbnailTemplateSelection = document.getElementById("video-info-thumbnail-template"); const thumbnailTemplateSelection = document.getElementById("video-info-thumbnail-template");
const thumbnailTemplatesListResponse = await fetch("/thrimshim/templates"); const thumbnailTemplatesListResponse = await fetch("/thrimshim/templates");
if (thumbnailTemplatesListResponse.ok) { if (thumbnailTemplatesListResponse.ok) {
const thumbnailTemplatesList = (await thumbnailTemplatesListResponse.json()).map(t => t.name); const thumbnailTemplatesList = await thumbnailTemplatesListResponse.json();
thumbnailTemplatesList.sort(); const templateNames = thumbnailTemplatesList.map(t => t.name);
for (const templateName of thumbnailTemplatesList) { templateNames.sort();
for (const template of thumbnailTemplatesList) {
thumbnailTemplates[template.name] = template;
}
for (const templateName of templateNames) {
const templateOption = document.createElement("option"); const templateOption = document.createElement("option");
templateOption.innerText = templateName; templateOption.innerText = templateName;
templateOption.value = templateName; templateOption.value = templateName;
templateOption.title = thumbnailTemplates[templateName].description;
if (templateName === videoInfo.thumbnail_template) { if (templateName === videoInfo.thumbnail_template) {
templateOption.selected = true; templateOption.selected = true;
} }
thumbnailTemplateSelection.appendChild(templateOption); thumbnailTemplateSelection.appendChild(templateOption);
} }
thumbnailTemplateChanged();
} else { } else {
addError("Failed to load thumbnail templates list"); addError("Failed to load thumbnail templates list");
} }
if (videoInfo.thumbnail_crop !== null) {
for (let i = 0; i < 4; i++) {
document.getElementById(`video-info-thumbnail-crop-${i}`).value = videoInfo.thumbnail_crop[i];
}
}
if (videoInfo.thumbnail_location !== null) {
for (let i = 0; i < 4; i++) {
document.getElementById(`video-info-thumbnail-location-${i}`).value = videoInfo.thumbnail_location[i];
}
}
document.getElementById("video-info-thumbnail-mode").value = videoInfo.thumbnail_mode; document.getElementById("video-info-thumbnail-mode").value = videoInfo.thumbnail_mode;
updateThumbnailInputState(); updateThumbnailInputState();
if (videoInfo.thumbnail_time) { if (videoInfo.thumbnail_time) {
@ -841,6 +868,7 @@ function updateThumbnailInputState(event) {
} else if (newValue === "TEMPLATE") { } else if (newValue === "TEMPLATE") {
unhideIDs.push("video-info-thumbnail-template-options"); unhideIDs.push("video-info-thumbnail-template-options");
unhideIDs.push("video-info-thumbnail-time-options"); unhideIDs.push("video-info-thumbnail-time-options");
unhideIDs.push("video-info-thumbnail-position-options");
unhideIDs.push("video-info-thumbnail-template-preview"); unhideIDs.push("video-info-thumbnail-template-preview");
} else if (newValue === "CUSTOM") { } else if (newValue === "CUSTOM") {
unhideIDs.push("video-info-thumbnail-custom-options"); unhideIDs.push("video-info-thumbnail-custom-options");
@ -856,6 +884,38 @@ function updateThumbnailInputState(event) {
} }
} }
function thumbnailTemplateChanged(event) {
handleFieldChange(event);
const newTemplate = document.getElementById("video-info-thumbnail-template").value;
for (const field of ["crop", "location"]) {
const newValue = thumbnailTemplates[newTemplate][field];
for (let i = 0; i < 4; i++) {
document.getElementById(`video-info-thumbnail-${field}-${i}`).value = newValue[i];
}
}
}
// Returns [crop, location], with either being null on error.
function getTemplatePosition() {
const ret = [];
for (const field of ["crop", "location"]) {
let values = [null, null, null, null];
for (let i = 0; i < 4; i++) {
const value = parseInt(document.getElementById(`video-info-thumbnail-${field}-${i}`).value);
if (isNaN(value)) {
values = null;
break;
}
values[i] = value;
}
ret.push(values);
}
return ret;
}
function getStartTime() { function getStartTime() {
if (!globalStartTimeString) { if (!globalStartTimeString) {
return null; return null;
@ -1092,6 +1152,8 @@ async function sendVideoData(newState, overrideChanges) {
let thumbnailTemplate = null; let thumbnailTemplate = null;
let thumbnailTime = null; let thumbnailTime = null;
let thumbnailImage = null; let thumbnailImage = null;
let thumbnailCrop = null;
let thumbnailLocation = null;
if (thumbnailMode === "BARE" || thumbnailMode === "TEMPLATE") { if (thumbnailMode === "BARE" || thumbnailMode === "TEMPLATE") {
thumbnailTime = wubloaderTimeFromVideoHumanTime( thumbnailTime = wubloaderTimeFromVideoHumanTime(
document.getElementById("video-info-thumbnail-time").value, document.getElementById("video-info-thumbnail-time").value,
@ -1103,6 +1165,11 @@ async function sendVideoData(newState, overrideChanges) {
} }
if (thumbnailMode === "TEMPLATE") { if (thumbnailMode === "TEMPLATE") {
thumbnailTemplate = document.getElementById("video-info-thumbnail-template").value; thumbnailTemplate = document.getElementById("video-info-thumbnail-template").value;
[thumbnailCrop, thumbnailLocation] = getTemplatePosition();
if (thumbnailCrop === null || thumbnailLocation === null) {
submissionError("The thumbnail crop/location options are invalid");
return;
}
} }
if (thumbnailMode === "CUSTOM") { if (thumbnailMode === "CUSTOM") {
const fileInput = document.getElementById("video-info-thumbnail-custom"); const fileInput = document.getElementById("video-info-thumbnail-custom");
@ -1164,6 +1231,8 @@ async function sendVideoData(newState, overrideChanges) {
state: newState, state: newState,
thumbnail_mode: thumbnailMode, thumbnail_mode: thumbnailMode,
thumbnail_template: thumbnailTemplate, thumbnail_template: thumbnailTemplate,
thumbnail_crop: thumbnailCrop,
thumbnail_location: thumbnailLocation,
thumbnail_time: thumbnailTime, thumbnail_time: thumbnailTime,
thumbnail_image: thumbnailImage, thumbnail_image: thumbnailImage,

@ -363,6 +363,10 @@ input.range-definition-chapter-marker-description {
margin: 2px 0; margin: 2px 0;
} }
.video-info-thumbnail-position {
width: 50px;
}
#video-info-thumbnail-template-preview-image { #video-info-thumbnail-template-preview-image {
max-width: 320px; max-width: 320px;
} }

Loading…
Cancel
Save