forked from 77media/video-flow
集成 自动剪辑
This commit is contained in:
parent
25de95c910
commit
19a24cc220
@ -33,6 +33,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
const [aiEditingInProgress, setAiEditingInProgress] = React.useState(false);
|
const [aiEditingInProgress, setAiEditingInProgress] = React.useState(false);
|
||||||
const [isHovered, setIsHovered] = React.useState(false);
|
const [isHovered, setIsHovered] = React.useState(false);
|
||||||
const [aiEditingResult, setAiEditingResult] = React.useState<any>(null);
|
const [aiEditingResult, setAiEditingResult] = React.useState<any>(null);
|
||||||
|
const aiEditingButtonRef = useRef<{ handleAIEditing: () => Promise<void> }>(null);
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const episodeId = searchParams.get('episodeId') || '';
|
const episodeId = searchParams.get('episodeId') || '';
|
||||||
@ -41,6 +42,12 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
|
|
||||||
SaveEditUseCase.setProjectId(episodeId);
|
SaveEditUseCase.setProjectId(episodeId);
|
||||||
// 使用自定义 hooks 管理状态
|
// 使用自定义 hooks 管理状态
|
||||||
|
// 处理编辑计划生成完成的回调
|
||||||
|
const handleEditPlanGenerated = useCallback(() => {
|
||||||
|
console.log('✨ 编辑计划生成完成,开始AI剪辑');
|
||||||
|
aiEditingButtonRef.current?.handleAIEditing();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
taskObject,
|
taskObject,
|
||||||
scriptData,
|
scriptData,
|
||||||
@ -60,7 +67,9 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
showGotoCutButton,
|
showGotoCutButton,
|
||||||
generateEditPlan,
|
generateEditPlan,
|
||||||
handleRetryVideo
|
handleRetryVideo
|
||||||
} = useWorkflowData();
|
} = useWorkflowData({
|
||||||
|
onEditPlanGenerated: handleEditPlanGenerated
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isVideoPlaying,
|
isVideoPlaying,
|
||||||
@ -209,6 +218,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
<div className="fixed right-[2rem] top-[8rem] z-[49]">
|
<div className="fixed right-[2rem] top-[8rem] z-[49]">
|
||||||
<Tooltip title="AI智能剪辑" placement="left">
|
<Tooltip title="AI智能剪辑" placement="left">
|
||||||
<AIEditingIframeButton
|
<AIEditingIframeButton
|
||||||
|
ref={aiEditingButtonRef}
|
||||||
projectId={episodeId}
|
projectId={episodeId}
|
||||||
token={localStorage.getItem("token") || ""}
|
token={localStorage.getItem("token") || ""}
|
||||||
userId={userId.toString()}
|
userId={userId.toString()}
|
||||||
|
|||||||
@ -31,6 +31,12 @@ interface AIEditingResult {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 定义组件实例接口
|
||||||
|
interface AIEditingIframeHandle {
|
||||||
|
startAIEditing: () => void;
|
||||||
|
stopAIEditing: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface AIEditingIframeProps {
|
interface AIEditingIframeProps {
|
||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -62,7 +68,8 @@ interface ProgressState {
|
|||||||
* AI剪辑iframe组件
|
* AI剪辑iframe组件
|
||||||
* 提供无缝集成的智能剪辑功能,避免页面跳转
|
* 提供无缝集成的智能剪辑功能,避免页面跳转
|
||||||
*/
|
*/
|
||||||
export const AIEditingIframe: React.FC<AIEditingIframeProps> = ({
|
export const AIEditingIframe = React.forwardRef<AIEditingIframeHandle, AIEditingIframeProps>((props, ref) => {
|
||||||
|
const {
|
||||||
projectId,
|
projectId,
|
||||||
token,
|
token,
|
||||||
userId,
|
userId,
|
||||||
@ -72,7 +79,7 @@ export const AIEditingIframe: React.FC<AIEditingIframeProps> = ({
|
|||||||
buttonMode = false,
|
buttonMode = false,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
autoStart = false
|
autoStart = false
|
||||||
}) => {
|
} = props;
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const [isMinimized, setIsMinimized] = useState(false);
|
const [isMinimized, setIsMinimized] = useState(false);
|
||||||
const [hideIframe, setHideIframe] = useState(true); // 默认隐藏iframe
|
const [hideIframe, setHideIframe] = useState(true); // 默认隐藏iframe
|
||||||
@ -84,6 +91,7 @@ export const AIEditingIframe: React.FC<AIEditingIframeProps> = ({
|
|||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
const progressIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const progressIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// 构建智能剪辑URL
|
// 构建智能剪辑URL
|
||||||
const aiEditingUrl = `https://smartcut.movieflow.ai/ai-editor/${projectId}?token=${token}&user_id=${userId}&auto=true&embedded=true`;
|
const aiEditingUrl = `https://smartcut.movieflow.ai/ai-editor/${projectId}?token=${token}&user_id=${userId}&auto=true&embedded=true`;
|
||||||
@ -327,7 +335,7 @@ export const AIEditingIframe: React.FC<AIEditingIframeProps> = ({
|
|||||||
}, [autoStart, isProcessing, progressState.stage, startAIEditing]);
|
}, [autoStart, isProcessing, progressState.stage, startAIEditing]);
|
||||||
|
|
||||||
// 按钮模式渲染
|
// 按钮模式渲染
|
||||||
if (buttonMode) {
|
const renderButtonMode = () => {
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{/* 主按钮 */}
|
{/* 主按钮 */}
|
||||||
@ -335,7 +343,7 @@ export const AIEditingIframe: React.FC<AIEditingIframeProps> = ({
|
|||||||
onClick={startAIEditing}
|
onClick={startAIEditing}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
className={`
|
className={`
|
||||||
relative flex items-center gap-2 px-4 py-2 rounded-lg
|
fixed -top-[999999px] -left-[999999px] flex items-center gap-2 px-4 py-2 rounded-lg
|
||||||
backdrop-blur-lg bg-gradient-to-r from-blue-500/20 to-purple-500/20
|
backdrop-blur-lg bg-gradient-to-r from-blue-500/20 to-purple-500/20
|
||||||
border border-white/20 shadow-xl
|
border border-white/20 shadow-xl
|
||||||
text-white font-medium text-sm
|
text-white font-medium text-sm
|
||||||
@ -420,6 +428,7 @@ export const AIEditingIframe: React.FC<AIEditingIframeProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 直接iframe模式渲染
|
// 直接iframe模式渲染
|
||||||
|
const renderIframeMode = () => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full relative">
|
<div className="w-full h-full relative">
|
||||||
<iframe
|
<iframe
|
||||||
@ -458,14 +467,33 @@ export const AIEditingIframe: React.FC<AIEditingIframeProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
React.useImperativeHandle(ref, () => ({
|
||||||
|
startAIEditing,
|
||||||
|
stopAIEditing
|
||||||
|
}));
|
||||||
|
|
||||||
|
return buttonMode ? renderButtonMode() : renderIframeMode();
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简化版AI剪辑图标按钮
|
* 简化版AI剪辑图标按钮
|
||||||
* 用于空间受限的场景
|
* 用于空间受限的场景
|
||||||
*/
|
*/
|
||||||
export const AIEditingIframeButton: React.FC<Omit<AIEditingIframeProps, 'buttonMode'> & {
|
export const AIEditingIframeButton = React.forwardRef<
|
||||||
size?: 'sm' | 'md' | 'lg'
|
{ handleAIEditing: () => Promise<void> },
|
||||||
}> = (props) => {
|
Omit<AIEditingIframeProps, 'buttonMode'> & { size?: 'sm' | 'md' | 'lg' }
|
||||||
return <AIEditingIframe {...props} buttonMode={true} />;
|
>((props, ref) => {
|
||||||
};
|
const iframeRef = useRef<AIEditingIframeHandle>(null);
|
||||||
|
|
||||||
|
// 暴露 handleAIEditing 方法
|
||||||
|
React.useImperativeHandle(ref, () => ({
|
||||||
|
handleAIEditing: async () => {
|
||||||
|
iframeRef.current?.startAIEditing();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return <AIEditingIframe ref={iframeRef} {...props} buttonMode={true} />;
|
||||||
|
});
|
||||||
|
|
||||||
export default AIEditingIframe;
|
export default AIEditingIframe;
|
||||||
|
|||||||
@ -7,7 +7,11 @@ 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';
|
||||||
|
|
||||||
export function useWorkflowData() {
|
interface UseWorkflowDataProps {
|
||||||
|
onEditPlanGenerated?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps = {}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("init-useWorkflowData");
|
console.log("init-useWorkflowData");
|
||||||
return () => console.log("unmount-useWorkflowData");
|
return () => console.log("unmount-useWorkflowData");
|
||||||
@ -115,24 +119,27 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
}, [taskObject.currentStage]);
|
}, [taskObject.currentStage]);
|
||||||
|
|
||||||
// const generateEditPlan = useCallback(async (isInit?: boolean) => {
|
const generateEditPlan = useCallback(async (isInit?: boolean) => {
|
||||||
// if (isLoaded) {
|
if (isLoaded) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// localStorage.setItem(`isLoaded_plan_${episodeId}`, 'true');
|
localStorage.setItem(`isLoaded_plan_${episodeId}`, 'true');
|
||||||
// isInit && await getGenerateEditPlan({ project_id: episodeId });
|
if (isInit) {
|
||||||
// openEditPlan();
|
await getGenerateEditPlan({ project_id: episodeId });
|
||||||
// }, [episodeId]);
|
// 触发回调,通知父组件计划生成完成
|
||||||
|
onEditPlanGenerated?.();
|
||||||
|
}
|
||||||
|
}, [episodeId, onEditPlanGenerated]);
|
||||||
|
|
||||||
const openEditPlan = useCallback(async () => {
|
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]);
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// if (!from && canGoToCut && taskObject.status !== 'COMPLETED') {
|
if (!from && canGoToCut && taskObject.status !== 'COMPLETED') {
|
||||||
// generateEditPlan(true);
|
generateEditPlan(true);
|
||||||
// }
|
}
|
||||||
// }, [canGoToCut, taskObject.status]);
|
}, [canGoToCut, taskObject.status]);
|
||||||
|
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
@ -297,21 +304,20 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
taskCurrent.videos.data = videoList;
|
taskCurrent.videos.data = videoList;
|
||||||
console.log('----------正在生成视频中', realTaskResultData.length);
|
console.log('----------正在生成视频中', realTaskResultData.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (task.task_status === 'COMPLETED') {
|
if (task.task_status === 'COMPLETED') {
|
||||||
console.log('----------视频生成完成');
|
console.log('----------视频生成完成');
|
||||||
// 视频生成完成
|
// 视频生成完成
|
||||||
// 暂时没有音频生成 直接跳过
|
// 暂时没有音频生成 直接跳过
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 视频分析
|
// 视频分析
|
||||||
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT' && item.task_status !== 'RUNNING').length;
|
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT' && item.task_status !== 'RUNNING').length;
|
||||||
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
||||||
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
||||||
setCanGoToCut(true);
|
setCanGoToCut(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 粗剪
|
// 粗剪
|
||||||
if (task.task_name === 'generate_final_simple_video') {
|
if (task.task_name === 'generate_final_simple_video') {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user