|
@@ -66,6 +66,8 @@ const ProjectDetail = () => {
|
66
|
66
|
const [lipSyncProgress, setLipSyncProgress] = useState(0);
|
67
|
67
|
const [lipSyncTaskId, setLipSyncTaskId] = useState(null);
|
68
|
68
|
const [lipSyncResultUrl, setLipSyncResultUrl] = useState(null);
|
|
69
|
+ // 添加导出剪映草稿相关状态
|
|
70
|
+ const [exportingDraft, setExportingDraft] = useState(false);
|
69
|
71
|
|
70
|
72
|
const fileInputRef = useRef(null);
|
71
|
73
|
|
|
@@ -2004,6 +2006,97 @@ const ProjectDetail = () => {
|
2004
|
2006
|
}
|
2005
|
2007
|
};
|
2006
|
2008
|
|
|
2009
|
+ /**
|
|
2010
|
+ * 处理导出剪映草稿
|
|
2011
|
+ */
|
|
2012
|
+ const handleExportDraft = async () => {
|
|
2013
|
+ try {
|
|
2014
|
+ // 检查是否有音频和视频
|
|
2015
|
+ if (!project.audio_url || !project.lip_sync_video_path) {
|
|
2016
|
+ toast.error('请先完成对口型视频生成');
|
|
2017
|
+ return;
|
|
2018
|
+ }
|
|
2019
|
+
|
|
2020
|
+ // 检查是否有分镜数据
|
|
2021
|
+ if (!segments || segments.length === 0) {
|
|
2022
|
+ toast.error('没有可导出的分镜数据');
|
|
2023
|
+ return;
|
|
2024
|
+ }
|
|
2025
|
+
|
|
2026
|
+ // 检查所有分镜是否都有必要的字段
|
|
2027
|
+ const invalidSegments = segments.filter(segment =>
|
|
2028
|
+ !segment.start_time ||
|
|
2029
|
+ !segment.end_time ||
|
|
2030
|
+ !segment.text ||
|
|
2031
|
+ !segment.image_path
|
|
2032
|
+ );
|
|
2033
|
+
|
|
2034
|
+ if (invalidSegments.length > 0) {
|
|
2035
|
+ toast.error(`有${invalidSegments.length}个分镜缺少必要信息,请完善后再导出`);
|
|
2036
|
+ return;
|
|
2037
|
+ }
|
|
2038
|
+
|
|
2039
|
+ setExportingDraft(true);
|
|
2040
|
+
|
|
2041
|
+ // 转换时间格式为微秒
|
|
2042
|
+ const textList = segments.map(segment => {
|
|
2043
|
+ const startTime = convertTimeToMicroseconds(segment.start_time);
|
|
2044
|
+ const endTime = convertTimeToMicroseconds(segment.end_time);
|
|
2045
|
+ const duration = endTime - startTime;
|
|
2046
|
+
|
|
2047
|
+ return {
|
|
2048
|
+ start_time: startTime,
|
|
2049
|
+ end_time: endTime,
|
|
2050
|
+ duration: duration,
|
|
2051
|
+ text: segment.text,
|
|
2052
|
+ image_path: segment.image_path
|
|
2053
|
+ };
|
|
2054
|
+ });
|
|
2055
|
+
|
|
2056
|
+ // 调用工作流API
|
|
2057
|
+ const cozeInstance = initCozeService();
|
|
2058
|
+ const response = await cozeInstance.runWorkflow(WORKFLOW_IDS.exportJianyingDraft, {
|
|
2059
|
+ audio_url: project.audio_url,
|
|
2060
|
+ video_url: project.lip_sync_video_path,
|
|
2061
|
+ text_list: textList
|
|
2062
|
+ });
|
|
2063
|
+
|
|
2064
|
+ if (response && response.data) {
|
|
2065
|
+ try {
|
|
2066
|
+ const result = JSON.parse(response.data);
|
|
2067
|
+ if (result.output) {
|
|
2068
|
+ toast.success('剪映草稿导出成功');
|
|
2069
|
+ // 这里可以添加下载草稿文件的逻辑
|
|
2070
|
+ } else {
|
|
2071
|
+ throw new Error('导出结果格式不正确');
|
|
2072
|
+ }
|
|
2073
|
+ } catch (e) {
|
|
2074
|
+ throw new Error('解析导出结果失败');
|
|
2075
|
+ }
|
|
2076
|
+ } else {
|
|
2077
|
+ throw new Error('导出失败,未收到有效响应');
|
|
2078
|
+ }
|
|
2079
|
+ } catch (error) {
|
|
2080
|
+ console.error('导出剪映草稿失败:', error);
|
|
2081
|
+ toast.error(`导出失败: ${error.message}`);
|
|
2082
|
+ } finally {
|
|
2083
|
+ setExportingDraft(false);
|
|
2084
|
+ }
|
|
2085
|
+ };
|
|
2086
|
+
|
|
2087
|
+ // 辅助函数:将时间格式转换为微秒
|
|
2088
|
+ const convertTimeToMicroseconds = (timeStr) => {
|
|
2089
|
+ if (!timeStr) return 0;
|
|
2090
|
+
|
|
2091
|
+ // 处理时间格式 "HH:MM:SS,SSS"
|
|
2092
|
+ const [time, milliseconds] = timeStr.split(',');
|
|
2093
|
+ const [hours, minutes, seconds] = time.split(':').map(Number);
|
|
2094
|
+ const ms = milliseconds ? parseInt(milliseconds) : 0;
|
|
2095
|
+
|
|
2096
|
+ // 转换为微秒
|
|
2097
|
+ return (hours * 3600 + minutes * 60 + seconds) * 1000000 + ms * 1000;
|
|
2098
|
+ };
|
|
2099
|
+
|
2007
|
2100
|
if (loading && !silentLoading) {
|
2008
|
2101
|
return <div className="text-center p-5">加载中...</div>;
|
2009
|
2102
|
}
|
|
@@ -2281,6 +2374,28 @@ const ProjectDetail = () => {
|
2281
|
2374
|
>
|
2282
|
2375
|
清除选择
|
2283
|
2376
|
</Button>
|
|
2377
|
+ <Button
|
|
2378
|
+ variant="outline-success"
|
|
2379
|
+ size="sm"
|
|
2380
|
+ onClick={handleExportDraft}
|
|
2381
|
+ disabled={exportingDraft || !project.audio_url || !project.lip_sync_video_path || !segments || segments.length === 0}
|
|
2382
|
+ >
|
|
2383
|
+ {exportingDraft ? (
|
|
2384
|
+ <>
|
|
2385
|
+ <Spinner
|
|
2386
|
+ as="span"
|
|
2387
|
+ animation="border"
|
|
2388
|
+ size="sm"
|
|
2389
|
+ role="status"
|
|
2390
|
+ aria-hidden="true"
|
|
2391
|
+ className="me-1"
|
|
2392
|
+ />
|
|
2393
|
+ 导出中...
|
|
2394
|
+ </>
|
|
2395
|
+ ) : (
|
|
2396
|
+ '导出剪映草稿'
|
|
2397
|
+ )}
|
|
2398
|
+ </Button>
|
2284
|
2399
|
</div>
|
2285
|
2400
|
</Card.Header>
|
2286
|
2401
|
<Card.Body>
|