Updated Speech Length Calculator Node UI and added duration output to Load Audio UI node
Updated Speech Length Calculator Node UI and added duration output to Load Audio UI node
This commit is contained in:
@@ -1,6 +1,164 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||||
|
|
||||||
|
// 1. DEFINE CSS STYLES GLOBALLY ONCE
|
||||||
|
// We use DOM elements instead of Canvas drawing so it works flawlessly in ComfyUI V3
|
||||||
|
const cssStyles = `
|
||||||
|
.slc-ui-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 260px; /* Increased from 220px to prevent V1 cropping */
|
||||||
|
background-color: rgba(15, 15, 19, 0.7);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: #ffffff;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: auto; /* allows text selection */
|
||||||
|
}
|
||||||
|
.slc-empty {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.slc-empty-title { color: #aaaaaa; font-size: 14px; font-weight: 500; margin-bottom: 4px; }
|
||||||
|
.slc-empty-sub { color: #777777; font-size: 11px; }
|
||||||
|
.slc-headers {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.slc-header-block {
|
||||||
|
flex: 1;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.slc-header-title { color: #888888; font-size: 9px; font-weight: bold; margin-bottom: 4px; }
|
||||||
|
.slc-header-value { color: #ffffff; font-size: 16px; font-weight: bold; }
|
||||||
|
.slc-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.slc-card {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px 12px 8px 16px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.slc-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.slc-card.slow::before { background-color: #93c5fd; }
|
||||||
|
.slc-card.avg::before { background-color: #86efac; }
|
||||||
|
.slc-card.fast::before { background-color: #fca5a5; }
|
||||||
|
|
||||||
|
.slc-card-left { display: flex; flex-direction: column; }
|
||||||
|
.slc-card-speed { font-size: 12px; font-weight: bold; margin-bottom: 2px; }
|
||||||
|
.slc-card.slow .slc-card-speed { color: #93c5fd; }
|
||||||
|
.slc-card.avg .slc-card-speed { color: #86efac; }
|
||||||
|
.slc-card.fast .slc-card-speed { color: #fca5a5; }
|
||||||
|
.slc-card-wpm { font-size: 10px; color: #aaaaaa; }
|
||||||
|
|
||||||
|
.slc-card-right { display: flex; flex-direction: column; align-items: flex-end; }
|
||||||
|
.slc-card-time { font-size: 13px; font-weight: bold; color: #ffffff; margin-bottom: 2px; }
|
||||||
|
.slc-card-frames { font-size: 11px; font-family: monospace; color: #888888; }
|
||||||
|
|
||||||
|
.slc-legend {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 8px;
|
||||||
|
color: #999999;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Inject styles to document head safely
|
||||||
|
if (!document.getElementById("speech-length-calculator-styles")) {
|
||||||
|
const styleEl = document.createElement("style");
|
||||||
|
styleEl.id = "speech-length-calculator-styles";
|
||||||
|
styleEl.textContent = cssStyles;
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. HELPER TO GENERATE THE HTML CONTENT
|
||||||
|
function buildUIHTML(statsData) {
|
||||||
|
if (!statsData || statsData.empty) {
|
||||||
|
return `
|
||||||
|
<div class="slc-empty">
|
||||||
|
<div class="slc-empty-title">Awaiting Script</div>
|
||||||
|
<div class="slc-empty-sub">Wrap spoken text inside "quotes"</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="slc-headers">
|
||||||
|
<div class="slc-header-block">
|
||||||
|
<div class="slc-header-title">SPOKEN WORDS</div>
|
||||||
|
<div class="slc-header-value">${statsData.wordCount}</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-header-block">
|
||||||
|
<div class="slc-header-title">ADDED TIME</div>
|
||||||
|
<div class="slc-header-value">${statsData.additionalTime}s</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-cards">
|
||||||
|
<div class="slc-card slow">
|
||||||
|
<div class="slc-card-left">
|
||||||
|
<div class="slc-card-speed">SLOW</div>
|
||||||
|
<div class="slc-card-wpm">100 WPM</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-card-right">
|
||||||
|
<div class="slc-card-time">${statsData.slow.time}</div>
|
||||||
|
<div class="slc-card-frames">${statsData.slow.frames} frames</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-card avg">
|
||||||
|
<div class="slc-card-left">
|
||||||
|
<div class="slc-card-speed">AVG</div>
|
||||||
|
<div class="slc-card-wpm">130 WPM</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-card-right">
|
||||||
|
<div class="slc-card-time">${statsData.avg.time}</div>
|
||||||
|
<div class="slc-card-frames">${statsData.avg.frames} frames</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-card fast">
|
||||||
|
<div class="slc-card-left">
|
||||||
|
<div class="slc-card-speed">FAST</div>
|
||||||
|
<div class="slc-card-wpm">160 WPM</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-card-right">
|
||||||
|
<div class="slc-card-time">${statsData.fast.time}</div>
|
||||||
|
<div class="slc-card-frames">${statsData.fast.frames} frames</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="slc-legend">WPM = Words Per Minute</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "Comfy.SpeechLengthCalculator",
|
name: "Comfy.SpeechLengthCalculator",
|
||||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
@@ -10,74 +168,75 @@ app.registerExtension({
|
|||||||
nodeType.prototype.onNodeCreated = function () {
|
nodeType.prototype.onNodeCreated = function () {
|
||||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||||
|
|
||||||
// Add a read-only multiline widget to display the results natively in the node
|
// 3. CREATE THE DOM WIDGET
|
||||||
const statsWidget = ComfyWidgets["STRING"](this, "Stats", ["STRING", { multiline: true }], app).widget;
|
const uiContainer = document.createElement("div");
|
||||||
|
uiContainer.className = "slc-ui-container";
|
||||||
|
uiContainer.innerHTML = buildUIHTML({ empty: true });
|
||||||
|
|
||||||
// Style the widget so it looks like a stats display and prevent editing
|
// Attach to node: standard addDOMWidget works natively in both V1 and V3 Frontends
|
||||||
if (statsWidget.inputEl) {
|
let statsWidget;
|
||||||
statsWidget.inputEl.readOnly = true;
|
if (typeof this.addDOMWidget === "function") {
|
||||||
statsWidget.inputEl.style.opacity = "0.9";
|
statsWidget = this.addDOMWidget("Stats", "HTML", uiContainer, {
|
||||||
statsWidget.inputEl.style.backgroundColor = "rgba(0,0,0,0.3)";
|
serialize: false,
|
||||||
statsWidget.inputEl.style.fontFamily = "monospace";
|
hideOnZoom: false
|
||||||
statsWidget.inputEl.style.pointerEvents = "none";
|
});
|
||||||
statsWidget.inputEl.style.height = "150px"; // Increased to fit new line
|
} else {
|
||||||
statsWidget.inputEl.style.minHeight = "150px";
|
// Fallback for very old unpatched V1 installations
|
||||||
statsWidget.inputEl.style.overflow = "hidden";
|
statsWidget = ComfyWidgets["STRING"](this, "Stats", ["STRING", { multiline: true }], app).widget;
|
||||||
|
if (statsWidget.inputEl) {
|
||||||
|
statsWidget.inputEl.style.display = "none";
|
||||||
|
if (statsWidget.inputEl.parentNode) {
|
||||||
|
statsWidget.inputEl.parentNode.appendChild(uiContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicitly tell LiteGraph how much space this custom widget requires
|
// Tell LiteGraph exactly how much vertical space to hold for our UI
|
||||||
statsWidget.computeSize = function() {
|
statsWidget.computeSize = function() {
|
||||||
return [0, 155]; // 150px for the height + 5px padding
|
return [0, 280]; // Increased from 240 to prevent bottom cropping in V1
|
||||||
};
|
};
|
||||||
|
|
||||||
// Force the node to recalculate its dimensions to perfectly fit all widgets.
|
// Ensure the node is wide enough on initial creation
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const targetWidth = 400;
|
const minWidth = 340;
|
||||||
const targetHeight = 400;
|
if (this.size[0] < minWidth) this.size[0] = minWidth;
|
||||||
|
|
||||||
|
// Fine tune initial height to account for the larger computeSize and make text box taller
|
||||||
|
if (this.size[1] < 500) this.size[1] = 500;
|
||||||
|
|
||||||
if (this.computeSize) {
|
|
||||||
const minSize = this.computeSize([targetWidth, targetHeight]);
|
|
||||||
this.size = [Math.max(targetWidth, minSize[0]), Math.max(targetHeight, minSize[1])];
|
|
||||||
} else {
|
|
||||||
this.size = [targetWidth, targetHeight];
|
|
||||||
}
|
|
||||||
this.setDirtyCanvas(true, true);
|
this.setDirtyCanvas(true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper to fetch the current active text (from connected node OR internal widget)
|
// Fetch text from input link OR widget
|
||||||
this._getCurrentText = () => {
|
this._getCurrentText = () => {
|
||||||
// Check if text_input (or a converted text widget) is connected via link
|
|
||||||
const inputSlot = this.inputs && this.inputs.find(i => i.name === "text_input" || (i.name === "text" && i.link));
|
const inputSlot = this.inputs && this.inputs.find(i => i.name === "text_input" || (i.name === "text" && i.link));
|
||||||
if (inputSlot && inputSlot.link) {
|
if (inputSlot && inputSlot.link) {
|
||||||
const link = app.graph.links[inputSlot.link];
|
const link = app.graph.links[inputSlot.link];
|
||||||
if (link) {
|
if (link) {
|
||||||
const sourceNode = app.graph.getNodeById(link.origin_id);
|
const sourceNode = app.graph.getNodeById(link.origin_id);
|
||||||
if (sourceNode && sourceNode.widgets) {
|
if (sourceNode && sourceNode.widgets) {
|
||||||
// Search for a text/value widget on the connected node
|
|
||||||
const w = sourceNode.widgets.find(w => w.name === "value" || w.name === "text" || w.name === "Text" || w.type === "customtext" || w.type === "STRING");
|
const w = sourceNode.widgets.find(w => w.name === "value" || w.name === "text" || w.name === "Text" || w.type === "customtext" || w.type === "STRING");
|
||||||
if (w && typeof w.value === "string") return w.value;
|
if (w && typeof w.value === "string") return w.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback to our internal text widget
|
const textWidget = this.widgets && this.widgets.find(w => w.name === "text");
|
||||||
const textWidget = this.widgets.find(w => w.name === "text");
|
|
||||||
return textWidget ? (textWidget.value || "") : "";
|
return textWidget ? (textWidget.value || "") : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track the last state so we only recalculate when values actually change
|
|
||||||
this._lastState = { text: null, fps: null, addTime: null };
|
this._lastState = { text: null, fps: null, addTime: null };
|
||||||
|
|
||||||
const updateStats = () => {
|
const updateStats = () => {
|
||||||
const fpsWidget = this.widgets.find(w => w.name === "fps");
|
const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps");
|
||||||
const additionalTimeWidget = this.widgets.find(w => w.name === "additional_time");
|
const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time");
|
||||||
|
|
||||||
if (!fpsWidget || !statsWidget) return;
|
if (!fpsWidget) return;
|
||||||
|
|
||||||
const text = this._getCurrentText();
|
const text = this._getCurrentText();
|
||||||
const fps = fpsWidget.value || 24;
|
const fps = fpsWidget.value || 24;
|
||||||
const additionalTime = additionalTimeWidget ? parseFloat(additionalTimeWidget.value) || 0 : 0;
|
const additionalTime = additionalTimeWidget ? parseFloat(additionalTimeWidget.value) || 0 : 0;
|
||||||
|
|
||||||
// Prevent unnecessary recalculations if nothing has changed
|
// Skip expensive calculations if nothing changed
|
||||||
if (this._lastState.text === text &&
|
if (this._lastState.text === text &&
|
||||||
this._lastState.fps === fps &&
|
this._lastState.fps === fps &&
|
||||||
this._lastState.addTime === additionalTime) {
|
this._lastState.addTime === additionalTime) {
|
||||||
@@ -88,7 +247,6 @@ app.registerExtension({
|
|||||||
this._lastState.fps = fps;
|
this._lastState.fps = fps;
|
||||||
this._lastState.addTime = additionalTime;
|
this._lastState.addTime = additionalTime;
|
||||||
|
|
||||||
// Regex to find words inside standard or smart quotes
|
|
||||||
const regex = /"([^"]*)"|'([^']*)'|“([^”]*)”|‘([^’]*)’/g;
|
const regex = /"([^"]*)"|'([^']*)'|“([^”]*)”|‘([^’]*)’/g;
|
||||||
let match;
|
let match;
|
||||||
let quotedText = "";
|
let quotedText = "";
|
||||||
@@ -96,71 +254,64 @@ app.registerExtension({
|
|||||||
quotedText += (match[1] || match[2] || match[3] || match[4] || "") + " ";
|
quotedText += (match[1] || match[2] || match[3] || match[4] || "") + " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count words
|
|
||||||
const words = quotedText.trim().split(/\s+/).filter(w => w.length > 0);
|
const words = quotedText.trim().split(/\s+/).filter(w => w.length > 0);
|
||||||
const wordCount = words.length;
|
const wordCount = words.length;
|
||||||
|
|
||||||
if (wordCount === 0 && additionalTime === 0) {
|
|
||||||
statsWidget.value = `Spoken Words: 0\nAdditional Time: 0s\nWPM = Words Per Minute\n(No text inside quotes found.)\n(Wrap spoken text in "quotes" to calculate)`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatTime = (wpm) => {
|
const formatTime = (wpm) => {
|
||||||
const baseMins = wordCount / wpm;
|
const baseMins = wordCount / wpm;
|
||||||
const totalSecs = (baseMins * 60) + additionalTime;
|
const totalSecs = (baseMins * 60) + additionalTime;
|
||||||
|
|
||||||
const mins = Math.floor(totalSecs / 60);
|
const mins = Math.floor(totalSecs / 60);
|
||||||
const secs = Math.round(totalSecs % 60);
|
let secs = totalSecs % 60;
|
||||||
|
secs = Math.ceil(secs * 10) / 10;
|
||||||
const frames = Math.ceil(totalSecs * fps);
|
const frames = Math.ceil(totalSecs * fps);
|
||||||
|
|
||||||
const secsStr = secs.toString().padStart(2, '0');
|
const secsStr = secs.toFixed(1);
|
||||||
|
const timeStr = mins > 0 ? `${mins}m ${secsStr}s` : `${secsStr}s`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
time: `${mins}m ${secsStr}s`,
|
time: timeStr,
|
||||||
frames: frames.toString()
|
frames: frames.toString()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const slow = formatTime(100);
|
const statsData = {
|
||||||
const avg = formatTime(130);
|
empty: (wordCount === 0 && additionalTime === 0),
|
||||||
const fast = formatTime(160);
|
wordCount: wordCount,
|
||||||
|
additionalTime: additionalTime,
|
||||||
|
slow: formatTime(100),
|
||||||
|
avg: formatTime(130),
|
||||||
|
fast: formatTime(160)
|
||||||
|
};
|
||||||
|
|
||||||
statsWidget.value =
|
// 4. INJECT HTML TO THE DOM
|
||||||
`Spoken Words: ${wordCount}
|
// Replaces the heavy canvas redrawing!
|
||||||
Additional Time: ${additionalTime}s
|
uiContainer.innerHTML = buildUIHTML(statsData);
|
||||||
WPM = Words Per Minute
|
|
||||||
--------------------------------------------
|
this.setDirtyCanvas(true, false);
|
||||||
Speech Speed Length Frames
|
|
||||||
Slow (100 WPM) ${slow.time.padEnd(10)} ${slow.frames}
|
|
||||||
Avg (130 WPM) ${avg.time.padEnd(10)} ${avg.frames}
|
|
||||||
Fast (160 WPM) ${fast.time.padEnd(10)} ${fast.frames}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook into LiteGraph's drawing cycle to actively poll the upstream node
|
// We still use onDrawBackground as an efficient silent trigger
|
||||||
// This guarantees the display updates if a connected node's text changes
|
// to auto-update in case upstream nodes silently change their text
|
||||||
const onDrawBackground = this.onDrawBackground;
|
const onDrawBackground = this.onDrawBackground;
|
||||||
this.onDrawBackground = function(ctx) {
|
this.onDrawBackground = function(ctx) {
|
||||||
if (onDrawBackground) onDrawBackground.apply(this, arguments);
|
if (onDrawBackground) onDrawBackground.apply(this, arguments);
|
||||||
updateStats();
|
updateStats();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bind initial events to update the stats in real-time natively
|
// Bind events to update in real time based on node interactions
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const textWidget = this.widgets.find(w => w.name === "text");
|
const textWidget = this.widgets && this.widgets.find(w => w.name === "text");
|
||||||
const fpsWidget = this.widgets.find(w => w.name === "fps");
|
const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps");
|
||||||
const additionalTimeWidget = this.widgets.find(w => w.name === "additional_time");
|
const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time");
|
||||||
|
|
||||||
// Update on text typing natively
|
if (textWidget) {
|
||||||
if (textWidget && textWidget.inputEl) {
|
|
||||||
textWidget.inputEl.addEventListener("input", updateStats);
|
|
||||||
} else if (textWidget) {
|
|
||||||
const origCallback = textWidget.callback;
|
const origCallback = textWidget.callback;
|
||||||
textWidget.callback = function() {
|
textWidget.callback = function() {
|
||||||
if (origCallback) origCallback.apply(this, arguments);
|
if (origCallback) origCallback.apply(this, arguments);
|
||||||
updateStats();
|
updateStats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update on FPS changing
|
|
||||||
if (fpsWidget) {
|
if (fpsWidget) {
|
||||||
const origFpsCallback = fpsWidget.callback;
|
const origFpsCallback = fpsWidget.callback;
|
||||||
fpsWidget.callback = function() {
|
fpsWidget.callback = function() {
|
||||||
@@ -168,8 +319,6 @@ Fast (160 WPM) ${fast.time.padEnd(10)} ${fast.frames}`;
|
|||||||
updateStats();
|
updateStats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update on Additional Time changing
|
|
||||||
if (additionalTimeWidget) {
|
if (additionalTimeWidget) {
|
||||||
const origAddCallback = additionalTimeWidget.callback;
|
const origAddCallback = additionalTimeWidget.callback;
|
||||||
additionalTimeWidget.callback = function() {
|
additionalTimeWidget.callback = function() {
|
||||||
@@ -177,8 +326,6 @@ Fast (160 WPM) ${fast.time.padEnd(10)} ${fast.frames}`;
|
|||||||
updateStats();
|
updateStats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial calculation
|
|
||||||
updateStats();
|
updateStats();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ class LoadAudioUI:
|
|||||||
}
|
}
|
||||||
|
|
||||||
CATEGORY = "audio"
|
CATEGORY = "audio"
|
||||||
RETURN_TYPES = ("AUDIO", )
|
RETURN_TYPES = ("AUDIO", "FLOAT")
|
||||||
|
RETURN_NAMES = ("audio", "duration")
|
||||||
FUNCTION = "load_audio"
|
FUNCTION = "load_audio"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -134,4 +135,7 @@ class LoadAudioUI:
|
|||||||
# Format for ComfyUI's standard AUDIO type: [batch, channels, time]
|
# Format for ComfyUI's standard AUDIO type: [batch, channels, time]
|
||||||
audio_output = {"waveform": trimmed_waveform.unsqueeze(0), "sample_rate": sample_rate}
|
audio_output = {"waveform": trimmed_waveform.unsqueeze(0), "sample_rate": sample_rate}
|
||||||
|
|
||||||
return (audio_output, )
|
# Calculate the final trimmed duration in seconds as a float
|
||||||
|
final_duration = float(trimmed_waveform.shape[1] / sample_rate)
|
||||||
|
|
||||||
|
return (audio_output, final_duration)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "WhatDreamsCost-ComfyUI"
|
name = "WhatDreamsCost-ComfyUI"
|
||||||
description = "A variety of custom ComfyUI nodes and workflows for creatives."
|
description = "A variety of custom ComfyUI nodes and workflows for creatives."
|
||||||
version = "1.2.5"
|
version = "1.2.6"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
# classifiers = [
|
# classifiers = [
|
||||||
# # For OS-independent nodes (works on all operating systems)
|
# # For OS-independent nodes (works on all operating systems)
|
||||||
|
|||||||
Reference in New Issue
Block a user