diff --git a/components/pages/style/work-flow.css b/components/pages/style/work-flow.css index fe016f9..49c7ddb 100644 --- a/components/pages/style/work-flow.css +++ b/components/pages/style/work-flow.css @@ -165,7 +165,7 @@ display: grid; grid-auto-rows: auto; justify-content: center; - align-items: center; + align-items: self-start; } .videoContainer-qteKNi { flex: 1; @@ -181,7 +181,7 @@ object-position: center; background-color: #0003; border-radius: 8px; - height: 100%; + /* height: 100%; */ } .container-kIPoeH { box-sizing: border-box; diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx index f090f8b..fcb3276 100644 --- a/components/pages/work-flow.tsx +++ b/components/pages/work-flow.tsx @@ -448,8 +448,13 @@ Please process this video editing request.`; ref={containerRef} > {isDesktop ? ( -
+
handleRetryVideo(video_id)} + aspectRatio={aspectRatio} /> + + {['scene', 'character', 'video', 'final_video'].includes(taskObject.currentStage) && ( +
+ { + if (index === -1 && taskObject.final.url) { + // 点击最终视频 + setSelectedView('final'); + setCurrentSketchIndex(0); + } else { + // 点击普通视频 + taskObject.final.url && setSelectedView('video'); + setCurrentSketchIndex(index); + } + }} + onRetryVideo={handleRetryVideo} + className={isDesktop ? 'auto-cols-[20%]' : (isTablet ? 'auto-cols-[25%]' : 'auto-cols-[33%]')} + cols={isDesktop ? '20%' : isTablet ? '25%' : '33%'} + selectedView={selectedView} + aspectRatio={aspectRatio} + isMobile={isMobile} + /> +
+ )} +
) : ( - setIsSmartChatBoxOpen(true)} - setVideoPreview={(url, id) => { - setPreviewVideoUrl(url); - setPreviewVideoId(id); - }} - showGotoCutButton={showGotoCutButton || editingStatus !== 'idle'} - onGotoCut={generateEditPlan} - isSmartChatBoxOpen={isSmartChatBoxOpen} - onRetryVideo={(video_id) => handleRetryVideo(video_id)} - onSelectView={(view) => setSelectedView(view)} - enableVideoEdit={true} - onVideoEditDescriptionSubmit={handleVideoEditDescriptionSubmit} - projectId={episodeId} - /> +
+ setIsSmartChatBoxOpen(true)} + setVideoPreview={(url, id) => { + setPreviewVideoUrl(url); + setPreviewVideoId(id); + }} + showGotoCutButton={showGotoCutButton || editingStatus !== 'idle'} + onGotoCut={generateEditPlan} + isSmartChatBoxOpen={isSmartChatBoxOpen} + onRetryVideo={(video_id) => handleRetryVideo(video_id)} + onSelectView={(view) => setSelectedView(view)} + enableVideoEdit={true} + onVideoEditDescriptionSubmit={handleVideoEditDescriptionSubmit} + projectId={episodeId} + aspectRatio={aspectRatio === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16'} + /> + + {['scene', 'character', 'video', 'final_video'].includes(taskObject.currentStage) && ( +
+ { + if (index === -1) { + // 点击最终视频 + setSelectedView('final'); + setCurrentSketchIndex(0); + } else { + // 点击普通视频 + setSelectedView('video'); + setCurrentSketchIndex(index); + } + }} + onRetryVideo={handleRetryVideo} + className={isDesktop ? 'auto-cols-[20%]' : (isTablet ? 'auto-cols-[25%]' : 'auto-cols-[33%]')} + cols={isDesktop ? '20%' : isTablet ? '25%' : '33%'} + selectedView={selectedView} + aspectRatio={aspectRatio} + isMobile={isMobile} + /> +
+ )} +
)}
- {taskObject.currentStage !== 'script' && ( -
- { - if (index === -1) { - // 点击最终视频 - setSelectedView('final'); - setCurrentSketchIndex(0); - } else { - // 点击普通视频 - setSelectedView('video'); - setCurrentSketchIndex(index); - } - }} - onRetryVideo={handleRetryVideo} - className={isDesktop ? 'auto-cols-[20%]' : (isTablet ? 'auto-cols-[25%]' : 'auto-cols-[33%]')} - cols={isDesktop ? '20%' : isTablet ? '25%' : '33%'} - selectedView={selectedView} - aspectRatio={aspectRatio} - isMobile={isMobile} - /> -
- )} + diff --git a/components/pages/work-flow/H5MediaViewer.tsx b/components/pages/work-flow/H5MediaViewer.tsx index f3756c6..dba81ec 100644 --- a/components/pages/work-flow/H5MediaViewer.tsx +++ b/components/pages/work-flow/H5MediaViewer.tsx @@ -49,6 +49,8 @@ interface H5MediaViewerProps { onVideoEditDescriptionSubmit?: (editPoint: any, description: string) => void; /** 项目ID */ projectId?: string; + /** 视频比例 */ + aspectRatio?: string; } /** @@ -76,7 +78,8 @@ export function H5MediaViewer({ onSelectView, enableVideoEdit, onVideoEditDescriptionSubmit, - projectId + projectId, + aspectRatio }: H5MediaViewerProps) { const carouselRef = useRef(null); const videoRefs = useRef>([]); @@ -85,10 +88,34 @@ export function H5MediaViewer({ const [isPlaying, setIsPlaying] = useState(false); const [isCatalogOpen, setIsCatalogOpen] = useState(false); + /** 解析形如 "16:9" 的比例字符串 */ + const parseAspect = (input?: string): { w: number; h: number } => { + const parts = (typeof input === 'string' ? input.split(':') : []); + const w = Number(parts[0]); + const h = Number(parts[1]); + return { + w: Number.isFinite(w) && w > 0 ? w : 16, + h: Number.isFinite(h) && h > 0 ? h : 9 + }; + }; + + /** 视频轮播容器高度:按 aspectRatio 计算(基于视口宽度) */ + const videoWrapperHeight = useMemo(() => { + const { w, h } = parseAspect(aspectRatio); + return `calc(100vw * ${h} / ${w})`; + }, [aspectRatio]); + + /** 图片轮播容器高度:默认 16:9 */ + const imageWrapperHeight = useMemo(() => { + // return 'calc(100vw * 9 / 16)'; + const { w, h } = parseAspect(aspectRatio); + return `calc(100vw * ${h} / ${w})`; + }, [aspectRatio]); + // 计算当前阶段类型 const stage = (selectedView === 'final' && taskObject.final?.url) ? 'final_video' - : (!['script', 'character', 'scene'].includes(taskObject.currentStage) ? 'video' : taskObject.currentStage); + : (!['init', 'script', 'character', 'scene'].includes(taskObject.currentStage) ? 'video' : taskObject.currentStage); // 生成各阶段对应的 slides 数据 const videoUrls = useMemo(() => { @@ -103,16 +130,19 @@ export function H5MediaViewer({ return []; }, [stage, taskObject.final?.url, taskObject.videos?.data]); - const imageUrls = useMemo(() => { + const imageItems = useMemo(() => { if (stage === 'scene' || stage === 'character') { const roles = (taskObject.roles?.data ?? []) as Array; const scenes = (taskObject.scenes?.data ?? []) as Array; console.log('h5-media-viewer:stage', stage); console.log('h5-media-viewer:roles', roles); console.log('h5-media-viewer:scenes', scenes); - return [...roles, ...scenes].map(item => item?.url).filter(Boolean) as string[]; + return [...roles, ...scenes].map(item => ({ + url: item?.url as string | undefined, + status: item?.status as number | undefined, + })); } - return []; + return [] as Array<{ url?: string; status?: number }>; }, [stage, taskObject.roles?.data, taskObject.scenes?.data]); // 占位,避免未使用警告 @@ -164,7 +194,7 @@ export function H5MediaViewer({ // 渲染视频 slide const renderVideoSlides = () => (
(videoRefs.current[idx] = el)} className="w-full h-full object-contain [transform:translateZ(0)] [backface-visibility:hidden] [will-change:transform] bg-black" style={{ - maxHeight: 'calc(100vh - 20rem)', + maxHeight: '100%', }} src={url} preload="metadata" @@ -224,7 +254,7 @@ export function H5MediaViewer({ ) : (
{status === 0 && ( Generating... @@ -250,11 +280,11 @@ export function H5MediaViewer({ // 渲染图片 slide const renderImageSlides = () => (
- {imageUrls.map((url, idx) => ( -
- scene -
- ))} + {imageItems.map((item, idx) => { + const status = item?.status; + const url = item?.url; + const showImage = status === 1 && typeof url === 'string' && url.length > 0; + return ( +
+ {showImage ? ( + scene + ) : ( +
+ {status === 0 && ( + Generating... + )} + {status === 2 && ( +
+
⚠️
+ Generate failed +
+ )} +
+ )} +
+ ); + })}
); @@ -285,7 +336,10 @@ export function H5MediaViewer({ } }; return ( -
+
{scriptData ? ( <> +
{stage === 'final_video' && videoUrls.length > 0 && renderVideoSlides()} {stage === 'video' && videoUrls.length > 0 && renderVideoSlides()} - {(stage === 'scene' || stage === 'character') && imageUrls.length > 0 && renderImageSlides()} + {(stage === 'scene' || stage === 'character') && imageItems.length > 0 && renderImageSlides()} {/* 全局固定操作区(右下角)视频暂停时展示 */} {(stage === 'video' || stage === 'final_video') && !isPlaying && ( -
+
{stage === 'video' && ( <> {` [data-alt='carousel-wrapper'] .slick-slide { display: flex !important;justify-content: center; } - .slick-slider { height: 100% !important;display: flex !important; } + .slick-slider { height: 100% !important; } .ant-carousel { height: 100% !important; } - .slick-list { width: 100%;height: 100% !important;max-height: calc(100vh - 20rem); } + .slick-list { width: 100%;height: 100% !important;max-height: 100%; } .slick-track { display: flex !important; align-items: center;height: 100% !important; } + [data-alt='carousel-wrapper'] .slick-arrow { z-index: 70 !important; } + [data-alt='carousel-wrapper'] .slick-prev { left: 8px; } + [data-alt='carousel-wrapper'] .slick-next { right: 8px; } `}
); diff --git a/components/pages/work-flow/media-viewer.tsx b/components/pages/work-flow/media-viewer.tsx index b0de51f..134de1d 100644 --- a/components/pages/work-flow/media-viewer.tsx +++ b/components/pages/work-flow/media-viewer.tsx @@ -15,6 +15,8 @@ import { downloadVideo, downloadAllVideos, getFirstFrame } from '@/utils/tools'; import { VideoEditOverlay } from './video-edit/VideoEditOverlay'; import { EditPoint as EditPointType } from './video-edit/types'; import { isVideoModificationEnabled } from '@/lib/server-config'; +import error_image from '@/public/assets/error.webp'; +import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector'; interface MediaViewerProps { taskObject: TaskObject; @@ -38,6 +40,7 @@ interface MediaViewerProps { enableVideoEdit?: boolean; onVideoEditDescriptionSubmit?: (editPoint: EditPointType, description: string) => void; projectId?: string; + aspectRatio: AspectRatioValue; } export const MediaViewer = React.memo(function MediaViewer({ @@ -61,7 +64,8 @@ export const MediaViewer = React.memo(function MediaViewer({ onRetryVideo, enableVideoEdit = true, onVideoEditDescriptionSubmit, - projectId + projectId, + aspectRatio }: MediaViewerProps) { const mainVideoRef = useRef(null); const finalVideoRef = useRef(null); @@ -212,6 +216,9 @@ export const MediaViewer = React.memo(function MediaViewer({