diff --git a/apps/api/app/services/cover_service.py b/apps/api/app/services/cover_service.py deleted file mode 100644 index a99ca8961..000000000 --- a/apps/api/app/services/cover_service.py +++ /dev/null @@ -1,278 +0,0 @@ -"""封面管理服务. - -提供封面配置管理和从视频抽帧生成封面的能力。 -抽帧使用 FFmpeg,上传使用共享存储服务。 -""" - -from __future__ import annotations - -import logging -import tempfile -from pathlib import Path -from typing import Any, Dict - -logger = logging.getLogger(__name__) - - -# ── 常量 ────────────────────────────────────────────────────────────────────── - -DEFAULT_COVER_WIDTH = 1080 -DEFAULT_COVER_HEIGHT = 1920 -DEFAULT_COVER_QUALITY = 5 # JPEG quality (1-31, 越小越好) -COVER_STORAGE_PREFIX = "covers" - - -class CoverService: - """封面管理服务.""" - - def __init__(self, storage_service: Any, asset_repository: Any) -> None: - self._storage = storage_service - self._asset_repo = asset_repository - - # ── 配置读写 ────────────────────────────────────────────────────────── - - @staticmethod - def get_cover_config(plan_config: Dict[str, Any]) -> Dict[str, Any]: - """从 plan.config 中提取封面配置. - - Args: - plan_config: 剪辑计划的 config 字段 - - Returns: - 封面配置 dict - """ - cover = plan_config.get("cover", {}) - if not isinstance(cover, dict): - cover = {} - # 确保默认字段存在 - return { - "type": cover.get("type", "ai_frame"), - "image_url": cover.get("image_url", ""), - "frame_time": cover.get("frame_time"), - } - - # ── 抽帧生成封面 ────────────────────────────────────────────────────── - - def extract_cover_from_clip( - self, - plan_id: str, - asset_id: str, - frame_time: float = 1.0, - *, - width: int = DEFAULT_COVER_WIDTH, - height: int = DEFAULT_COVER_HEIGHT, - quality: int = DEFAULT_COVER_QUALITY, - ) -> Dict[str, Any]: - """从指定素材的指定时间点抽取一帧作为封面. - - Args: - plan_id: 剪辑计划 ID(用于生成存储路径) - asset_id: 素材 ID - frame_time: 抽帧时间点(秒) - width: 输出宽度 - height: 输出高度 - quality: JPEG 质量 - - Returns: - 封面数据 dict,包含 type / image_url / frame_time - - Raises: - ValueError: 素材不存在或不是视频 - RuntimeError: 抽帧或上传失败 - """ - # 1. 获取素材 - asset = self._asset_repo.get(asset_id) if self._asset_repo else None - if not asset: - raise ValueError(f"素材不存在: {asset_id}") - - storage_key = getattr(asset, "storage_key", "") - if not storage_key: - raise ValueError(f"素材没有文件: {asset_id}") - - mime_type = getattr(asset, "mime_type", "") - if mime_type and not mime_type.startswith("video"): - raise ValueError(f"素材不是视频类型: {mime_type}") - - # 2. 下载视频到临时目录 - with tempfile.TemporaryDirectory(prefix="cover_extract_") as tmp_dir: - tmp_path = Path(tmp_dir) - video_path = tmp_path / f"source_{asset_id[:8]}" - - logger.info("下载素材用于封面抽帧: asset_id=%s", asset_id) - try: - self._storage.download_file(storage_key, str(video_path)) - except Exception as e: - raise RuntimeError(f"下载素材失败: {e}") from e - - if not video_path.exists() or video_path.stat().st_size == 0: - raise RuntimeError("下载的素材文件为空") - - # 3. FFmpeg 抽帧 - output_path = tmp_path / "cover.jpg" - self._extract_frame( - video_path=video_path, - output_path=output_path, - time_sec=frame_time, - width=width, - height=height, - quality=quality, - ) - - if not output_path.exists() or output_path.stat().st_size == 0: - raise RuntimeError("封面抽帧失败") - - # 4. 上传到 OSS - cover_key = f"{COVER_STORAGE_PREFIX}/{plan_id}/cover_{int(frame_time * 1000)}.jpg" - logger.info("上传封面到存储: key=%s", cover_key) - - try: - self._storage.upload_file( - file_or_path=str(output_path), - storage_key=cover_key, - content_type="image/jpeg", - ) - except Exception as e: - raise RuntimeError(f"上传封面失败: {e}") from e - - # 5. 获取访问 URL - try: - image_url = self._storage.get_url(cover_key) - except Exception: - image_url = cover_key # 降级为 storage_key - - logger.info( - "封面抽帧完成: plan_id=%s asset_id=%s time=%.2fs size=%d", - plan_id, - asset_id, - frame_time, - output_path.stat().st_size if output_path.exists() else 0, - ) - - return { - "type": "manual", - "image_url": image_url, - "frame_time": frame_time, - } - - def generate_smart_cover( - self, - plan_id: str, - asset_id: str, - *, - width: int = DEFAULT_COVER_WIDTH, - height: int = DEFAULT_COVER_HEIGHT, - quality: int = DEFAULT_COVER_QUALITY, - ) -> Dict[str, Any]: - """智能选帧:从视频中选取多帧,选最清晰的一帧. - - Args: - plan_id: 剪辑计划 ID - asset_id: 素材 ID - width: 输出宽度 - height: 输出高度 - quality: JPEG 质量 - - Returns: - 封面数据 dict - """ - # 简单实现:取视频 1/3 处的帧作为智能封面 - # 更复杂的多帧选清晰帧可以后续优化 - frame_time = 3.0 # 默认第3秒,后续可以根据视频时长动态计算 - - result = self.extract_cover_from_clip( - plan_id=plan_id, - asset_id=asset_id, - frame_time=frame_time, - width=width, - height=height, - quality=quality, - ) - result["type"] = "ai_frame" - return result - - # ── 内部方法 ────────────────────────────────────────────────────────── - - @staticmethod - def _extract_frame( - video_path: Path, - output_path: Path, - *, - time_sec: float, - width: int, - height: int, - quality: int, - ) -> None: - """使用 FFmpeg 从视频中抽取一帧. - - Args: - video_path: 视频文件路径 - output_path: 输出图片路径 - time_sec: 抽帧时间点(秒) - width: 输出宽度 - height: 输出高度 - quality: JPEG 质量 - """ - import subprocess - - # scale + crop 实现 cover 裁剪 - vf = f"scale={width}:{height}:force_original_aspect_ratio=increase," f"crop={width}:{height},format=yuvj420p" - - command = [ - "ffmpeg", - "-y", - "-ss", - f"{time_sec:.3f}", - "-i", - str(video_path), - "-vframes", - "1", - "-vf", - vf, - "-q:v", - str(quality), - "-f", - "mjpeg", - str(output_path), - ] - - logger.debug("FFmpeg 抽帧命令: %s", " ".join(command)) - - try: - result = subprocess.run( - command, - capture_output=True, - text=True, - timeout=60, - ) - if result.returncode != 0: - logger.warning("FFmpeg 抽帧返回非零: %s\nstderr: %s", result.returncode, result.stderr[-500:]) - # 尝试不使用 scale+crop 的简化命令 - simple_command = [ - "ffmpeg", - "-y", - "-ss", - f"{time_sec:.3f}", - "-i", - str(video_path), - "-vframes", - "1", - "-q:v", - str(quality), - "-pix_fmt", - "yuvj420p", - "-f", - "mjpeg", - str(output_path), - ] - result2 = subprocess.run( - simple_command, - capture_output=True, - text=True, - timeout=60, - ) - if result2.returncode != 0: - raise RuntimeError(f"FFmpeg 抽帧失败: {result2.stderr[-300:]}") - except subprocess.TimeoutExpired as e: - raise RuntimeError("FFmpeg 抽帧超时") from e - except FileNotFoundError as e: - raise RuntimeError("FFmpeg 不可用") from e diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.tsx b/apps/web/src/pages/editing-planner/EditingPlanner.tsx index 267751d79..68f9fad47 100644 --- a/apps/web/src/pages/editing-planner/EditingPlanner.tsx +++ b/apps/web/src/pages/editing-planner/EditingPlanner.tsx @@ -74,8 +74,6 @@ const EditingPlanner: React.FC = () => { setChromaKeySettings, stickerSettings, setStickerSettings, - coverConfig, - setCoverConfig, } = useGlobalSettings() /* ── 右侧栏 Tab ── */ @@ -119,7 +117,6 @@ const EditingPlanner: React.FC = () => { setTitleConfig, setSubtitleSettings, setBgmSettings, - setCoverConfig, clips, totalDuration, titleConfig, @@ -131,7 +128,6 @@ const EditingPlanner: React.FC = () => { filterSettings, chromaKeySettings, stickerSettings, - coverConfig, }) /* ──────────── 渲染 ──────────── */ @@ -181,7 +177,6 @@ const EditingPlanner: React.FC = () => { selectedClipId={clipOps.selectedClipId} isPlaying={playback.isPlaying} titleConfig={titleConfig} - coverConfig={coverConfig} subtitleSettings={{ enabled: subtitleSettings.enabled, position: subtitleSettings.position, @@ -243,7 +238,6 @@ const EditingPlanner: React.FC = () => { onOpenFilterDrawer={() => drawers.setFilterDrawerOpen(true)} onOpenGreenScreenDrawer={() => drawers.setChromaKeyDrawerOpen(true)} onOpenStickerDrawer={() => drawers.setStickerDrawerOpen(true)} - onOpenCoverDrawer={() => drawers.setCoverDrawerOpen(true)} clips={clips} selectedClipId={clipOps.selectedClipId} onClipSelect={clipOps.handleClipSelect} @@ -336,11 +330,6 @@ const EditingPlanner: React.FC = () => { stickerSettings={stickerSettings} onStickerChange={setStickerSettings} onCloseStickerDrawer={() => drawers.setStickerDrawerOpen(false)} - coverDrawerOpen={drawers.coverDrawerOpen} - onCloseCoverDrawer={() => drawers.setCoverDrawerOpen(false)} - coverConfig={coverConfig} - setCoverConfig={setCoverConfig} - templateId={urlTemplateId} /> ) diff --git a/apps/web/src/pages/editing-planner/components/ClipPropertiesPanel.tsx b/apps/web/src/pages/editing-planner/components/ClipPropertiesPanel.tsx index e635332d6..ce9674d0f 100644 --- a/apps/web/src/pages/editing-planner/components/ClipPropertiesPanel.tsx +++ b/apps/web/src/pages/editing-planner/components/ClipPropertiesPanel.tsx @@ -35,7 +35,6 @@ const ClipPropertiesPanel: React.FC = ({ onOpenFilterDrawer, onOpenGreenScreenDrawer, onOpenStickerDrawer, - onOpenCoverDrawer, }) => { const { previewingId, handlePreviewVoice, stopPreview } = useVoicePreview() @@ -141,21 +140,6 @@ const ClipPropertiesPanel: React.FC = ({ )} - {/* ═══ 封面设置 ═══ */} -
-
- 🖼️ - 封面设置 -
- {onOpenCoverDrawer && ( - - )} -
- {/* ═══ 片段详情(选中时显示) ═══ */} {selectedClip && ( void onCloseStickerDrawer: () => void - // 封面 - coverDrawerOpen: boolean - onCloseCoverDrawer: () => void - coverConfig: CoverConfig - setCoverConfig: (config: CoverConfig | ((prev: CoverConfig) => CoverConfig)) => void - templateId: string } const EditorDrawers: React.FC = ({ @@ -143,11 +135,6 @@ const EditorDrawers: React.FC = ({ stickerSettings, onStickerChange, onCloseStickerDrawer, - coverDrawerOpen, - onCloseCoverDrawer, - coverConfig, - setCoverConfig, - templateId, }) => { const transitionConfig = transitionTargetClipId ? (clips.find((c) => c.id === transitionTargetClipId)?.transition ?? DEFAULT_TRANSITION) @@ -253,15 +240,6 @@ const EditorDrawers: React.FC = ({ totalDuration={totalDuration} /> - {/* 封面选择器 Drawer */} - ) } diff --git a/apps/web/src/pages/editing-planner/components/PreviewPlayer.tsx b/apps/web/src/pages/editing-planner/components/PreviewPlayer.tsx index ddab8b6cc..9daec44bd 100644 --- a/apps/web/src/pages/editing-planner/components/PreviewPlayer.tsx +++ b/apps/web/src/pages/editing-planner/components/PreviewPlayer.tsx @@ -1,12 +1,10 @@ /** * 预览区 — V8 原型 1:1 还原 - * 手机模型预览 + 封面预览 并排 - * 封面为只读展示(从模板/计划继承) + * 手机模型预览 */ import React from "react" import type { ClipData, ClipType } from "../types" import type { TitleConfig } from "@/api/template-editor" -import type { CoverConfig } from "../types" interface SubtitleSettings { enabled: boolean @@ -21,7 +19,6 @@ interface PreviewPlayerProps { selectedClipId: string | null isPlaying: boolean titleConfig?: TitleConfig - coverConfig?: CoverConfig subtitleSettings?: SubtitleSettings onClipSelect: (clipId: string) => void onPlayPause: () => void @@ -37,18 +34,11 @@ const CLIP_TYPE_LABELS: Record = { pip: "混剪", } -const COVER_MODE_LABELS: Record = { - auto: "智能封面", - frame: "抽帧封面", - upload: "上传封面", -} - const PreviewPlayer: React.FC = ({ clips, selectedClipId, isPlaying, titleConfig, - coverConfig, subtitleSettings, onPlayPause, }) => { @@ -132,26 +122,6 @@ const PreviewPlayer: React.FC = ({ )} - - {/* 封面预览(只读) */} -
-
- {coverConfig?.thumbnail_url || coverConfig?.upload_url ? ( - 封面预览 - ) : displayClip ? ( - {CLIP_TYPE_ICONS[displayClip.type] || "🎬"} - ) : ( - 暂无封面 - )} -
-
- {coverConfig?.enabled ? COVER_MODE_LABELS[coverConfig.mode] || "封面预览" : "未启用封面"} -
-
) } diff --git a/apps/web/src/pages/editing-planner/components/RightPanel.tsx b/apps/web/src/pages/editing-planner/components/RightPanel.tsx index f9e4601da..8d710ba50 100644 --- a/apps/web/src/pages/editing-planner/components/RightPanel.tsx +++ b/apps/web/src/pages/editing-planner/components/RightPanel.tsx @@ -35,7 +35,6 @@ interface RightPanelProps { onOpenFilterDrawer: () => void onOpenGreenScreenDrawer: () => void onOpenStickerDrawer: () => void - onOpenCoverDrawer: () => void // 片段 tab clips: ClipData[] selectedClipId: string | null @@ -73,7 +72,6 @@ const RightPanel: React.FC = ({ onOpenFilterDrawer, onOpenGreenScreenDrawer, onOpenStickerDrawer, - onOpenCoverDrawer, clips, selectedClipId, onClipSelect, @@ -144,7 +142,6 @@ const RightPanel: React.FC = ({ onOpenFilterDrawer={onOpenFilterDrawer} onOpenGreenScreenDrawer={onOpenGreenScreenDrawer} onOpenStickerDrawer={onOpenStickerDrawer} - onOpenCoverDrawer={onOpenCoverDrawer} /> ) })()} diff --git a/apps/web/src/pages/editing-planner/components/cover-selector/CoverModePanels.tsx b/apps/web/src/pages/editing-planner/components/cover-selector/CoverModePanels.tsx deleted file mode 100644 index d13dd5072..000000000 --- a/apps/web/src/pages/editing-planner/components/cover-selector/CoverModePanels.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from "react" -import type { CoverConfig } from "../../types" - -interface CoverAutoModeProps { - config: CoverConfig - formatTime: (s: number) => string - onUseAiSuggestion: () => void -} - -/** 智能封面模式面板 */ -export const CoverAutoMode: React.FC = ({ - config, - formatTime, - onUseAiSuggestion, -}) => ( -
-
AI 将分析视频内容,自动选择最具吸引力的画面作为封面。
- {config.ai_suggested_time !== null ? ( -
-
AI 推荐
-
推荐时间点:{formatTime(config.ai_suggested_time)}
- -
- ) : ( -
-
- AI 分析中...(生成视频后自动推荐) -
- )} -
-) - -interface CoverFrameModeProps { - config: CoverConfig - totalDuration: number - formatTime: (s: number) => string - onFrameTimeChange: (time: number) => void -} - -/** 抽帧选封面模式面板 */ -export const CoverFrameMode: React.FC = ({ - config, - totalDuration, - formatTime, - onFrameTimeChange, -}) => ( -
-
-
- 🎞️ - {formatTime(config.frame_time)} -
-
-
-
- 拖动选择封面帧 - {formatTime(config.frame_time)} -
- onFrameTimeChange(Number(e.target.value))} - /> -
- 00:00 - {formatTime(totalDuration)} -
-
-
- 快捷选帧: - {[0, 0.25, 0.5, 0.75].map((ratio) => { - const t = totalDuration * ratio - return ( - - ) - })} -
-
-) - -interface CoverUploadModeProps { - config: CoverConfig - isDragging: boolean - fileInputRef: React.RefObject - onDragOver: (e: React.DragEvent) => void - onDragLeave: () => void - onDrop: (e: React.DragEvent) => void - onAreaClick: () => void - onFileChange: (file: File) => void -} - -/** 上传封面模式面板 */ -export const CoverUploadMode: React.FC = ({ - config, - isDragging, - fileInputRef, - onDragOver, - onDragLeave, - onDrop, - onAreaClick, - onFileChange, -}) => ( -
-
- {config.upload_url ? ( -
- 封面预览 -
点击更换
-
- ) : ( -
- 📤 - 点击或拖拽上传封面图片 - 支持 JPG / PNG,建议 16:9 比例 -
- )} - { - const file = e.target.files?.[0] - if (file) onFileChange(file) - }} - /> -
-
-) diff --git a/apps/web/src/pages/editing-planner/components/cover-selector/index.tsx b/apps/web/src/pages/editing-planner/components/cover-selector/index.tsx deleted file mode 100644 index 5ee92f1b9..000000000 --- a/apps/web/src/pages/editing-planner/components/cover-selector/index.tsx +++ /dev/null @@ -1,195 +0,0 @@ -/** - * 封面选择器 - * 抽帧选封面 + 上传自定义封面 + 智能封面推荐 - */ -import React, { useState } from "react" -import { Drawer, Modal, Spin, message } from "antd" -import { generateCover } from "@/api/template-editor/aiFeatures" -import type { CoverConfig, CoverMode } from "../../types" -import { useCoverSelector, MODE_LABELS, MODE_ICONS } from "./useCoverSelector" -import { CoverAutoMode, CoverFrameMode, CoverUploadMode } from "./CoverModePanels" - -interface CoverSelectorProps { - open: boolean - onClose: () => void - config: CoverConfig - onChange: (config: CoverConfig) => void - totalDuration: number - templateId: string -} - -const CoverSelector: React.FC = ({ - open, - onClose, - config, - onChange, - totalDuration, - templateId, -}) => { - const { - fileInputRef, - isDragging, - setIsDragging, - update, - handleReset, - handleModeChange, - handleFileUpload, - handleDrop, - handleUseAiSuggestion, - formatTime, - } = useCoverSelector({ config, onChange }) - - const [generating, setGenerating] = useState(false) - - const handleGenerateCover = async () => { - if (!templateId) { - message.error("请先保存模板") - return - } - setGenerating(true) - try { - const res = await generateCover(templateId, { - asset_ids: [], - cover_type: "ai_frame", - }) - const imageUrl = res.cover?.image_url || res.cover?.thumbnail_url || "" - if (imageUrl) { - update({ upload_url: imageUrl, thumbnail_url: imageUrl }) - } - message.success("封面生成成功") - } catch (err: any) { - message.error("封面生成失败: " + (err?.message || "未知错误")) - } finally { - setGenerating(false) - } - } - - return ( - - {/* 顶部开关 */} -
- 启用自定义封面 - -
- - {/* 模式选择 */} -
-
封面来源
-
- {(["auto", "frame", "upload"] as CoverMode[]).map((m) => ( - - ))} -
-
- - {/* 模式内容区 */} -
- {config.mode === "auto" && ( - - )} - - {config.mode === "frame" && ( - update({ frame_time: t })} - /> - )} - - {config.mode === "upload" && ( - { - e.preventDefault() - setIsDragging(true) - }} - onDragLeave={() => setIsDragging(false)} - onDrop={handleDrop} - onAreaClick={() => fileInputRef.current?.click()} - onFileChange={handleFileUpload} - /> - )} -
- - {/* 封面预览 */} -
-
封面预览
-
- {config.upload_url ? ( - 封面预览 - ) : ( -
- 🖼️ - - {config.mode === "auto" - ? "AI 智能选择" - : config.mode === "frame" - ? `帧 ${formatTime(config.frame_time)}` - : "未上传封面"} - -
- )} -
16:9
-
-
- - {/* AI 生成封面按钮 */} - {config.enabled && config.mode === "auto" && ( -
- -
- )} - - {/* 底部 */} -
- -
- - {/* 生成进度弹窗 */} - -
- -

AI 正在生成封面,请稍候...

-
-
-
- ) -} - -export default CoverSelector diff --git a/apps/web/src/pages/editing-planner/components/cover-selector/useCoverSelector.ts b/apps/web/src/pages/editing-planner/components/cover-selector/useCoverSelector.ts deleted file mode 100644 index dbb649e22..000000000 --- a/apps/web/src/pages/editing-planner/components/cover-selector/useCoverSelector.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { useCallback, useRef, useState } from "react" -import type { CoverConfig, CoverMode } from "../../types" -import { DEFAULT_COVER_CONFIG } from "../../types" - -/** 封面模式标签 */ -export const MODE_LABELS: Record = { - auto: "智能封面", - frame: "抽帧选封面", - upload: "上传封面", -} - -/** 封面模式图标 */ -export const MODE_ICONS: Record = { - auto: "🤖", - frame: "🎞️", - upload: "📤", -} - -interface UseCoverSelectorOptions { - config: CoverConfig - onChange: (config: CoverConfig) => void -} - -/** - * 封面选择器 Hook - * 封装状态管理、文件上传、模式切换等逻辑 - */ -export function useCoverSelector({ config, onChange }: UseCoverSelectorOptions) { - const fileInputRef = useRef(null) - const [isDragging, setIsDragging] = useState(false) - - const update = useCallback( - (partial: Partial) => { - onChange({ ...config, ...partial }) - }, - [config, onChange], - ) - - const handleReset = useCallback(() => { - onChange({ ...DEFAULT_COVER_CONFIG, enabled: config.enabled }) - }, [config.enabled, onChange]) - - const handleModeChange = useCallback( - (mode: CoverMode) => { - update({ mode }) - }, - [update], - ) - - const handleFileUpload = useCallback( - (file: File) => { - if (!file.type.startsWith("image/")) return - const reader = new FileReader() - reader.onload = (e) => { - const url = e.target?.result as string - update({ upload_url: url, thumbnail_url: url, mode: "upload" }) - } - reader.readAsDataURL(file) - }, - [update], - ) - - const handleDrop = useCallback( - (e: React.DragEvent) => { - e.preventDefault() - setIsDragging(false) - const file = e.dataTransfer.files[0] - if (file) handleFileUpload(file) - }, - [handleFileUpload], - ) - - const handleUseAiSuggestion = useCallback(() => { - if (config.ai_suggested_time !== null) { - update({ frame_time: config.ai_suggested_time, mode: "frame" }) - } - }, [config.ai_suggested_time, update]) - - const formatTime = (seconds: number) => { - const m = Math.floor(seconds / 60) - const s = Math.floor(seconds % 60) - const ms = Math.floor((seconds % 1) * 10) - return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}.${ms}` - } - - return { - fileInputRef, - isDragging, - setIsDragging, - update, - handleReset, - handleModeChange, - handleFileUpload, - handleDrop, - handleUseAiSuggestion, - formatTime, - } -} diff --git a/apps/web/src/pages/editing-planner/hooks/template-management/usePlanLoading.ts b/apps/web/src/pages/editing-planner/hooks/template-management/usePlanLoading.ts index 8f15ce36c..1c5f209e0 100644 --- a/apps/web/src/pages/editing-planner/hooks/template-management/usePlanLoading.ts +++ b/apps/web/src/pages/editing-planner/hooks/template-management/usePlanLoading.ts @@ -6,7 +6,6 @@ import type { SubtitleStyleConfig } from "../../types/subtitle" import type { TitleConfig } from "@/api/template-editor" import type { BgmMixConfig } from "@/api/bgm" import type { TransitionEffect } from "@/api/template-editor" -import type { CoverConfig } from "../../types" interface UsePlanLoadingOptions { loadedPlanId: string | null @@ -16,7 +15,6 @@ interface UsePlanLoadingOptions { setTitleConfig: Dispatch> setSubtitleSettings: Dispatch> setBgmSettings: Dispatch> - setCoverConfig: Dispatch> } /** @@ -31,7 +29,6 @@ export function usePlanLoading({ setTitleConfig, setSubtitleSettings, setBgmSettings, - setCoverConfig, }: UsePlanLoadingOptions) { useEffect(() => { if (!loadedPlanId) return @@ -77,17 +74,6 @@ export function usePlanLoading({ music_id: cfg.bgm_config!.music_id || "", })) } - if (cfg.cover_config) { - setCoverConfig((prev: CoverConfig) => ({ - ...prev, - enabled: cfg.cover_config!.enabled ?? prev.enabled, - mode: (cfg.cover_config!.mode as CoverConfig["mode"]) || prev.mode, - frame_time: cfg.cover_config!.frame_time ?? prev.frame_time, - upload_url: cfg.cover_config!.upload_url || prev.upload_url, - thumbnail_url: cfg.cover_config!.thumbnail_url || prev.thumbnail_url, - ai_suggested_time: cfg.cover_config!.ai_suggested_time ?? prev.ai_suggested_time, - })) - } /* 还原片段:优先从后端 clips 表,其次从 config.segments 兜底 */ const backendClips = clipsRes?.items || [] @@ -152,6 +138,5 @@ export function usePlanLoading({ setTitleConfig, setSubtitleSettings, setBgmSettings, - setCoverConfig, ]) } diff --git a/apps/web/src/pages/editing-planner/hooks/template-management/useTemplateSave.ts b/apps/web/src/pages/editing-planner/hooks/template-management/useTemplateSave.ts index 0ba6f2223..d4f03146f 100644 --- a/apps/web/src/pages/editing-planner/hooks/template-management/useTemplateSave.ts +++ b/apps/web/src/pages/editing-planner/hooks/template-management/useTemplateSave.ts @@ -17,7 +17,6 @@ import type { } from "../../types" import type { SubtitleStyleConfig } from "../../types/subtitle" import type { TitleConfig } from "@/api/template-editor" -import type { CoverConfig } from "../../types" import type { BgmMixConfig } from "@/api/bgm" interface UseTemplateSaveOptions { @@ -33,7 +32,6 @@ interface UseTemplateSaveOptions { filterSettings: FilterConfig chromaKeySettings: ChromaKeyConfig stickerSettings: StickerConfig - coverConfig: CoverConfig loadedTemplateId: string | null loadTemplates: () => Promise } @@ -55,7 +53,6 @@ export function useTemplateSave(options: UseTemplateSaveOptions) { filterSettings, chromaKeySettings, stickerSettings, - coverConfig, loadedTemplateId, loadTemplates, } = options @@ -132,7 +129,6 @@ export function useTemplateSave(options: UseTemplateSaveOptions) { filter_config: { ...filterSettings }, green_screen_config: { ...chromaKeySettings }, sticker_config: { ...stickerSettings }, - cover_config: { ...coverConfig }, } if (loadedTemplateId) { await updateEditingTemplate(loadedTemplateId, payload) @@ -163,7 +159,6 @@ export function useTemplateSave(options: UseTemplateSaveOptions) { filterSettings, chromaKeySettings, stickerSettings, - coverConfig, loadedTemplateId, loadTemplates, ]) diff --git a/apps/web/src/pages/editing-planner/hooks/useEditorDrawers.ts b/apps/web/src/pages/editing-planner/hooks/useEditorDrawers.ts index ff44236c8..310ae7010 100644 --- a/apps/web/src/pages/editing-planner/hooks/useEditorDrawers.ts +++ b/apps/web/src/pages/editing-planner/hooks/useEditorDrawers.ts @@ -17,7 +17,6 @@ export const useEditorDrawers = () => { const [filterDrawerOpen, setFilterDrawerOpen] = useState(false) const [chromaKeyDrawerOpen, setChromaKeyDrawerOpen] = useState(false) const [stickerDrawerOpen, setStickerDrawerOpen] = useState(false) - const [coverDrawerOpen, setCoverDrawerOpen] = useState(false) /* ── 目标片段 ID ── */ /** 当前正在编辑转场的片段 ID(null = 全局默认转场) */ @@ -67,8 +66,6 @@ export const useEditorDrawers = () => { setChromaKeyDrawerOpen, stickerDrawerOpen, setStickerDrawerOpen, - coverDrawerOpen, - setCoverDrawerOpen, // 目标 ID transitionTargetClipId, speedTargetClipId, diff --git a/apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts b/apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts index 27f653743..8fb10a33b 100644 --- a/apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts +++ b/apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts @@ -11,7 +11,6 @@ import type { FilterConfig, ChromaKeyConfig, StickerConfig, - CoverConfig, } from "../types" import { DEFAULT_WATERMARK, @@ -20,7 +19,6 @@ import { DEFAULT_FILTER_CONFIG, DEFAULT_CHROMA_KEY_CONFIG, DEFAULT_STICKER_CONFIG, - DEFAULT_COVER_CONFIG, } from "../types" import type { SubtitleStyleConfig } from "../types/subtitle" import { DEFAULT_SUBTITLE_STYLE } from "../types/subtitle" @@ -47,8 +45,6 @@ export interface GlobalSettings { setChromaKeySettings: (config: ChromaKeyConfig) => void stickerSettings: StickerConfig setStickerSettings: (config: StickerConfig) => void - coverConfig: CoverConfig - setCoverConfig: (config: CoverConfig | ((prev: CoverConfig) => CoverConfig)) => void } export const useGlobalSettings = (): GlobalSettings => { @@ -92,10 +88,6 @@ export const useGlobalSettings = (): GlobalSettings => { ...DEFAULT_STICKER_CONFIG, }) - const [coverConfig, setCoverConfig] = useState({ - ...DEFAULT_COVER_CONFIG, - }) - return { titleConfig, setTitleConfig, @@ -115,7 +107,5 @@ export const useGlobalSettings = (): GlobalSettings => { setChromaKeySettings, stickerSettings, setStickerSettings, - coverConfig, - setCoverConfig, } } diff --git a/apps/web/src/pages/editing-planner/hooks/useTemplateManagement.ts b/apps/web/src/pages/editing-planner/hooks/useTemplateManagement.ts index e84787819..3e07fd8a5 100644 --- a/apps/web/src/pages/editing-planner/hooks/useTemplateManagement.ts +++ b/apps/web/src/pages/editing-planner/hooks/useTemplateManagement.ts @@ -9,7 +9,6 @@ import type { FilterConfig, ChromaKeyConfig, StickerConfig, - CoverConfig, } from "../types" import type { SubtitleStyleConfig } from "../types/subtitle" import type { BgmMixConfig } from "@/api/bgm" @@ -29,7 +28,6 @@ interface UseTemplateManagementParams { setTitleConfig: Dispatch> setSubtitleSettings: Dispatch> setBgmSettings: Dispatch> - setCoverConfig: Dispatch> clips: ClipData[] totalDuration: number titleConfig: TitleConfig @@ -41,7 +39,6 @@ interface UseTemplateManagementParams { filterSettings: FilterConfig chromaKeySettings: ChromaKeyConfig stickerSettings: StickerConfig - coverConfig: CoverConfig } /** @@ -59,7 +56,6 @@ export const useTemplateManagement = (params: UseTemplateManagementParams) => { setTitleConfig, setSubtitleSettings, setBgmSettings, - setCoverConfig, clips, totalDuration, titleConfig, @@ -71,7 +67,6 @@ export const useTemplateManagement = (params: UseTemplateManagementParams) => { filterSettings, chromaKeySettings, stickerSettings, - coverConfig, } = params const [currentMode, setCurrentMode] = useState("pip") @@ -119,7 +114,6 @@ export const useTemplateManagement = (params: UseTemplateManagementParams) => { filterSettings, chromaKeySettings, stickerSettings, - coverConfig, loadedTemplateId, loadTemplates, }) @@ -146,7 +140,6 @@ export const useTemplateManagement = (params: UseTemplateManagementParams) => { setTitleConfig, setSubtitleSettings, setBgmSettings, - setCoverConfig, }) /* ── 事件 ── */ diff --git a/apps/web/src/pages/editing-planner/types/clipProperties.ts b/apps/web/src/pages/editing-planner/types/clipProperties.ts index 51bdd29cf..f2572d599 100644 --- a/apps/web/src/pages/editing-planner/types/clipProperties.ts +++ b/apps/web/src/pages/editing-planner/types/clipProperties.ts @@ -67,6 +67,4 @@ export interface ClipPropertiesPanelProps { onOpenGreenScreenDrawer?: () => void /** 打开贴纸面板 Drawer */ onOpenStickerDrawer?: () => void - /** 打开封面选择器 Drawer */ - onOpenCoverDrawer?: () => void } diff --git a/apps/web/src/test/pages/EditingPlanner.test.tsx b/apps/web/src/test/pages/EditingPlanner.test.tsx index a5d5926ab..bdfb5f3e9 100644 --- a/apps/web/src/test/pages/EditingPlanner.test.tsx +++ b/apps/web/src/test/pages/EditingPlanner.test.tsx @@ -149,7 +149,6 @@ vi.mock("@/api/editing-planner", () => ({ vi.mock("@/api/template-editor", () => ({ getMediaAssets: vi.fn().mockResolvedValue({ items: [] }), getEditPlanGenerations: vi.fn().mockResolvedValue({ items: [] }), - generateCover: vi.fn().mockResolvedValue({}), getEditPlan: vi.fn().mockResolvedValue({}), createEditPlan: vi.fn().mockResolvedValue({ id: "test-plan" }), updateEditPlan: vi.fn().mockResolvedValue({}), @@ -223,9 +222,6 @@ vi.mock("@/pages/editing-planner/components/GreenScreenPanel", () => ({ vi.mock("@/pages/editing-planner/components/StickerPanel", () => ({ default: () => React.createElement("div", { "data-testid": "StickerPanel" }), })) -vi.mock("@/pages/editing-planner/components/CoverSelector", () => ({ - default: () => React.createElement("div", { "data-testid": "CoverSelector" }), -})) vi.mock("@/pages/editing-planner/components/SaveModal", () => ({ default: () => React.createElement("div", { "data-testid": "SaveModal" }), })) @@ -260,7 +256,6 @@ vi.mock("@/pages/editing-planner/types", () => ({ DEFAULT_FILTER_CONFIG: { enabled: false }, DEFAULT_CHROMA_KEY_CONFIG: { enabled: false }, DEFAULT_STICKER_CONFIG: { enabled: false }, - DEFAULT_COVER_CONFIG: { enabled: false }, })) vi.mock("@/pages/editing-planner/types/subtitle", () => ({ diff --git a/apps/web/src/test/pages/editing-planner/components/CoverSelector.test.tsx b/apps/web/src/test/pages/editing-planner/components/CoverSelector.test.tsx deleted file mode 100644 index 0a2305619..000000000 --- a/apps/web/src/test/pages/editing-planner/components/CoverSelector.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, expect, it, vi } from "vitest" -import { render } from "@testing-library/react" -import CoverSelector from "@/pages/editing-planner/components/CoverSelector" -import { DEFAULT_COVER_CONFIG } from "@/pages/editing-planner/types" - -const defaultProps = { - open: true, - onClose: vi.fn(), - config: DEFAULT_COVER_CONFIG, - onChange: vi.fn(), - totalDuration: 60, -} - -describe("CoverSelector", () => { - it("should render without crashing", () => { - const { container } = render() - expect(container).toBeTruthy() - }) - - it("should render when closed", () => { - const { container } = render() - expect(container).toBeTruthy() - }) -}) diff --git a/apps/web/src/test/pages/editing-planner/components/PreviewPlayer.test.tsx b/apps/web/src/test/pages/editing-planner/components/PreviewPlayer.test.tsx index 09d0ff76b..51b2a18b9 100644 --- a/apps/web/src/test/pages/editing-planner/components/PreviewPlayer.test.tsx +++ b/apps/web/src/test/pages/editing-planner/components/PreviewPlayer.test.tsx @@ -6,15 +6,10 @@ const defaultProps = { clips: [], selectedClipId: null, isPlaying: false, - currentCoverScheme: "scheme1", - coverSchemes: [{ id: "scheme1", name: "方案1", cover_url: "" }], - aiCoverLoading: false, titleSettings: undefined, subtitleSettings: undefined, onClipSelect: vi.fn(), - onCoverSchemeChange: vi.fn(), onPlayPause: vi.fn(), - onAiGenerateCover: vi.fn(), } describe("PreviewPlayer", () => { diff --git a/apps/web/src/test/pages/editing-planner/smoke.test.tsx b/apps/web/src/test/pages/editing-planner/smoke.test.tsx index 95aa7b22a..995d96b59 100755 --- a/apps/web/src/test/pages/editing-planner/smoke.test.tsx +++ b/apps/web/src/test/pages/editing-planner/smoke.test.tsx @@ -28,7 +28,6 @@ import "@/pages/editing-planner/utils/clipProperties" // 子组件 import "@/pages/editing-planner/components/BgmSelector" import "@/pages/editing-planner/components/ClipPropertiesPanel" -import "@/pages/editing-planner/components/CoverSelector" import "@/pages/editing-planner/components/EditorClipList" import "@/pages/editing-planner/components/EditingDrawers" import "@/pages/editing-planner/components/FilterPanel"