multimodalart HF Staff commited on
Commit
e886e32
·
verified ·
1 Parent(s): f014a7c

Update index.html

Browse files
Files changed (1) hide show
  1. 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
  },