forked from 77media/video-flow
重试视频,等待 接口 支持 完善
This commit is contained in:
parent
8616e2ab34
commit
150c1636bb
@ -665,6 +665,16 @@ export const regenerateShot = async (request: {
|
|||||||
return post("/movie/regenerate_shot_video", request);
|
return post("/movie/regenerate_shot_video", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 重新生成视频
|
||||||
|
export const regenerateVideo = async (request: {
|
||||||
|
/** 项目ID */
|
||||||
|
project_id: string;
|
||||||
|
/** 视频ID */
|
||||||
|
video_id: string;
|
||||||
|
}): Promise<ApiResponse<any>> => {
|
||||||
|
return post("/movie_cut/regenerate_video", request);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜列表
|
* 获取分镜列表
|
||||||
|
|||||||
@ -139,6 +139,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
taskObject={taskObject}
|
taskObject={taskObject}
|
||||||
currentSketchIndex={currentSketchIndex}
|
currentSketchIndex={currentSketchIndex}
|
||||||
onSketchSelect={setCurrentSketchIndex}
|
onSketchSelect={setCurrentSketchIndex}
|
||||||
|
onRetryVideo={handleRetryVideo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -430,10 +430,14 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
|
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
|
||||||
<div
|
<div
|
||||||
className="text-red-500 text-2xl font-bold flex items-center gap-2"
|
className="text-[#813b9dcc] text-2xl font-bold flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<RotateCcw className="w-10 h-10" />
|
<RotateCcw className="w-10 h-10 cursor-pointer" onClick={() => {
|
||||||
<span>Retry</span>
|
const video = taskObject.videos.data[currentSketchIndex];
|
||||||
|
if (onRetryVideo && video?.video_id) {
|
||||||
|
onRetryVideo(video.video_id);
|
||||||
|
}
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -442,7 +446,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
|
|
||||||
|
|
||||||
{/* 视频 多个 取第一个 */}
|
{/* 视频 多个 取第一个 */}
|
||||||
{ taskObject.videos.data[currentSketchIndex].urls && (
|
{ taskObject.videos.data[currentSketchIndex].urls && taskObject.videos.data[currentSketchIndex].urls.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
||||||
@ -458,7 +462,6 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
autoPlay={isVideoPlaying}
|
autoPlay={isVideoPlaying}
|
||||||
loop={true}
|
loop={true}
|
||||||
playsInline
|
playsInline
|
||||||
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
|
|
||||||
onEnded={() => {
|
onEnded={() => {
|
||||||
if (isVideoPlaying) {
|
if (isVideoPlaying) {
|
||||||
// 自动切换到下一个视频的逻辑在父组件处理
|
// 自动切换到下一个视频的逻辑在父组件处理
|
||||||
@ -471,17 +474,6 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
<div className="absolute top-4 right-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
|
<div className="absolute top-4 right-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
|
||||||
right: toosBtnRight
|
right: toosBtnRight
|
||||||
}}>
|
}}>
|
||||||
{/* 重试按钮 */}
|
|
||||||
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
|
||||||
<Tooltip placement="top" title="Retry video">
|
|
||||||
<GlassIconButton icon={RotateCcw} size='sm' onClick={() => {
|
|
||||||
const video = taskObject.videos.data[currentSketchIndex];
|
|
||||||
if (onRetryVideo && video?.video_id) {
|
|
||||||
onRetryVideo(video.video_id);
|
|
||||||
}
|
|
||||||
}} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{/* 添加到chat去编辑 按钮 */}
|
{/* 添加到chat去编辑 按钮 */}
|
||||||
<Tooltip placement="top" title="Edit video with chat">
|
<Tooltip placement="top" title="Edit video with chat">
|
||||||
<GlassIconButton icon={MessageCircleMore} size='sm' onClick={() => {
|
<GlassIconButton icon={MessageCircleMore} size='sm' onClick={() => {
|
||||||
|
|||||||
@ -12,13 +12,15 @@ interface ThumbnailGridProps {
|
|||||||
taskObject: TaskObject;
|
taskObject: TaskObject;
|
||||||
currentSketchIndex: number;
|
currentSketchIndex: number;
|
||||||
onSketchSelect: (index: number) => void;
|
onSketchSelect: (index: number) => void;
|
||||||
|
onRetryVideo: (video_id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ThumbnailGrid({
|
export function ThumbnailGrid({
|
||||||
isDisabledFocus,
|
isDisabledFocus,
|
||||||
taskObject,
|
taskObject,
|
||||||
currentSketchIndex,
|
currentSketchIndex,
|
||||||
onSketchSelect
|
onSketchSelect,
|
||||||
|
onRetryVideo
|
||||||
}: ThumbnailGridProps) {
|
}: ThumbnailGridProps) {
|
||||||
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
@ -186,19 +188,24 @@ export function ThumbnailGrid({
|
|||||||
)}
|
)}
|
||||||
{taskObject.videos.data[index].video_status === 2 && (
|
{taskObject.videos.data[index].video_status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
|
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
|
||||||
<div className="text-red-500 text-xl font-bold flex items-center gap-2">
|
<div className="text-[#813b9dcc] text-xl font-bold flex items-center gap-2">
|
||||||
<RotateCcw className="w-10 h-10" />
|
<RotateCcw className="w-10 h-10 cursor-pointer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{taskObject.videos.data[index].urls ? (
|
{taskObject.videos.data[index].urls && taskObject.videos.data[index].urls.length > 0 ? (
|
||||||
<video
|
// <video
|
||||||
|
// className="w-full h-full object-cover"
|
||||||
|
// src={taskObject.videos.data[index].urls[0]}
|
||||||
|
// playsInline
|
||||||
|
// loop
|
||||||
|
// muted
|
||||||
|
// />
|
||||||
|
<img
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
src={taskObject.videos.data[index].urls[0]}
|
src={`${taskObject.videos.data[index].urls[0]}?x-oss-process=video/snapshot,t_1000,f_jpg`}
|
||||||
playsInline
|
draggable="false"
|
||||||
loop
|
|
||||||
muted
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateShot } from '@/api/video_flow';
|
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateVideo } from '@/api/video_flow';
|
||||||
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
||||||
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||||
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
||||||
@ -18,6 +18,8 @@ export function useWorkflowData() {
|
|||||||
const from = searchParams.get('from') || '';
|
const from = searchParams.get('from') || '';
|
||||||
const token = localStorage.getItem('token') || '';
|
const token = localStorage.getItem('token') || '';
|
||||||
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
|
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
|
||||||
|
// 查看缓存中 是否已经 加载过 这个项目的 剪辑计划
|
||||||
|
const isLoaded = localStorage.getItem(`isLoaded_plan_${episodeId}`);
|
||||||
|
|
||||||
let tempTaskObject = useRef<TaskObject>({
|
let tempTaskObject = useRef<TaskObject>({
|
||||||
title: '',
|
title: '',
|
||||||
@ -114,7 +116,15 @@ export function useWorkflowData() {
|
|||||||
}, [taskObject.currentStage]);
|
}, [taskObject.currentStage]);
|
||||||
|
|
||||||
const generateEditPlan = useCallback(async (isInit?: boolean) => {
|
const generateEditPlan = useCallback(async (isInit?: boolean) => {
|
||||||
|
if (isLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localStorage.setItem(`isLoaded_plan_${episodeId}`, 'true');
|
||||||
isInit && await getGenerateEditPlan({ project_id: episodeId });
|
isInit && await getGenerateEditPlan({ project_id: episodeId });
|
||||||
|
openEditPlan();
|
||||||
|
}, [episodeId]);
|
||||||
|
|
||||||
|
const openEditPlan = useCallback(async () => {
|
||||||
window.open(`https://smartcut.movieflow.ai/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
|
window.open(`https://smartcut.movieflow.ai/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
|
||||||
}, [episodeId]);
|
}, [episodeId]);
|
||||||
|
|
||||||
@ -275,15 +285,17 @@ export function useWorkflowData() {
|
|||||||
taskCurrent.currentStage = 'video';
|
taskCurrent.currentStage = 'video';
|
||||||
// 正在生成视频中 替换视频数据
|
// 正在生成视频中 替换视频数据
|
||||||
const videoList = [];
|
const videoList = [];
|
||||||
|
let videoUrls: string[] = [];
|
||||||
let video_status = 0;
|
let video_status = 0;
|
||||||
for (const video of task.task_result.data) {
|
for (const video of task.task_result.data) {
|
||||||
|
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
|
||||||
// 适配旧数据
|
// 适配旧数据
|
||||||
video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
|
video_status = video.video_status === undefined ? (videoUrls.length > 0 ? 1 : 0) : video.video_status;
|
||||||
// 完成 还是 0 就是 生成失败
|
// 完成 还是 0 就是 生成失败
|
||||||
video_status = task.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
|
video_status = task.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
|
||||||
// 每一项 video 有多个视频 先默认取第一个
|
// 每一项 video 有多个视频 先默认取第一个
|
||||||
videoList.push({
|
videoList.push({
|
||||||
urls: video.urls,
|
urls: videoUrls,
|
||||||
video_id: video.video_id,
|
video_id: video.video_id,
|
||||||
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
|
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
|
||||||
type: 'video'
|
type: 'video'
|
||||||
@ -452,13 +464,16 @@ export function useWorkflowData() {
|
|||||||
taskCurrent.currentStage = 'video';
|
taskCurrent.currentStage = 'video';
|
||||||
taskCurrent.videos.total_count = data.video.total_count;
|
taskCurrent.videos.total_count = data.video.total_count;
|
||||||
const videoList = [];
|
const videoList = [];
|
||||||
|
let videoUrls: string[] = [];
|
||||||
console.log('----------data.video.data', data.video.data);
|
console.log('----------data.video.data', data.video.data);
|
||||||
for (const video of data.video.data) {
|
for (const video of data.video.data) {
|
||||||
let video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
|
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
|
||||||
|
console.log('----------videoUrls', videoUrls);
|
||||||
|
let video_status = video.video_status === undefined ? (videoUrls.length > 0 ? 1 : 0) : video.video_status;
|
||||||
video_status = data.video.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
|
video_status = data.video.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
|
||||||
// 每一项 video 有多个视频 默认取存在的项
|
// 每一项 video 有多个视频 默认取存在的项
|
||||||
videoList.push({
|
videoList.push({
|
||||||
urls: video.urls,
|
urls: videoUrls,
|
||||||
video_id: video.video_id,
|
video_id: video.video_id,
|
||||||
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
|
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
|
||||||
type: 'video'
|
type: 'video'
|
||||||
@ -527,23 +542,33 @@ export function useWorkflowData() {
|
|||||||
// 重试生成视频
|
// 重试生成视频
|
||||||
const handleRetryVideo = async (video_id: string) => {
|
const handleRetryVideo = async (video_id: string) => {
|
||||||
try {
|
try {
|
||||||
|
// 先停止轮询
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setNeedStreamData(false);
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
// 重置视频状态为生成中
|
// 重置视频状态为生成中
|
||||||
setTaskObject(prev => {
|
await new Promise(resolve => {
|
||||||
const newState = JSON.parse(JSON.stringify(prev));
|
setTaskObject(prev => {
|
||||||
const videoIndex = newState.videos.data.findIndex((v: any) => v.video_id === video_id);
|
const newState = JSON.parse(JSON.stringify(prev));
|
||||||
if (videoIndex !== -1) {
|
const videoIndex = newState.videos.data.findIndex((v: any) => v.video_id === video_id);
|
||||||
newState.videos.data[videoIndex].video_status = 0;
|
if (videoIndex !== -1) {
|
||||||
}
|
newState.videos.data[videoIndex].video_status = 0;
|
||||||
return newState;
|
}
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
resolve(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 调用重新生成接口
|
// 调用重新生成接口
|
||||||
await regenerateShot({ project_id: episodeId, shot_id: video_id });
|
await regenerateVideo({ project_id: episodeId, video_id: video_id });
|
||||||
|
|
||||||
// 重新开启轮询 如果轮询结束的话
|
// 重新开启轮询
|
||||||
if (!needStreamData) setNeedStreamData(true);
|
setNeedStreamData(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('重试生成视频失败:', error);
|
console.error('重试生成视频失败:', error);
|
||||||
|
// 发生错误时也要恢复轮询状态
|
||||||
|
setNeedStreamData(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -587,7 +612,7 @@ export function useWorkflowData() {
|
|||||||
originalText: state.originalText,
|
originalText: state.originalText,
|
||||||
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
||||||
showGotoCutButton: canGoToCut ? true : false,
|
showGotoCutButton: canGoToCut ? true : false,
|
||||||
generateEditPlan,
|
generateEditPlan: openEditPlan,
|
||||||
handleRetryVideo
|
handleRetryVideo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user