diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png
index d19e6a4b8..8b65e56c3 100644
Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ
diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png
index f4624a45b..cc881f9ba 100644
Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index d5bb80f89..95a5a7e4d 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -7,14 +7,20 @@
"comingSoon": "Coming Soon",
"download": "Download",
"downloadImage": "Download image",
+ "downloadVideo": "Download video",
"editOrMaskImage": "Edit or mask image",
"removeImage": "Remove image",
+ "removeVideo": "Remove video",
"viewImageOfTotal": "View image {index} of {total}",
+ "viewVideoOfTotal": "View video {index} of {total}",
"imagePreview": "Image preview - Use arrow keys to navigate between images",
+ "videoPreview": "Video preview - Use arrow keys to navigate between videos",
"galleryImage": "Gallery image",
"galleryThumbnail": "Gallery thumbnail",
"errorLoadingImage": "Error loading image",
+ "errorLoadingVideo": "Error loading video",
"failedToDownloadImage": "Failed to download image",
+ "failedToDownloadVideo": "Failed to download video",
"calculatingDimensions": "Calculating dimensions",
"import": "Import",
"loadAllFolders": "Load All Folders",
diff --git a/src/renderer/extensions/vueNodes/VideoPreview.vue b/src/renderer/extensions/vueNodes/VideoPreview.vue
new file mode 100644
index 000000000..901876b59
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/VideoPreview.vue
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+
+
{{ $t('g.videoFailedToLoad') }}
+
+ {{ getVideoFilename(currentVideoUrl) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('g.errorLoadingVideo') }}
+
+
+ {{ $t('g.loading') }}...
+
+
+ {{ actualDimensions || $t('g.calculatingDimensions') }}
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue
index 2911abbc0..b1a7f9acf 100644
--- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue
+++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue
@@ -101,7 +101,7 @@
@@ -267,10 +267,10 @@ onMounted(() => {
// Track collapsed state
const isCollapsed = computed(() => nodeData.flags?.collapsed ?? false)
-// Check if node has custom content (like image outputs)
+// Check if node has custom content (like image/video outputs)
const hasCustomContent = computed(() => {
- // Show custom content if node has image outputs
- return nodeImageUrls.value.length > 0
+ // Show custom content if node has media outputs
+ return !!nodeMedia.value && nodeMedia.value.urls.length > 0
})
// Computed classes and conditions for better reusability
@@ -340,26 +340,29 @@ const nodeOutputs = useNodeOutputStore()
const nodeOutputLocatorId = computed(() =>
nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id
)
-const nodeImageUrls = computed(() => {
- const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value]
+
+const lgraphNode = computed(() => {
const locatorId = getLocatorIdFromNodeData(nodeData)
-
- // Use root graph for getNodeByLocatorId since it needs to traverse from root
const rootGraph = app.graph?.rootGraph || app.graph
- if (!rootGraph) {
- return []
- }
+ if (!rootGraph) return null
+ return getNodeByLocatorId(rootGraph, locatorId)
+})
- const node = getNodeByLocatorId(rootGraph, locatorId)
+const nodeMedia = computed(() => {
+ const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value]
+ const node = lgraphNode.value
+ // Note: Despite the field name "images", videos are also included.
+ // The actual media type is determined by node.previewMediaType
+ // TODO: fix the backend to return videos using the vidoes key instead of the images key
if (node && newOutputs?.images?.length) {
const urls = nodeOutputs.getNodeImageUrls(node)
- if (urls) {
- return urls
+ if (urls && urls.length > 0) {
+ const type = node.previewMediaType === 'video' ? 'video' : 'image'
+ return { type, urls } as const
}
}
- // Clear URLs if no outputs or no images
- return []
+ return undefined
})
const nodeContainerRef = ref()
diff --git a/src/renderer/extensions/vueNodes/components/NodeContent.vue b/src/renderer/extensions/vueNodes/components/NodeContent.vue
index 0874c4013..0b5b00a47 100644
--- a/src/renderer/extensions/vueNodes/components/NodeContent.vue
+++ b/src/renderer/extensions/vueNodes/components/NodeContent.vue
@@ -5,9 +5,15 @@
+
@@ -20,24 +26,24 @@ import { computed, onErrorCaptured, ref } from 'vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling'
-import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import VideoPreview from '../VideoPreview.vue'
import ImagePreview from './ImagePreview.vue'
interface NodeContentProps {
- node?: LGraphNode // For backwards compatibility
- nodeData?: VueNodeData // New clean data structure
- imageUrls?: string[]
+ nodeData?: VueNodeData
+ media?: {
+ type: 'image' | 'video'
+ urls: string[]
+ }
}
const props = defineProps()
-const hasImages = computed(() => props.imageUrls && props.imageUrls.length > 0)
+const hasMedia = computed(() => props.media && props.media.urls.length > 0)
-// Get node ID from nodeData or node prop
-const nodeId = computed(() => {
- return props.nodeData?.id?.toString() || props.node?.id?.toString()
-})
+// Get node ID from nodeData
+const nodeId = computed(() => props.nodeData?.id?.toString())
// Error boundary implementation
const renderError = ref(null)
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
index c1c313890..dcdc4c9ce 100644
--- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
@@ -83,6 +83,7 @@ const specDescriptor = computed<{
const allowUpload =
image_upload === true ||
animated_image_upload === true ||
+ video_upload === true ||
audio_upload === true
return {
kind,
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
index 83d6854eb..9c16f8373 100644
--- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
@@ -1,5 +1,5 @@
@@ -81,9 +94,17 @@ function handleImageLoad(event: Event) {
>
+
![]()
diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
index 154b765cf..2b43fc127 100644
--- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
+++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/types.ts
@@ -1,9 +1,13 @@
+import type { ComputedRef, InjectionKey } from 'vue'
+
+import type { AssetKind } from '@/types/widgetTypes'
+
export type OptionId = string | number | symbol
export type SelectedKey = OptionId
export interface DropdownItem {
id: SelectedKey
- imageSrc: string
+ mediaSrc: string // URL for image, video, or other media
name: string
metadata: string
}
@@ -19,3 +23,6 @@ export interface FilterOption {
}
export type LayoutMode = 'list' | 'grid' | 'list-small'
+
+export const AssetKindKey: InjectionKey
> =
+ Symbol('assetKind')