Files
diodiogod-TTS-Audio-Suite/web/mouth_movement_preview.js.reference
T
diodiogod 95d3b691a4 feat: Add viseme detection (vowel classification) to mouth movement analyzer
- Enhanced geometric feature extraction for comprehensive mouth shape analysis
- Implemented vowel classification (A, E, I, O, U) based on lip geometry
- Added new SRT format option to display detected viseme sequences
- New toggle for enabling/disabling viseme detection
- Caching system supports viseme detection state
- Viseme sequences grouped for readability (A3-E2-I-O4)

Technical implementation:
- Extract 8 geometric features: lip width/height, mouth area, roundedness, curvature
- Rule-based vowel classification with confidence scoring
- Frame-by-frame viseme detection integrated with existing movement analysis
- ~20% performance impact when enabled, fully cached for repeated analysis
2025-08-15 01:06:41 -03:00

187 lines
8.0 KiB
Plaintext

/**
* Mouth Movement Analyzer In-Node Video Preview
* Uses ComfyUI's addDOMWidget to display video directly inside the node
*/
import { app } from "../../scripts/app.js";
app.registerExtension({
name: "TTS_Audio_Suite.MouthMovementPreview",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "MouthMovementAnalyzer") {
// Override onNodeCreated to add DOM widget when preview mode is enabled
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
const result = onNodeCreated?.apply(this, arguments);
// Find the preview_mode widget
const previewWidget = this.widgets?.find(w => w.name === "preview_mode");
if (previewWidget) {
const originalCallback = previewWidget.callback;
previewWidget.callback = (value) => {
// Call original callback
if (originalCallback) originalCallback.call(this, value);
// Handle preview widget visibility
if (value) {
this.showVideoWidget();
} else {
this.hideVideoWidget();
}
};
}
return result;
};
// Method to create and show video DOM widget
nodeType.prototype.showVideoWidget = function() {
if (this.videoWidget) return; // Already exists
// Create container for video
const container = document.createElement("div");
container.style.width = "100%";
container.style.minHeight = "200px";
container.style.backgroundColor = "#1a1a1a";
container.style.borderRadius = "4px";
container.style.border = "1px solid #444";
container.style.padding = "8px";
container.style.marginTop = "8px";
// Add title
const title = document.createElement("div");
title.textContent = "👄 Mouth Movement Preview";
title.style.color = "#fff";
title.style.fontSize = "12px";
title.style.marginBottom = "8px";
title.style.fontWeight = "bold";
title.style.textAlign = "center";
// Add placeholder
const placeholder = document.createElement("div");
placeholder.textContent = "Preview will appear after analysis...";
placeholder.style.color = "#888";
placeholder.style.textAlign = "center";
placeholder.style.padding = "40px 20px";
placeholder.style.fontSize = "11px";
container.appendChild(title);
container.appendChild(placeholder);
// Create DOM widget using ComfyUI's addDOMWidget
this.videoWidget = this.addDOMWidget(
"video_preview",
"div",
container,
{
hideOnZoom: false,
getValue: () => container.innerHTML,
setValue: (v) => { /* Not used */ }
}
);
// Store references
this.videoContainer = container;
this.videoTitle = title;
this.videoPlaceholder = placeholder;
// Resize node to accommodate widget
this.setSize([Math.max(400, this.size[0]), this.size[1] + 250]);
};
// Method to hide video widget
nodeType.prototype.hideVideoWidget = function() {
if (this.videoWidget) {
this.removeWidget(this.videoWidget);
this.videoWidget = null;
this.videoContainer = null;
this.videoTitle = null;
this.videoPlaceholder = null;
// Resize node back
this.setSize([this.size[0], this.size[1] - 250]);
}
};
// Method to update video content
nodeType.prototype.updateVideoPreview = function(videoPath) {
if (!this.videoContainer || !videoPath) return;
if (videoPath === "Preview mode disabled" || videoPath.startsWith("Error:") || videoPath.startsWith("No preview")) {
this.videoTitle.textContent = "❌ " + videoPath;
this.videoTitle.style.color = "#ff6b6b";
this.videoContainer.removeChild(this.videoContainer.querySelector("video")); // Remove if exists
return;
}
// Remove existing video if any
const existingVideo = this.videoContainer.querySelector("video");
if (existingVideo) {
this.videoContainer.removeChild(existingVideo);
}
// Remove placeholder
if (this.videoPlaceholder && this.videoPlaceholder.parentNode) {
this.videoContainer.removeChild(this.videoPlaceholder);
}
// Create video element
const videoElement = document.createElement("video");
videoElement.controls = true;
videoElement.loop = true;
videoElement.muted = true;
videoElement.style.width = "100%";
videoElement.style.maxHeight = "200px";
videoElement.style.borderRadius = "4px";
videoElement.style.backgroundColor = "#000";
// Set video source
if (videoPath.includes('/output/') || videoPath.includes('\\output\\')) {
const filename = videoPath.split(/[/\\]/).pop();
videoElement.src = `/view?filename=${encodeURIComponent(filename)}&type=output`;
} else {
videoElement.src = videoPath;
}
// Handle load events
videoElement.onloadeddata = () => {
this.videoTitle.textContent = "✅ Mouth Movement Preview";
this.videoTitle.style.color = "#51cf66";
};
videoElement.onerror = () => {
this.videoTitle.textContent = "❌ Failed to load preview";
this.videoTitle.style.color = "#ff6b6b";
};
// Add video to container
this.videoContainer.appendChild(videoElement);
};
// Override onExecuted to update video when analysis completes
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function(message) {
const result = onExecuted?.apply(this, arguments);
// Check if we have a video widget and preview data
if (this.videoWidget && message && message.ui && message.ui.preview_path) {
const previewPath = message.ui.preview_path[0];
this.updateVideoPreview(previewPath);
}
return result;
};
// Cleanup when node is removed
const onRemoved = nodeType.prototype.onRemoved;
nodeType.prototype.onRemoved = function() {
this.hideVideoWidget();
return onRemoved?.apply(this, arguments);
};
}
}
});
console.log("Mouth Movement Analyzer in-node video preview loaded");