mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
[3d] add record video support for load3d animation node (#3798)
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
@background-image-change="listenBackgroundImageChange"
|
@background-image-change="listenBackgroundImageChange"
|
||||||
@animation-list-change="animationListChange"
|
@animation-list-change="animationListChange"
|
||||||
@up-direction-change="listenUpDirectionChange"
|
@up-direction-change="listenUpDirectionChange"
|
||||||
|
@recording-status-change="listenRecordingStatusChange"
|
||||||
/>
|
/>
|
||||||
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||||
<Load3DControls
|
<Load3DControls
|
||||||
@@ -66,6 +67,21 @@
|
|||||||
@animation-change="animationChange"
|
@animation-change="animationChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showRecordingControls"
|
||||||
|
class="absolute top-12 right-2 z-20 pointer-events-auto"
|
||||||
|
>
|
||||||
|
<RecordingControls
|
||||||
|
:node="node"
|
||||||
|
:is-recording="isRecording"
|
||||||
|
:has-recording="hasRecording"
|
||||||
|
:recording-duration="recordingDuration"
|
||||||
|
@start-recording="handleStartRecording"
|
||||||
|
@stop-recording="handleStopRecording"
|
||||||
|
@export-recording="handleExportRecording"
|
||||||
|
@clear-recording="handleClearRecording"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -75,6 +91,7 @@ import { computed, ref } from 'vue'
|
|||||||
import Load3DAnimationControls from '@/components/load3d/Load3DAnimationControls.vue'
|
import Load3DAnimationControls from '@/components/load3d/Load3DAnimationControls.vue'
|
||||||
import Load3DAnimationScene from '@/components/load3d/Load3DAnimationScene.vue'
|
import Load3DAnimationScene from '@/components/load3d/Load3DAnimationScene.vue'
|
||||||
import Load3DControls from '@/components/load3d/Load3DControls.vue'
|
import Load3DControls from '@/components/load3d/Load3DControls.vue'
|
||||||
|
import RecordingControls from '@/components/load3d/controls/RecordingControls.vue'
|
||||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
import {
|
import {
|
||||||
AnimationItem,
|
AnimationItem,
|
||||||
@@ -111,6 +128,11 @@ const selectedSpeed = ref(1)
|
|||||||
const selectedAnimation = ref(0)
|
const selectedAnimation = ref(0)
|
||||||
const backgroundImage = ref('')
|
const backgroundImage = ref('')
|
||||||
|
|
||||||
|
const isRecording = ref(false)
|
||||||
|
const hasRecording = ref(false)
|
||||||
|
const recordingDuration = ref(0)
|
||||||
|
const showRecordingControls = ref(!inputSpec.isPreview)
|
||||||
|
|
||||||
const showPreviewButton = computed(() => {
|
const showPreviewButton = computed(() => {
|
||||||
return !type.includes('Preview')
|
return !type.includes('Preview')
|
||||||
})
|
})
|
||||||
@@ -133,6 +155,54 @@ const handleMouseLeave = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleStartRecording = async () => {
|
||||||
|
const sceneRef = load3DAnimationSceneRef.value?.load3DSceneRef
|
||||||
|
if (sceneRef?.load3d) {
|
||||||
|
await sceneRef.load3d.startRecording()
|
||||||
|
isRecording.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStopRecording = () => {
|
||||||
|
const sceneRef = load3DAnimationSceneRef.value?.load3DSceneRef
|
||||||
|
if (sceneRef?.load3d) {
|
||||||
|
sceneRef.load3d.stopRecording()
|
||||||
|
isRecording.value = false
|
||||||
|
hasRecording.value = true
|
||||||
|
recordingDuration.value = sceneRef.load3d.getRecordingDuration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleExportRecording = () => {
|
||||||
|
const sceneRef = load3DAnimationSceneRef.value?.load3DSceneRef
|
||||||
|
if (sceneRef?.load3d) {
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||||
|
const filename = `${timestamp}-animation-recording.mp4`
|
||||||
|
sceneRef.load3d.exportRecording(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClearRecording = () => {
|
||||||
|
const sceneRef = load3DAnimationSceneRef.value?.load3DSceneRef
|
||||||
|
if (sceneRef?.load3d) {
|
||||||
|
sceneRef.load3d.clearRecording()
|
||||||
|
hasRecording.value = false
|
||||||
|
recordingDuration.value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listenRecordingStatusChange = (value: boolean) => {
|
||||||
|
isRecording.value = value
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
const sceneRef = load3DAnimationSceneRef.value?.load3DSceneRef
|
||||||
|
if (sceneRef?.load3d) {
|
||||||
|
hasRecording.value = true
|
||||||
|
recordingDuration.value = sceneRef.load3d.getRecordingDuration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const switchCamera = () => {
|
const switchCamera = () => {
|
||||||
cameraType.value =
|
cameraType.value =
|
||||||
cameraType.value === 'perspective' ? 'orthographic' : 'perspective'
|
cameraType.value === 'perspective' ? 'orthographic' : 'perspective'
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
@camera-type-change="listenCameraTypeChange"
|
@camera-type-change="listenCameraTypeChange"
|
||||||
@show-grid-change="listenShowGridChange"
|
@show-grid-change="listenShowGridChange"
|
||||||
@show-preview-change="listenShowPreviewChange"
|
@show-preview-change="listenShowPreviewChange"
|
||||||
|
@recording-status-change="listenRecordingStatusChange"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -148,6 +149,15 @@ watch(
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'animationListChange', animationList: string): void
|
(e: 'animationListChange', animationList: string): void
|
||||||
|
(e: 'materialModeChange', materialMode: string): void
|
||||||
|
(e: 'backgroundColorChange', color: string): void
|
||||||
|
(e: 'lightIntensityChange', lightIntensity: number): void
|
||||||
|
(e: 'fovChange', fov: number): void
|
||||||
|
(e: 'cameraTypeChange', cameraType: string): void
|
||||||
|
(e: 'showGridChange', showGrid: boolean): void
|
||||||
|
(e: 'showPreviewChange', showPreview: boolean): void
|
||||||
|
(e: 'upDirectionChange', direction: string): void
|
||||||
|
(e: 'recording-status-change', status: boolean): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const listenMaterialModeChange = (mode: MaterialMode) => {
|
const listenMaterialModeChange = (mode: MaterialMode) => {
|
||||||
@@ -182,6 +192,10 @@ const listenShowPreviewChange = (value: boolean) => {
|
|||||||
showPreview.value = value
|
showPreview.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const listenRecordingStatusChange = (value: boolean) => {
|
||||||
|
emit('recording-status-change', value)
|
||||||
|
}
|
||||||
|
|
||||||
const animationListeners = {
|
const animationListeners = {
|
||||||
animationListChange: (newValue: any) => {
|
animationListChange: (newValue: any) => {
|
||||||
emit('animationListChange', newValue)
|
emit('animationListChange', newValue)
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ useExtensionService().registerExtension({
|
|||||||
const width = node.widgets?.find((w: IWidget) => w.name === 'width')
|
const width = node.widgets?.find((w: IWidget) => w.name === 'width')
|
||||||
const height = node.widgets?.find((w: IWidget) => w.name === 'height')
|
const height = node.widgets?.find((w: IWidget) => w.name === 'height')
|
||||||
|
|
||||||
if (modelWidget && width && height && sceneWidget) {
|
if (modelWidget && width && height && sceneWidget && load3d) {
|
||||||
const config = new Load3DConfiguration(load3d)
|
const config = new Load3DConfiguration(load3d)
|
||||||
|
|
||||||
config.configure('input', modelWidget, cameraState, width, height)
|
config.configure('input', modelWidget, cameraState, width, height)
|
||||||
@@ -375,6 +375,10 @@ useExtensionService().registerExtension({
|
|||||||
|
|
||||||
load3d.toggleAnimation(false)
|
load3d.toggleAnimation(false)
|
||||||
|
|
||||||
|
if (load3d.isRecording()) {
|
||||||
|
load3d.stopRecording()
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
scene: imageData,
|
scene: imageData,
|
||||||
mask: maskData,
|
mask: maskData,
|
||||||
@@ -392,12 +396,23 @@ useExtensionService().registerExtension({
|
|||||||
|
|
||||||
load3d.handleResize()
|
load3d.handleResize()
|
||||||
|
|
||||||
return {
|
const returnVal = {
|
||||||
image: `threed/${data.name} [temp]`,
|
image: `threed/${data.name} [temp]`,
|
||||||
mask: `threed/${dataMask.name} [temp]`,
|
mask: `threed/${dataMask.name} [temp]`,
|
||||||
normal: `threed/${dataNormal.name} [temp]`,
|
normal: `threed/${dataNormal.name} [temp]`,
|
||||||
camera_info: node.properties['Camera Info']
|
camera_info: node.properties['Camera Info'],
|
||||||
|
recording: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const recordingData = load3d.getRecordingData()
|
||||||
|
if (recordingData) {
|
||||||
|
const [recording] = await Promise.all([
|
||||||
|
Load3dUtils.uploadTempImage(recordingData, 'recording', 'mp4')
|
||||||
|
])
|
||||||
|
returnVal['recording'] = `threed/${recording.name} [temp]`
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnVal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ class Load3d {
|
|||||||
return (
|
return (
|
||||||
this.STATUS_MOUSE_ON_NODE ||
|
this.STATUS_MOUSE_ON_NODE ||
|
||||||
this.STATUS_MOUSE_ON_SCENE ||
|
this.STATUS_MOUSE_ON_SCENE ||
|
||||||
|
this.isRecording() ||
|
||||||
!this.INITIAL_RENDER_DONE
|
!this.INITIAL_RENDER_DONE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -461,7 +462,7 @@ class Load3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isRecording(): boolean {
|
public isRecording(): boolean {
|
||||||
return this.recordingManager.hasRecording()
|
return this.recordingManager.getIsRecording()
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRecordingDuration(): number {
|
public getRecordingDuration(): number {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export class RecordingManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recordingDuration = (Date.now() - this.recordingStartTime) / 1000 // In seconds
|
this.recordingDuration = (Date.now() - this.recordingStartTime) / 1000
|
||||||
|
|
||||||
this.mediaRecorder.stop()
|
this.mediaRecorder.stop()
|
||||||
if (this.recordingStream) {
|
if (this.recordingStream) {
|
||||||
@@ -114,6 +114,10 @@ export class RecordingManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getIsRecording(): boolean {
|
||||||
|
return this.isRecording
|
||||||
|
}
|
||||||
|
|
||||||
public hasRecording(): boolean {
|
public hasRecording(): boolean {
|
||||||
return this.recordedChunks.length > 0
|
return this.recordedChunks.length > 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user