返回 Pixelle-Video
batch_manager.py
根目录 / web / utils / batch_manager.py
1 # Copyright (C) 2025 AIDC-AI
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12
13 """
14 Lightweight batch manager for Streamlit (Simplified YAGNI version)
15 """
16 import time
17 import traceback
18 from typing import List, Dict, Any, Optional, Callable
19 from loguru import logger
20
21
22 class SimpleBatchManager:
23 """
24 Ultra-simple batch manager following YAGNI principle
25
26 Design principles:
27 1. Only supports "AI generate content" mode
28 2. Same config for all videos, only topics differ
29 3. No CSV, no complex validation, just loop and execute
30 """
31
32 def __init__(self):
33 self.results = []
34 self.errors = []
35 self.current_index = 0
36 self.total_count = 0
37
38 def execute_batch(
39 self,
40 pixelle_video,
41 topics: List[str],
42 shared_config: Dict[str, Any],
43 overall_progress_callback: Optional[Callable] = None,
44 task_progress_callback_factory: Optional[Callable] = None
45 ) -> Dict[str, Any]:
46 """
47 Execute batch generation with shared config
48
49 Args:
50 pixelle_video: PixelleVideoCore instance
51 topics: List of topics (one per video)
52 shared_config: Shared configuration for all videos
53 overall_progress_callback: Callback for overall progress
54 task_progress_callback_factory: Factory function to create per-task callback
55
56 Returns:
57 {
58 "results": [...],
59 "errors": [...],
60 "total_count": N,
61 "success_count": M,
62 "failed_count": K
63 }
64 """
65 self.results = []
66 self.errors = []
67 self.total_count = len(topics)
68
69 logger.info(f"Starting batch generation: {self.total_count} topics")
70
71 for idx, topic in enumerate(topics, 1):
72 self.current_index = idx
73
74 # Report overall progress
75 if overall_progress_callback:
76 overall_progress_callback(
77 current=idx,
78 total=self.total_count,
79 topic=topic
80 )
81
82 try:
83 logger.info(f"Task {idx}/{self.total_count} started: {topic}")
84
85 # Extract title_prefix from shared_config (not a valid parameter for generate_video)
86 title_prefix = shared_config.get("title_prefix")
87
88 # Build task params (merge topic with shared config, excluding title_prefix)
89 task_params = {
90 "text": topic, # Topic as input
91 "mode": "generate", # Fixed mode
92 }
93
94 # Merge shared config, excluding title_prefix and None values
95 # Filter out None values to avoid interfering with parameter logic in generate_video
96 for key, value in shared_config.items():
97 if key != "title_prefix" and value is not None:
98 task_params[key] = value
99
100 # Generate title using title_prefix
101 if title_prefix:
102 task_params["title"] = f"{title_prefix} - {topic}"
103 else:
104 # Use topic as title
105 task_params["title"] = topic
106
107 # Add per-task progress callback
108 if task_progress_callback_factory:
109 task_params["progress_callback"] = task_progress_callback_factory(idx, topic)
110
111 # Execute generation
112 from web.utils.async_helpers import run_async
113 result = run_async(pixelle_video.generate_video(**task_params))
114
115 # Extract task_id from video_path (e.g., output/20251118_173821_f96a/final.mp4)
116 from pathlib import Path
117 task_id = Path(result.video_path).parent.name
118
119 # Record success
120 self.results.append({
121 "index": idx,
122 "topic": topic,
123 "task_id": task_id,
124 "video_path": result.video_path,
125 "status": "success"
126 })
127
128 logger.info(f"Task {idx}/{self.total_count} completed: {result.video_path}")
129
130 except Exception as e:
131 # Record error but continue
132 error_msg = str(e)
133 error_trace = traceback.format_exc()
134
135 logger.error(f"Task {idx}/{self.total_count} failed: {error_msg}")
136 logger.debug(f"Error traceback:\n{error_trace}")
137
138 self.errors.append({
139 "index": idx,
140 "topic": topic,
141 "error": error_msg,
142 "traceback": error_trace,
143 "status": "failed"
144 })
145
146 # Continue to next task
147 continue
148
149 success_count = len(self.results)
150 failed_count = len(self.errors)
151
152 logger.info(
153 f"Batch generation completed: "
154 f"{success_count}/{self.total_count} succeeded, "
155 f"{failed_count} failed"
156 )
157
158 return {
159 "results": self.results,
160 "errors": self.errors,
161 "total_count": self.total_count,
162 "success_count": success_count,
163 "failed_count": failed_count
164 }
165
166
166 lines PYTHON