Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Update index.html
Browse files- index.html +416 -0
index.html
CHANGED
|
@@ -709,12 +709,21 @@
|
|
| 709 |
const app = {
|
| 710 |
mode: 'text',
|
| 711 |
isGenerating: false,
|
|
|
|
| 712 |
frameCount: 0,
|
| 713 |
maxFrames: 234,
|
| 714 |
ws: null,
|
| 715 |
frameBuffer: [],
|
| 716 |
playbackInterval: null,
|
|
|
|
|
|
|
|
|
|
| 717 |
sessionStarted: false,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
|
| 719 |
init() {
|
| 720 |
this.setupEventListeners();
|
|
@@ -728,10 +737,17 @@
|
|
| 728 |
|
| 729 |
document.getElementById('playbackFps').addEventListener('input', (e) => {
|
| 730 |
document.getElementById('playbackFpsValue').textContent = e.target.value + ' fps';
|
|
|
|
|
|
|
|
|
|
| 731 |
});
|
| 732 |
|
| 733 |
document.getElementById('inputFps').addEventListener('input', (e) => {
|
| 734 |
document.getElementById('inputFpsValue').textContent = e.target.value;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 735 |
});
|
| 736 |
|
| 737 |
document.getElementById('strength').addEventListener('input', (e) => {
|
|
@@ -741,7 +757,20 @@
|
|
| 741 |
document.getElementById('numBlocks').addEventListener('input', (e) => {
|
| 742 |
document.getElementById('numBlocksValue').textContent = e.target.value;
|
| 743 |
this.updateEstimatedFrames();
|
|
|
|
|
|
|
|
|
|
| 744 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
},
|
| 746 |
|
| 747 |
setMode(mode) {
|
|
@@ -753,6 +782,43 @@
|
|
| 753 |
document.getElementById('videoControls').classList.toggle('hidden', mode !== 'video');
|
| 754 |
document.getElementById('webcamControls').classList.toggle('hidden', mode !== 'webcam');
|
| 755 |
document.getElementById('inputControls').classList.toggle('hidden', !(mode === 'video' || mode === 'webcam'));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 756 |
},
|
| 757 |
|
| 758 |
updateEstimatedFrames() {
|
|
@@ -763,6 +829,241 @@
|
|
| 763 |
document.getElementById('maxFrames').textContent = estimatedFrames;
|
| 764 |
},
|
| 765 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
randomizeSeed() {
|
| 767 |
document.getElementById('seed').value = Math.floor(Math.random() * (1 << 24));
|
| 768 |
},
|
|
@@ -855,6 +1156,52 @@
|
|
| 855 |
width: 832,
|
| 856 |
height: 480
|
| 857 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
|
| 859 |
const seedValue = document.getElementById('seed').value;
|
| 860 |
if (seedValue && seedValue.trim() !== '') {
|
|
@@ -862,13 +1209,81 @@
|
|
| 862 |
} else {
|
| 863 |
payload.seed = Math.floor(Math.random() * (1 << 24));
|
| 864 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 865 |
|
| 866 |
try {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
const encoded = createMsgpackEncoder(payload);
|
| 868 |
this.ws.send(encoded);
|
| 869 |
this.showInfo('Generation started!');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
} catch (error) {
|
| 871 |
this.showError('Failed to send parameters: ' + error.message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
}
|
| 873 |
},
|
| 874 |
|
|
@@ -926,6 +1341,7 @@
|
|
| 926 |
clearInterval(this.playbackInterval);
|
| 927 |
this.playbackInterval = null;
|
| 928 |
}
|
|
|
|
| 929 |
this.isGenerating = false;
|
| 930 |
this.updateUI();
|
| 931 |
},
|
|
|
|
| 709 |
const app = {
|
| 710 |
mode: 'text',
|
| 711 |
isGenerating: false,
|
| 712 |
+
isConnected: false,
|
| 713 |
frameCount: 0,
|
| 714 |
maxFrames: 234,
|
| 715 |
ws: null,
|
| 716 |
frameBuffer: [],
|
| 717 |
playbackInterval: null,
|
| 718 |
+
frameExtractionInterval: null,
|
| 719 |
+
mediaRecorder: null,
|
| 720 |
+
recordedChunks: [],
|
| 721 |
sessionStarted: false,
|
| 722 |
+
webcamStream: null,
|
| 723 |
+
videoMetadata: null,
|
| 724 |
+
currentVideoFile: null,
|
| 725 |
+
promptUpdateTimer: null,
|
| 726 |
+
pendingPromptUpdate: null,
|
| 727 |
|
| 728 |
init() {
|
| 729 |
this.setupEventListeners();
|
|
|
|
| 737 |
|
| 738 |
document.getElementById('playbackFps').addEventListener('input', (e) => {
|
| 739 |
document.getElementById('playbackFpsValue').textContent = e.target.value + ' fps';
|
| 740 |
+
if (this.playbackInterval) {
|
| 741 |
+
this.startPlaybackLoop();
|
| 742 |
+
}
|
| 743 |
});
|
| 744 |
|
| 745 |
document.getElementById('inputFps').addEventListener('input', (e) => {
|
| 746 |
document.getElementById('inputFpsValue').textContent = e.target.value;
|
| 747 |
+
if (this.isGenerating && (this.mode === 'video' || this.mode === 'webcam')) {
|
| 748 |
+
this.stopFrameExtraction();
|
| 749 |
+
this.startFrameExtraction();
|
| 750 |
+
}
|
| 751 |
});
|
| 752 |
|
| 753 |
document.getElementById('strength').addEventListener('input', (e) => {
|
|
|
|
| 757 |
document.getElementById('numBlocks').addEventListener('input', (e) => {
|
| 758 |
document.getElementById('numBlocksValue').textContent = e.target.value;
|
| 759 |
this.updateEstimatedFrames();
|
| 760 |
+
if (this.isGenerating) {
|
| 761 |
+
this.queuePromptUpdate();
|
| 762 |
+
}
|
| 763 |
});
|
| 764 |
+
|
| 765 |
+
// Prompt changes - queue updates every 2 seconds
|
| 766 |
+
document.getElementById('prompt').addEventListener('input', () => {
|
| 767 |
+
if (this.isGenerating && (this.mode === 'text' || this.mode === 'webcam')) {
|
| 768 |
+
this.queuePromptUpdate();
|
| 769 |
+
}
|
| 770 |
+
});
|
| 771 |
+
|
| 772 |
+
// Video file upload
|
| 773 |
+
document.getElementById('videoFile').addEventListener('change', (e) => this.handleVideoUpload(e));
|
| 774 |
},
|
| 775 |
|
| 776 |
setMode(mode) {
|
|
|
|
| 782 |
document.getElementById('videoControls').classList.toggle('hidden', mode !== 'video');
|
| 783 |
document.getElementById('webcamControls').classList.toggle('hidden', mode !== 'webcam');
|
| 784 |
document.getElementById('inputControls').classList.toggle('hidden', !(mode === 'video' || mode === 'webcam'));
|
| 785 |
+
|
| 786 |
+
// Start/stop webcam based on mode
|
| 787 |
+
if (mode === 'webcam') {
|
| 788 |
+
this.startWebcam();
|
| 789 |
+
} else {
|
| 790 |
+
this.stopWebcam();
|
| 791 |
+
}
|
| 792 |
+
},
|
| 793 |
+
|
| 794 |
+
async startWebcam() {
|
| 795 |
+
try {
|
| 796 |
+
this.webcamStream = await navigator.mediaDevices.getUserMedia({
|
| 797 |
+
video: {
|
| 798 |
+
width: { ideal: 1280 },
|
| 799 |
+
height: { ideal: 720 }
|
| 800 |
+
},
|
| 801 |
+
audio: false
|
| 802 |
+
});
|
| 803 |
+
|
| 804 |
+
const video = document.getElementById('webcamVideo');
|
| 805 |
+
video.srcObject = this.webcamStream;
|
| 806 |
+
this.showInfo('Webcam started successfully');
|
| 807 |
+
} catch (error) {
|
| 808 |
+
this.showError(`Failed to access webcam: ${error.message}`);
|
| 809 |
+
console.error('Webcam error:', error);
|
| 810 |
+
// Switch back to text mode if webcam fails
|
| 811 |
+
this.setMode('text');
|
| 812 |
+
}
|
| 813 |
+
},
|
| 814 |
+
|
| 815 |
+
stopWebcam() {
|
| 816 |
+
if (this.webcamStream) {
|
| 817 |
+
this.webcamStream.getTracks().forEach(track => track.stop());
|
| 818 |
+
this.webcamStream = null;
|
| 819 |
+
const video = document.getElementById('webcamVideo');
|
| 820 |
+
video.srcObject = null;
|
| 821 |
+
}
|
| 822 |
},
|
| 823 |
|
| 824 |
updateEstimatedFrames() {
|
|
|
|
| 829 |
document.getElementById('maxFrames').textContent = estimatedFrames;
|
| 830 |
},
|
| 831 |
|
| 832 |
+
queuePromptUpdate() {
|
| 833 |
+
// Store the pending update
|
| 834 |
+
this.pendingPromptUpdate = {
|
| 835 |
+
prompt: document.getElementById('prompt').value,
|
| 836 |
+
num_blocks: parseInt(document.getElementById('numBlocks').value)
|
| 837 |
+
};
|
| 838 |
+
|
| 839 |
+
// Clear existing timer
|
| 840 |
+
if (this.promptUpdateTimer) {
|
| 841 |
+
clearTimeout(this.promptUpdateTimer);
|
| 842 |
+
}
|
| 843 |
+
|
| 844 |
+
// Set new timer to send update after 2 seconds
|
| 845 |
+
this.promptUpdateTimer = setTimeout(() => {
|
| 846 |
+
if (this.pendingPromptUpdate && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
| 847 |
+
const encoded = createMsgpackEncoder(this.pendingPromptUpdate);
|
| 848 |
+
this.ws.send(encoded);
|
| 849 |
+
this.maxFrames = (12 * this.pendingPromptUpdate.num_blocks) - 6;
|
| 850 |
+
document.getElementById('maxFrames').textContent = this.maxFrames;
|
| 851 |
+
console.log('Sent prompt update:', this.pendingPromptUpdate);
|
| 852 |
+
this.pendingPromptUpdate = null;
|
| 853 |
+
}
|
| 854 |
+
}, 2000);
|
| 855 |
+
},
|
| 856 |
+
|
| 857 |
+
async handleVideoUpload(event) {
|
| 858 |
+
const file = event.target.files?.[0];
|
| 859 |
+
if (!file) return;
|
| 860 |
+
|
| 861 |
+
this.currentVideoFile = file;
|
| 862 |
+
const video = document.getElementById('hiddenVideo');
|
| 863 |
+
const url = URL.createObjectURL(file);
|
| 864 |
+
|
| 865 |
+
return new Promise((resolve) => {
|
| 866 |
+
video.onloadedmetadata = () => {
|
| 867 |
+
this.videoMetadata = {
|
| 868 |
+
duration: video.duration,
|
| 869 |
+
width: video.videoWidth,
|
| 870 |
+
height: video.videoHeight
|
| 871 |
+
};
|
| 872 |
+
|
| 873 |
+
document.getElementById('videoInfo').innerHTML = `
|
| 874 |
+
${file.name}<br>
|
| 875 |
+
${this.videoMetadata.width}×${this.videoMetadata.height} • ${this.videoMetadata.duration.toFixed(1)}s
|
| 876 |
+
`;
|
| 877 |
+
|
| 878 |
+
this.showInfo('Video loaded successfully');
|
| 879 |
+
resolve();
|
| 880 |
+
};
|
| 881 |
+
|
| 882 |
+
video.onerror = () => {
|
| 883 |
+
this.showError('Failed to load video');
|
| 884 |
+
this.currentVideoFile = null;
|
| 885 |
+
this.videoMetadata = null;
|
| 886 |
+
};
|
| 887 |
+
|
| 888 |
+
video.src = url;
|
| 889 |
+
});
|
| 890 |
+
},
|
| 891 |
+
|
| 892 |
+
async extractVideoFrameBytes() {
|
| 893 |
+
const video = document.getElementById('hiddenVideo');
|
| 894 |
+
const canvas = document.getElementById('extractionCanvas');
|
| 895 |
+
|
| 896 |
+
if (!video || !canvas) {
|
| 897 |
+
console.error('Video or canvas element not found');
|
| 898 |
+
return null;
|
| 899 |
+
}
|
| 900 |
+
|
| 901 |
+
const ctx = canvas.getContext('2d');
|
| 902 |
+
canvas.width = 832;
|
| 903 |
+
canvas.height = 480;
|
| 904 |
+
|
| 905 |
+
// Scale and crop to 832x480
|
| 906 |
+
const scale = Math.max(832 / video.videoWidth, 480 / video.videoHeight);
|
| 907 |
+
const scaledWidth = video.videoWidth * scale;
|
| 908 |
+
const scaledHeight = video.videoHeight * scale;
|
| 909 |
+
const offsetX = (832 - scaledWidth) / 2;
|
| 910 |
+
const offsetY = (480 - scaledHeight) / 2;
|
| 911 |
+
|
| 912 |
+
ctx.clearRect(0, 0, 832, 480);
|
| 913 |
+
ctx.drawImage(video, offsetX, offsetY, scaledWidth, scaledHeight);
|
| 914 |
+
|
| 915 |
+
return new Promise((resolve) => {
|
| 916 |
+
canvas.toBlob(async (blob) => {
|
| 917 |
+
if (!blob) {
|
| 918 |
+
console.error('Failed to create blob from canvas');
|
| 919 |
+
resolve(null);
|
| 920 |
+
return;
|
| 921 |
+
}
|
| 922 |
+
try {
|
| 923 |
+
const arrayBuffer = await blob.arrayBuffer();
|
| 924 |
+
const uint8Array = new Uint8Array(arrayBuffer);
|
| 925 |
+
resolve(uint8Array);
|
| 926 |
+
} catch (error) {
|
| 927 |
+
console.error('Failed to convert blob to Uint8Array:', error);
|
| 928 |
+
resolve(null);
|
| 929 |
+
}
|
| 930 |
+
}, 'image/jpeg', 0.9);
|
| 931 |
+
});
|
| 932 |
+
},
|
| 933 |
+
|
| 934 |
+
async extractWebcamBytes() {
|
| 935 |
+
const video = document.getElementById('webcamVideo');
|
| 936 |
+
const canvas = document.getElementById('extractionCanvas');
|
| 937 |
+
|
| 938 |
+
if (!video || !canvas || !video.videoWidth) {
|
| 939 |
+
console.error('Webcam not ready');
|
| 940 |
+
return null;
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
const ctx = canvas.getContext('2d');
|
| 944 |
+
|
| 945 |
+
// Set fixed dimensions
|
| 946 |
+
const targetWidth = 832;
|
| 947 |
+
const targetHeight = 480;
|
| 948 |
+
canvas.width = targetWidth;
|
| 949 |
+
canvas.height = targetHeight;
|
| 950 |
+
|
| 951 |
+
// Calculate source rectangle to crop center 832x480 from webcam
|
| 952 |
+
const videoWidth = video.videoWidth;
|
| 953 |
+
const videoHeight = video.videoHeight;
|
| 954 |
+
|
| 955 |
+
// Calculate crop dimensions maintaining aspect ratio
|
| 956 |
+
const sourceAspect = videoWidth / videoHeight;
|
| 957 |
+
const targetAspect = targetWidth / targetHeight;
|
| 958 |
+
|
| 959 |
+
let sx, sy, sWidth, sHeight;
|
| 960 |
+
|
| 961 |
+
if (sourceAspect > targetAspect) {
|
| 962 |
+
// Video is wider, crop sides
|
| 963 |
+
sHeight = videoHeight;
|
| 964 |
+
sWidth = videoHeight * targetAspect;
|
| 965 |
+
sx = (videoWidth - sWidth) / 2;
|
| 966 |
+
sy = 0;
|
| 967 |
+
} else {
|
| 968 |
+
// Video is taller, crop top/bottom
|
| 969 |
+
sWidth = videoWidth;
|
| 970 |
+
sHeight = videoWidth / targetAspect;
|
| 971 |
+
sx = 0;
|
| 972 |
+
sy = (videoHeight - sHeight) / 2;
|
| 973 |
+
}
|
| 974 |
+
|
| 975 |
+
// Draw cropped and scaled image
|
| 976 |
+
ctx.drawImage(video, sx, sy, sWidth, sHeight, 0, 0, targetWidth, targetHeight);
|
| 977 |
+
|
| 978 |
+
return new Promise((resolve) => {
|
| 979 |
+
canvas.toBlob(async (blob) => {
|
| 980 |
+
if (!blob) {
|
| 981 |
+
console.error('Failed to create blob from canvas');
|
| 982 |
+
resolve(null);
|
| 983 |
+
return;
|
| 984 |
+
}
|
| 985 |
+
try {
|
| 986 |
+
const arrayBuffer = await blob.arrayBuffer();
|
| 987 |
+
const uint8Array = new Uint8Array(arrayBuffer);
|
| 988 |
+
resolve(uint8Array);
|
| 989 |
+
} catch (error) {
|
| 990 |
+
console.error('Failed to convert blob to Uint8Array:', error);
|
| 991 |
+
resolve(null);
|
| 992 |
+
}
|
| 993 |
+
}, 'image/jpeg', 0.9);
|
| 994 |
+
});
|
| 995 |
+
},
|
| 996 |
+
|
| 997 |
+
startFrameExtraction() {
|
| 998 |
+
const inputFps = parseInt(document.getElementById('inputFps').value);
|
| 999 |
+
const intervalMs = Math.floor(1000 / inputFps);
|
| 1000 |
+
const video = document.getElementById('hiddenVideo');
|
| 1001 |
+
|
| 1002 |
+
if (this.mode === 'video') {
|
| 1003 |
+
video.play();
|
| 1004 |
+
}
|
| 1005 |
+
|
| 1006 |
+
this.frameExtractionInterval = setInterval(async () => {
|
| 1007 |
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
| 1008 |
+
this.stopFrameExtraction();
|
| 1009 |
+
return;
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
+
let frameBytes = null;
|
| 1013 |
+
if (this.mode === 'video') {
|
| 1014 |
+
if (video.ended) {
|
| 1015 |
+
video.currentTime = 0;
|
| 1016 |
+
video.play();
|
| 1017 |
+
}
|
| 1018 |
+
frameBytes = await this.extractVideoFrameBytes();
|
| 1019 |
+
} else if (this.mode === 'webcam') {
|
| 1020 |
+
frameBytes = await this.extractWebcamBytes();
|
| 1021 |
+
}
|
| 1022 |
+
|
| 1023 |
+
if (!frameBytes || frameBytes.length === 0) {
|
| 1024 |
+
console.warn('Empty frame, skipping');
|
| 1025 |
+
return;
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
const strengthValue = parseFloat(document.getElementById('strength').value);
|
| 1029 |
+
if (isNaN(strengthValue)) {
|
| 1030 |
+
console.error('Invalid strength value');
|
| 1031 |
+
return;
|
| 1032 |
+
}
|
| 1033 |
+
|
| 1034 |
+
const message = {
|
| 1035 |
+
image: frameBytes,
|
| 1036 |
+
strength: strengthValue,
|
| 1037 |
+
timestamp: Date.now()
|
| 1038 |
+
};
|
| 1039 |
+
|
| 1040 |
+
// For webcam mode, include prompt and num_blocks continually
|
| 1041 |
+
if (this.mode === 'webcam') {
|
| 1042 |
+
message.prompt = document.getElementById('prompt').value;
|
| 1043 |
+
message.num_blocks = parseInt(document.getElementById('numBlocks').value);
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
if (!(message.image instanceof Uint8Array)) {
|
| 1047 |
+
console.error('Frame bytes is not a Uint8Array');
|
| 1048 |
+
return;
|
| 1049 |
+
}
|
| 1050 |
+
|
| 1051 |
+
const encoded = createMsgpackEncoder(message);
|
| 1052 |
+
this.ws.send(encoded);
|
| 1053 |
+
}, intervalMs);
|
| 1054 |
+
},
|
| 1055 |
+
|
| 1056 |
+
stopFrameExtraction() {
|
| 1057 |
+
if (this.frameExtractionInterval) {
|
| 1058 |
+
clearInterval(this.frameExtractionInterval);
|
| 1059 |
+
this.frameExtractionInterval = null;
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
const video = document.getElementById('hiddenVideo');
|
| 1063 |
+
video.pause();
|
| 1064 |
+
video.currentTime = 0;
|
| 1065 |
+
},
|
| 1066 |
+
|
| 1067 |
randomizeSeed() {
|
| 1068 |
document.getElementById('seed').value = Math.floor(Math.random() * (1 << 24));
|
| 1069 |
},
|
|
|
|
| 1156 |
width: 832,
|
| 1157 |
height: 480
|
| 1158 |
};
|
| 1159 |
+
|
| 1160 |
+
// Handle start frame for video and webcam modes
|
| 1161 |
+
if (this.mode === 'video' && this.currentVideoFile) {
|
| 1162 |
+
try {
|
| 1163 |
+
const video = document.getElementById('hiddenVideo');
|
| 1164 |
+
if (!video) {
|
| 1165 |
+
throw new Error('Video element not found');
|
| 1166 |
+
}
|
| 1167 |
+
|
| 1168 |
+
video.currentTime = 0;
|
| 1169 |
+
|
| 1170 |
+
await new Promise((resolve, reject) => {
|
| 1171 |
+
const timeout = setTimeout(() => {
|
| 1172 |
+
reject(new Error('Video seek timeout'));
|
| 1173 |
+
}, 5000);
|
| 1174 |
+
|
| 1175 |
+
video.onseeked = () => {
|
| 1176 |
+
clearTimeout(timeout);
|
| 1177 |
+
resolve();
|
| 1178 |
+
};
|
| 1179 |
+
});
|
| 1180 |
+
|
| 1181 |
+
const startFrame = await this.extractVideoFrameBytes();
|
| 1182 |
+
if (!startFrame || !(startFrame instanceof Uint8Array)) {
|
| 1183 |
+
throw new Error('Failed to extract valid start frame');
|
| 1184 |
+
}
|
| 1185 |
+
|
| 1186 |
+
payload.start_frame = new Uint8Array(startFrame);
|
| 1187 |
+
console.log('Start frame extracted:', payload.start_frame.length, 'bytes');
|
| 1188 |
+
} catch (error) {
|
| 1189 |
+
this.showError(`Failed to extract start frame: ${error.message}`);
|
| 1190 |
+
return false;
|
| 1191 |
+
}
|
| 1192 |
+
} else if (this.mode === 'webcam') {
|
| 1193 |
+
try {
|
| 1194 |
+
const startFrame = await this.extractWebcamBytes();
|
| 1195 |
+
if (!startFrame || !(startFrame instanceof Uint8Array)) {
|
| 1196 |
+
throw new Error('Failed to extract valid start frame from webcam');
|
| 1197 |
+
}
|
| 1198 |
+
payload.start_frame = new Uint8Array(startFrame);
|
| 1199 |
+
console.log('Start frame extracted from webcam:', payload.start_frame.length, 'bytes');
|
| 1200 |
+
} catch (error) {
|
| 1201 |
+
this.showError(`Failed to extract start frame from webcam: ${error.message}`);
|
| 1202 |
+
return false;
|
| 1203 |
+
}
|
| 1204 |
+
}
|
| 1205 |
|
| 1206 |
const seedValue = document.getElementById('seed').value;
|
| 1207 |
if (seedValue && seedValue.trim() !== '') {
|
|
|
|
| 1209 |
} else {
|
| 1210 |
payload.seed = Math.floor(Math.random() * (1 << 24));
|
| 1211 |
}
|
| 1212 |
+
|
| 1213 |
+
// Clear canvas
|
| 1214 |
+
const canvas = document.getElementById('outputCanvas');
|
| 1215 |
+
if (canvas) {
|
| 1216 |
+
const ctx = canvas.getContext('2d');
|
| 1217 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 1218 |
+
}
|
| 1219 |
+
|
| 1220 |
+
this.frameBuffer = [];
|
| 1221 |
+
this.frameCount = 0;
|
| 1222 |
+
document.getElementById('frameCount').textContent = '0';
|
| 1223 |
+
this.updateProgress();
|
| 1224 |
+
|
| 1225 |
+
this.startRecording();
|
| 1226 |
|
| 1227 |
try {
|
| 1228 |
+
console.log('Sending payload:', {
|
| 1229 |
+
...payload,
|
| 1230 |
+
start_frame: payload.start_frame ? `[${payload.start_frame.length} bytes]` : undefined
|
| 1231 |
+
});
|
| 1232 |
+
|
| 1233 |
const encoded = createMsgpackEncoder(payload);
|
| 1234 |
this.ws.send(encoded);
|
| 1235 |
this.showInfo('Generation started!');
|
| 1236 |
+
this.isConnected = true;
|
| 1237 |
+
document.getElementById('statusPill').className = 'status-pill status-connected';
|
| 1238 |
+
document.getElementById('statusPill').textContent = 'Connected';
|
| 1239 |
+
|
| 1240 |
+
// Start frame extraction for video/webcam modes
|
| 1241 |
+
if (this.mode === 'video' || this.mode === 'webcam') {
|
| 1242 |
+
setTimeout(() => this.startFrameExtraction(), 500);
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
return true;
|
| 1246 |
} catch (error) {
|
| 1247 |
this.showError('Failed to send parameters: ' + error.message);
|
| 1248 |
+
return false;
|
| 1249 |
+
}
|
| 1250 |
+
},
|
| 1251 |
+
|
| 1252 |
+
startRecording() {
|
| 1253 |
+
const canvas = document.getElementById('outputCanvas');
|
| 1254 |
+
const fps = parseInt(document.getElementById('playbackFps').value);
|
| 1255 |
+
const stream = canvas.captureStream(fps);
|
| 1256 |
+
|
| 1257 |
+
this.recordedChunks = [];
|
| 1258 |
+
this.mediaRecorder = new MediaRecorder(stream, {
|
| 1259 |
+
mimeType: MediaRecorder.isTypeSupported('video/webm;codecs=vp9')
|
| 1260 |
+
? 'video/webm;codecs=vp9'
|
| 1261 |
+
: 'video/webm'
|
| 1262 |
+
});
|
| 1263 |
+
|
| 1264 |
+
this.mediaRecorder.ondataavailable = (event) => {
|
| 1265 |
+
if (event.data && event.data.size > 0) {
|
| 1266 |
+
this.recordedChunks.push(event.data);
|
| 1267 |
+
}
|
| 1268 |
+
};
|
| 1269 |
+
|
| 1270 |
+
this.mediaRecorder.start();
|
| 1271 |
+
},
|
| 1272 |
+
|
| 1273 |
+
stopRecording() {
|
| 1274 |
+
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
|
| 1275 |
+
this.mediaRecorder.stop();
|
| 1276 |
+
}
|
| 1277 |
+
},
|
| 1278 |
+
|
| 1279 |
+
updateProgress() {
|
| 1280 |
+
const progress = Math.min(100, (this.frameCount / this.maxFrames) * 100);
|
| 1281 |
+
document.getElementById('progressFill').style.width = progress + '%';
|
| 1282 |
+
|
| 1283 |
+
// Auto-disconnect when reaching max frames
|
| 1284 |
+
if (this.frameCount >= this.maxFrames && this.isGenerating) {
|
| 1285 |
+
this.showInfo(`Reached maximum frames (${this.maxFrames}). Disconnecting...`);
|
| 1286 |
+
setTimeout(() => this.disconnect(), 1000);
|
| 1287 |
}
|
| 1288 |
},
|
| 1289 |
|
|
|
|
| 1341 |
clearInterval(this.playbackInterval);
|
| 1342 |
this.playbackInterval = null;
|
| 1343 |
}
|
| 1344 |
+
this.stopWebcam();
|
| 1345 |
this.isGenerating = false;
|
| 1346 |
this.updateUI();
|
| 1347 |
},
|