batch_manager.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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. Lightweight batch manager for Streamlit (Simplified YAGNI version)
  14. """
  15. import time
  16. import traceback
  17. from typing import List, Dict, Any, Optional, Callable
  18. from loguru import logger
  19. class SimpleBatchManager:
  20. """
  21. Ultra-simple batch manager following YAGNI principle
  22. Design principles:
  23. 1. Only supports "AI generate content" mode
  24. 2. Same config for all videos, only topics differ
  25. 3. No CSV, no complex validation, just loop and execute
  26. """
  27. def __init__(self):
  28. self.results = []
  29. self.errors = []
  30. self.current_index = 0
  31. self.total_count = 0
  32. def execute_batch(
  33. self,
  34. pixelle_video,
  35. topics: List[str],
  36. shared_config: Dict[str, Any],
  37. overall_progress_callback: Optional[Callable] = None,
  38. task_progress_callback_factory: Optional[Callable] = None
  39. ) -> Dict[str, Any]:
  40. """
  41. Execute batch generation with shared config
  42. Args:
  43. pixelle_video: PixelleVideoCore instance
  44. topics: List of topics (one per video)
  45. shared_config: Shared configuration for all videos
  46. overall_progress_callback: Callback for overall progress
  47. task_progress_callback_factory: Factory function to create per-task callback
  48. Returns:
  49. {
  50. "results": [...],
  51. "errors": [...],
  52. "total_count": N,
  53. "success_count": M,
  54. "failed_count": K
  55. }
  56. """
  57. self.results = []
  58. self.errors = []
  59. self.total_count = len(topics)
  60. logger.info(f"Starting batch generation: {self.total_count} topics")
  61. for idx, topic in enumerate(topics, 1):
  62. self.current_index = idx
  63. # Report overall progress
  64. if overall_progress_callback:
  65. overall_progress_callback(
  66. current=idx,
  67. total=self.total_count,
  68. topic=topic
  69. )
  70. try:
  71. logger.info(f"Task {idx}/{self.total_count} started: {topic}")
  72. # Extract title_prefix from shared_config (not a valid parameter for generate_video)
  73. title_prefix = shared_config.get("title_prefix")
  74. # Build task params (merge topic with shared config, excluding title_prefix)
  75. task_params = {
  76. "text": topic, # Topic as input
  77. "mode": "generate", # Fixed mode
  78. }
  79. # Merge shared config, excluding title_prefix and None values
  80. # Filter out None values to avoid interfering with parameter logic in generate_video
  81. for key, value in shared_config.items():
  82. if key != "title_prefix" and value is not None:
  83. task_params[key] = value
  84. # Generate title using title_prefix
  85. if title_prefix:
  86. task_params["title"] = f"{title_prefix} - {topic}"
  87. else:
  88. # Use topic as title
  89. task_params["title"] = topic
  90. # Add per-task progress callback
  91. if task_progress_callback_factory:
  92. task_params["progress_callback"] = task_progress_callback_factory(idx, topic)
  93. # Execute generation
  94. from web.utils.async_helpers import run_async
  95. result = run_async(pixelle_video.generate_video(**task_params))
  96. # Extract task_id from video_path (e.g., output/20251118_173821_f96a/final.mp4)
  97. from pathlib import Path
  98. task_id = Path(result.video_path).parent.name
  99. # Record success
  100. self.results.append({
  101. "index": idx,
  102. "topic": topic,
  103. "task_id": task_id,
  104. "video_path": result.video_path,
  105. "status": "success"
  106. })
  107. logger.info(f"Task {idx}/{self.total_count} completed: {result.video_path}")
  108. except Exception as e:
  109. # Record error but continue
  110. error_msg = str(e)
  111. error_trace = traceback.format_exc()
  112. logger.error(f"Task {idx}/{self.total_count} failed: {error_msg}")
  113. logger.debug(f"Error traceback:\n{error_trace}")
  114. self.errors.append({
  115. "index": idx,
  116. "topic": topic,
  117. "error": error_msg,
  118. "traceback": error_trace,
  119. "status": "failed"
  120. })
  121. # Continue to next task
  122. continue
  123. success_count = len(self.results)
  124. failed_count = len(self.errors)
  125. logger.info(
  126. f"Batch generation completed: "
  127. f"{success_count}/{self.total_count} succeeded, "
  128. f"{failed_count} failed"
  129. )
  130. return {
  131. "results": self.results,
  132. "errors": self.errors,
  133. "total_count": self.total_count,
  134. "success_count": success_count,
  135. "failed_count": failed_count
  136. }