Skip to content

Commit

Permalink
support video preview
Browse files Browse the repository at this point in the history
  • Loading branch information
6174 committed Jul 10, 2024
1 parent 137ad68 commit 9e7c7c0
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useCallback } from "react";
import { message } from "antd";
import {downloadFile, downloadImagesAsZip} from "@comflowy/common/utils/download-helper";
import { getImagePreviewUrl } from "@comflowy/common/comfyui-bridge/bridge";
import { usePreviewImages } from "../reactflow-node/reactflow-node-imagepreviews";

export function SaveImageMenuItem(props: NodeMenuProps) {
const {id, node} = props;
Expand All @@ -13,14 +14,7 @@ export function SaveImageMenuItem(props: NodeMenuProps) {
if (images.length === 0) {
return null;
}
const imageWithPreview = images.map(image => {
const imageSrc = getImagePreviewUrl(image.filename, image.type, image.subfolder)
return {
src: imageSrc,
filename: image.filename,
image
}
});;
const {mixed: imageWithPreview} = usePreviewImages(images)
const doDownload = useCallback(async () => {
try {
if (imageWithPreview.length === 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,39 @@ import { KEYS, t } from "@comflowy/common/i18n";
import { ImageWithDownload, PreviewGroupWithDownload } from './image-with-download';
import { PreviewImage } from '@comflowy/common/types';
import { downloadFile, downloadImagesAsZip } from '@comflowy/common/utils/download-helper';
import { usePreviewImages } from '../reactflow-node/reactflow-node-imagepreviews';
import { VideoPreview } from '../reactflow-input/input-video-player-async';

const Gallery = (props: {
editing?: boolean;
selectedImages?: PreviewImage[];
setSelectedImages?: (images: PreviewImage[]) => void;
}) => {
let images = useAppStore(st => st.persistedWorkflow.gallery || []);
const imagesWithSrc = images.map(image => {
const imageSrc = getImagePreviewUrl(image.filename, image.type, image.subfolder)
return {
src: imageSrc,
filename: image.filename,
image
}
});
const {mixed: imagesWithSrc} = usePreviewImages(images)

let $content = (
<PreviewGroupWithDownload images={imagesWithSrc}>
{imagesWithSrc.map((image, index) => {
return (
<Image
key={image.src + index}
src={image.src}
/>
)
if (image.isImage) {
return (
<Image
key={image.src + index}
src={image.src}
/>
)
}
if (image.isVideo) {
return (
<div className="video-preview-card image-item " key={image.src + index} style={{
width: 140,
height: 140
}}>
<VideoPreview key={image.src + index} url={image.src} />
</div>
)
}
return null
})}
</PreviewGroupWithDownload>
)
Expand Down Expand Up @@ -107,14 +115,7 @@ export const GalleryEntry = React.memo(() => {

const downloadImages = useCallback(async () => {
try {
const selectImagesWithSrc = selectedImages.map(image => {
const imageSrc = getImagePreviewUrl(image.filename, image.type, image.subfolder)
return {
src: imageSrc,
filename: image.filename,
image
}
});;
const {mixed: selectImagesWithSrc} = usePreviewImages(selectedImages)

if (selectImagesWithSrc.length === 1) {
await downloadFile(selectImagesWithSrc[0].src, selectImagesWithSrc[0].filename)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { EyeOutlined } from "@ant-design/icons";
import { Button, Modal, Space } from "antd";
import { Suspense, lazy, useEffect, useState } from "react";
import { isWindow } from "ui/utils/is-window";

const AsyncCO = lazy(async () => {
return await import("./input-video-player");
});

export function AsyncVideoPlayer(props: {url: string}) {
export function AsyncVideoPlayer(props: { url: string, controls?: boolean }) {
const [showFrontEndCode, setShowFrontEndCode] = useState(false);
useEffect(() => {
if (isWindow) {
Expand All @@ -18,7 +20,55 @@ export function AsyncVideoPlayer(props: {url: string}) {

return (
<Suspense fallback={<div>Loading...</div>}>
<AsyncCO {...props}/>
<AsyncCO {...props} />
</Suspense>
);
}

export function VideoPreview(props: { url: string, controls?: boolean }) {
const [isModalVisible, setIsModalVisible] = useState(false);

const showModal = () => {
setIsModalVisible(true);
};

const handleCancel = () => {
setIsModalVisible(false);
};

const handleDownload = () => {
window.open(props.url, '_blank');
};

return (
<>
<div onClick={showModal} className="video-preview">
<div className="inner">
<AsyncVideoPlayer url={props.url} />
</div>
<div className="preview-notication">
<Space><EyeOutlined /> Preview video</Space>
</div>
</div>

<Modal
title="Video Preview"
open={isModalVisible}
onCancel={handleCancel}
footer={[
<Button key="download" onClick={handleDownload}>
Download
</Button>,
<Button key="close" onClick={handleCancel}>
Close
</Button>,
]}
width={800}
>
<div className="wrapper" style={{ width: "100%", display: "flex", justifyContent: "center" }}>
<AsyncVideoPlayer url={props.url} controls />
</div>
</Modal>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import ReactPlayer from 'react-player';

interface InputVideoPlayerProps {
url: string;
controls?: boolean;
}

const InputVideoPlayer: React.FC<InputVideoPlayerProps> = ({ url }) => {
const InputVideoPlayer: React.FC<InputVideoPlayerProps> = ({ url, controls }) => {
const playerRef = useRef(null);

useEffect(() => {
Expand All @@ -16,7 +17,7 @@ const InputVideoPlayer: React.FC<InputVideoPlayerProps> = ({ url }) => {
}, []);

return (
<ReactPlayer ref={playerRef} url={url} controls />
<ReactPlayer ref={playerRef} url={url} controls={controls ?? true} />
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useAppStore } from '@comflowy/common/store';
import { RcFile } from 'antd/es/upload';
import { getImagePreviewUrl, getUploadImageUrl } from '@comflowy/common/comfyui-bridge/bridge';
import { ImageWithDownload } from '../reactflow-gallery/image-with-download';
import { AsyncVideoPlayer } from './input-video-player-async';
import { AsyncVideoPlayer, VideoPreview } from './input-video-player-async';

export function InputUploadVideo({widget, node, id}: {
widget: Widget,
Expand Down Expand Up @@ -112,7 +112,12 @@ export function InputUploadVideo({widget, node, id}: {
}}
/>}
{!isGif && previewImage && (
<AsyncVideoPlayer url={previewImage}/>
<div className="video-preview-card" style={{
width: "100%",
height: 300
}}>
<VideoPreview url={previewImage} controls={false} />
</div>
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,12 @@ import { PreviewImage } from "@comflowy/common/types";
import { PreviewGroupWithDownload } from "../reactflow-gallery/image-with-download";
import { Image } from 'antd';
import {memo} from "react";
import { VideoPreview } from "../reactflow-input/input-video-player-async";

export const NodeImagePreviews = memo(({ imagePreviews }: {
imagePreviews: PreviewImage[]
}) => {
const imagePreviewsWithSrc = (imagePreviews || []).map((image, index) => {
if (image.blobUrl) {
return {
src: image.blobUrl,
filename: image.filename || "Untitled"
}
}
const imageSrc = getImagePreviewUrl(image.filename, image.type, image.subfolder)
return {
src: imageSrc,
filename: image.filename
}
});
const {images: imagePreviewsWithSrc, videos} = usePreviewImages(imagePreviews)

return (
<div className={`node-images-preview ${imagePreviews.length > 1 ? "multiple" : "single"}`} >
Expand All @@ -37,8 +26,53 @@ export const NodeImagePreviews = memo(({ imagePreviews }: {
})
}
</PreviewGroupWithDownload>
{videos.map((video, index) => {
return (
<div className="video-preview-card" key={video.src + index} style={{
width: "100%",
height: 300
}}>
<VideoPreview key={video.src + index} url={video.src} />
</div>
)
})}
</div>
</div>
)
});


export function usePreviewImages(imagePreviews: PreviewImage[]) {
const imagePreviewsWithSrc = (imagePreviews || []).map((image, index) => {
if (image.blobUrl) {
return {
src: image.blobUrl,
filename: image.filename || "Untitled",
image,
isImage: true,
isVideo: false
}
}
const imageSrc = getImagePreviewUrl(image.filename, image.type, image.subfolder)
const isVideo = image.filename.endsWith("mp4") || image.filename.endsWith("mov") || image.filename.endsWith("avi") || image.filename.endsWith("webm")
const isImage = image.filename.endsWith("png") || image.filename.endsWith("jpg") || image.filename.endsWith("jpeg") || image.filename.endsWith("gif")
return {
src: imageSrc,
filename: image.filename,
image,
isVideo,
isImage
}
});

const videos = imagePreviewsWithSrc.filter(img => {
return img.isVideo;
});

const images = imagePreviewsWithSrc.filter(img => {
return img.isImage;
});

return { mixed: imagePreviewsWithSrc, images, videos };

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,32 @@ export function WsController(props: {clientId: string}): JSX.Element {
images.push(...a_images);
images.push(...b_images);
}

const gifs = msg.data.output?.gifs;
if (gifs) {
images.push(...gifs);
}

const videos = msg.data.output?.videos;
if (videos) {
images.push(...videos);
}

const audios = msg.data.output?.audios;
if (audios) {
images.push(...audios);
}

const files = msg.data.output?.files;
if (files) {
images.push(...files);
}

const texts = msg.data.output?.texts;
if (texts) {
images.push(...texts);
}

if (Array.isArray(images) && images.length > 0) {
onImageSave(msg.data.node, images)
}
Expand Down
41 changes: 41 additions & 0 deletions apps/electron-frontend/src/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,45 @@ span.meta, .ant-tag {
&:hover, &.active {
background-color: var(--backgroundColorL3);
}
}


.video-preview {
height: 100%;
width: 100%;
position: relative;
overflow: hidden;
border-radius: 6px;
.inner {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.preview-notication {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.5);
color: white;
font-size: 14px;
font-weight: 500;
padding: 10px;
border-radius: 10px;
opacity: 0;
visibility: hidden;
transition: var(--transition);
cursor: pointer;
}
&:hover {
.preview-notication {
opacity: 1;
visibility: visible;
}
}
}

0 comments on commit 9e7c7c0

Please sign in to comment.