From 007a39c03af4498d82fe81cd3dbf161a39e7b684 Mon Sep 17 00:00:00 2001 From: puke <1129090915@qq.com> Date: Thu, 4 Dec 2025 15:23:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=E5=9F=BA=E4=BA=8E=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E7=B4=A0=E6=9D=90=E7=94=9F=E6=88=90=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E7=9A=84webui=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pixelle_video/pipelines/asset_based.py | 21 ++++-- pixelle_video/services/frame_processor.py | 26 ++++--- pixelle_video/services/video_analysis.py | 12 ++-- .../{image_pure.html => asset_default.html} | 67 +++++++++++++------ workflows/selfhost/analyse_image.json | 62 ++++++----------- workflows/selfhost/analyse_video.json | 63 +++++++++++++++++ 6 files changed, 172 insertions(+), 79 deletions(-) rename templates/1080x1920/{image_pure.html => asset_default.html} (62%) create mode 100644 workflows/selfhost/analyse_video.json diff --git a/pixelle_video/pipelines/asset_based.py b/pixelle_video/pipelines/asset_based.py index 413beed..5c85cac 100644 --- a/pixelle_video/pipelines/asset_based.py +++ b/pixelle_video/pipelines/asset_based.py @@ -472,7 +472,9 @@ Generate the video script now:""" context.narrations = all_narrations # Get template dimensions - template_name = "1080x1920/image_pure.html" + # Use asset_default.html template which supports both image and video assets + # (conditionally shows background image or provides transparent overlay) + template_name = "1080x1920/asset_default.html" # Extract dimensions from template name (e.g., "1080x1920") try: dims = template_name.split("/")[0].split("x") @@ -524,9 +526,20 @@ Generate the video script now:""" created_at=datetime.now() ) - # Store matched asset path in the frame - frame.image_path = scene["matched_asset"] - frame.media_type = "image" + # Get asset path and determine actual media type from asset_index + asset_path = scene["matched_asset"] + asset_metadata = self.asset_index.get(asset_path, {}) + asset_type = asset_metadata.get("type", "image") # Default to image if not found + + # Set media type and path based on actual asset type + if asset_type == "video": + frame.media_type = "video" + frame.video_path = asset_path + logger.debug(f"Scene {i}: Using video asset: {Path(asset_path).name}") + else: + frame.media_type = "image" + frame.image_path = asset_path + logger.debug(f"Scene {i}: Using image asset: {Path(asset_path).name}") # Store scene info for later audio generation frame._scene_data = scene # Temporary storage for multi-narration diff --git a/pixelle_video/services/frame_processor.py b/pixelle_video/services/frame_processor.py index 30d16e0..7ec5507 100644 --- a/pixelle_video/services/frame_processor.py +++ b/pixelle_video/services/frame_processor.py @@ -73,8 +73,8 @@ class FrameProcessor: frame_num = frame.index + 1 # Determine if this frame needs image generation - # If image_path is already set (e.g. asset-based pipeline), we consider it "needs image" but skip generation - has_existing_image = frame.image_path is not None + # If image_path or video_path is already set (e.g. asset-based pipeline), we consider it "has existing media" but skip generation + has_existing_media = frame.image_path is not None or frame.video_path is not None needs_generation = frame.image_prompt is not None try: @@ -93,7 +93,6 @@ class FrameProcessor: else: logger.debug(f" 1/4: Using existing audio: {frame.audio_path}") - # Step 2: Generate media (image or video, conditional) # Step 2: Generate media (image or video, conditional) if needs_generation: if progress_callback: @@ -106,8 +105,12 @@ class FrameProcessor: action="media" )) await self._step_generate_media(frame, config) - elif has_existing_image: - logger.debug(f" 2/4: Using existing image: {frame.image_path}") + elif has_existing_media: + # Log appropriate message based on media type + if frame.video_path: + logger.debug(f" 2/4: Using existing video: {frame.video_path}") + else: + logger.debug(f" 2/4: Using existing image: {frame.image_path}") else: frame.image_path = None frame.media_type = None @@ -117,7 +120,7 @@ class FrameProcessor: if progress_callback: progress_callback(ProgressEvent( event_type="frame_step", - progress=0.50 if (needs_generation or has_existing_image) else 0.33, + progress=0.50 if (needs_generation or has_existing_media) else 0.33, frame_current=frame_num, frame_total=total_frames, step=3, @@ -129,7 +132,7 @@ class FrameProcessor: if progress_callback: progress_callback(ProgressEvent( event_type="frame_step", - progress=0.75 if (needs_generation or has_existing_image) else 0.67, + progress=0.75 if (needs_generation or has_existing_media) else 0.67, frame_current=frame_num, frame_total=total_frames, step=4, @@ -313,12 +316,14 @@ class FrameProcessor: # Generate frame using HTML (size is auto-parsed from template path) generator = HTMLFrameGenerator(template_path) - logger.debug(f"Generating frame with image: '{frame.image_path}' (type: {type(frame.image_path)})") + # Use video_path for video media, image_path for images + media_path = frame.video_path if frame.media_type == "video" else frame.image_path + logger.debug(f"Generating frame with media: '{media_path}' (type: {frame.media_type})") composed_path = await generator.generate_frame( title=storyboard.title, text=frame.narration, - image=frame.image_path, + image=media_path, # HTMLFrameGenerator handles both image and video paths ext=ext, output_path=output_path ) @@ -372,7 +377,8 @@ class FrameProcessor: os.unlink(temp_video_with_overlay) elif frame.media_type == "image" or frame.media_type is None: - # Image workflow: create video from image + audio + # Image workflow: Use composed image directly + # The asset_default.html template includes the image in the composition logger.debug(f" → Using image-based composition") segment_path = video_service.create_video_from_image( diff --git a/pixelle_video/services/video_analysis.py b/pixelle_video/services/video_analysis.py index 85ce5f0..56a41c5 100644 --- a/pixelle_video/services/video_analysis.py +++ b/pixelle_video/services/video_analysis.py @@ -32,9 +32,9 @@ class VideoAnalysisService(ComfyBaseService): Uses ComfyKit to execute video understanding workflows. Returns detailed textual descriptions of video content. - Convention: workflows follow {source}/video_understanding.json pattern - - runninghub/video_understanding.json (default, cloud-based) - - selfhost/video_understanding.json (local ComfyUI, future) + Convention: workflows follow {source}/analyse_video.json pattern + - runninghub/analyse_video.json (default, cloud-based) + - selfhost/analyse_video.json (local ComfyUI, future) Usage: # Use default (runninghub cloud) @@ -50,7 +50,7 @@ class VideoAnalysisService(ComfyBaseService): workflows = pixelle_video.video_analysis.list_workflows() """ - WORKFLOW_PREFIX = "video_understanding" + WORKFLOW_PREFIX = "analyse_video" WORKFLOWS_DIR = "workflows" def __init__(self, config: dict, core=None): @@ -114,8 +114,8 @@ class VideoAnalysisService(ComfyBaseService): # 2. Resolve workflow path using convention if workflow is None: - # Use standardized naming: {source}/video_understanding.json - workflow = resolve_workflow_path("video_understanding", source) + # Use standardized naming: {source}/analyse_video.json + workflow = resolve_workflow_path("analyse_video", source) logger.info(f"Using {source} workflow: {workflow}") # 3. Resolve workflow (returns structured info) diff --git a/templates/1080x1920/image_pure.html b/templates/1080x1920/asset_default.html similarity index 62% rename from templates/1080x1920/image_pure.html rename to templates/1080x1920/asset_default.html index 880d42f..f38c226 100644 --- a/templates/1080x1920/image_pure.html +++ b/templates/1080x1920/asset_default.html @@ -1,5 +1,6 @@ +
@@ -9,17 +10,16 @@ margin: 0; padding: 0; } - + body { margin: 0; padding: 0; width: 1080px; height: 1920px; font-family: 'PingFang SC', 'Source Han Sans', 'Microsoft YaHei', sans-serif; - background: #000; overflow: hidden; } - + .page-container { width: 1080px; height: 1920px; @@ -27,7 +27,10 @@ overflow: hidden; } - /* 1. Background Image Layer (垫底图片) */ + /* 1. Background Media Layer (背景媒体层) + - For image assets: displays the image + - For video assets: hidden (video is composited in later step) + */ .background-layer { position: absolute; top: 0; @@ -44,10 +47,15 @@ display: block; } + /* Hide background layer when no image (video mode) */ + .background-layer:empty { + display: none; + } + /* 2. Gradient Overlay (渐变遮罩) - Ensures text readability regardless of image brightness + Ensures text readability regardless of background brightness Top: Darker for Title - Middle: Transparent for Image visibility + Middle: Transparent for Media visibility Bottom: Darker for Subtitles */ .gradient-overlay { @@ -57,13 +65,11 @@ width: 100%; height: 100%; z-index: 1; - background: linear-gradient( - to bottom, - rgba(0,0,0,0.6) 0%, - rgba(0,0,0,0.1) 25%, - rgba(0,0,0,0.1) 60%, - rgba(0,0,0,0.8) 100% - ); + background: linear-gradient(to bottom, + rgba(0, 0, 0, 0.6) 0%, + rgba(0, 0, 0, 0.1) 25%, + rgba(0, 0, 0, 0.1) 60%, + rgba(0, 0, 0, 0.8) 100%); } /* 3. Content Layer (内容层) */ @@ -72,7 +78,8 @@ z-index: 2; width: 100%; height: 100%; - padding: 120px 80px 0px 80px; /* Top, Right, Bottom, Left */ + padding: 120px 80px 0px 80px; + /* Top, Right, Bottom, Left */ box-sizing: border-box; display: flex; flex-direction: column; @@ -85,7 +92,7 @@ font-size: 80px; font-weight: 700; line-height: 1.2; - text-shadow: 0 4px 12px rgba(0,0,0,0.5); + text-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); margin-bottom: 40px; text-align: center; } @@ -110,16 +117,20 @@ font-weight: 500; line-height: 1.6; text-align: center; - text-shadow: 0 2px 8px rgba(0,0,0,0.6); + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6); backdrop-filter: blur(4px); } +