manager.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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. Configuration Manager - Singleton pattern
  14. Provides unified access to configuration with automatic validation.
  15. """
  16. from pathlib import Path
  17. from typing import Any, Optional
  18. from loguru import logger
  19. from .schema import PixelleVideoConfig
  20. from .loader import load_config_dict, save_config_dict
  21. class ConfigManager:
  22. """
  23. Configuration Manager (Singleton)
  24. Provides unified access to configuration with automatic validation.
  25. """
  26. _instance: Optional['ConfigManager'] = None
  27. def __new__(cls, config_path: str = "config.yaml"):
  28. if cls._instance is None:
  29. cls._instance = super().__new__(cls)
  30. return cls._instance
  31. def __init__(self, config_path: str = "config.yaml"):
  32. # Only initialize once
  33. if hasattr(self, '_initialized'):
  34. return
  35. self.config_path = Path(config_path)
  36. self.config: PixelleVideoConfig = self._load()
  37. self._initialized = True
  38. def _load(self) -> PixelleVideoConfig:
  39. """Load configuration from file"""
  40. data = load_config_dict(str(self.config_path))
  41. config = PixelleVideoConfig(**data)
  42. # Validate template path exists
  43. self._validate_template(config.template.default_template)
  44. return config
  45. def _validate_template(self, template_path: str):
  46. """Validate that the configured template exists"""
  47. from pixelle_video.utils.template_util import resolve_template_path
  48. try:
  49. # Try to resolve the template path
  50. resolved_path = resolve_template_path(template_path)
  51. logger.debug(f"Template validation passed: {template_path} -> {resolved_path}")
  52. except FileNotFoundError as e:
  53. logger.warning(
  54. f"Configured default template '{template_path}' not found. "
  55. f"Will fall back to '1080x1920/default.html' if needed. Error: {e}"
  56. )
  57. def reload(self):
  58. """Reload configuration from file"""
  59. self.config = self._load()
  60. logger.info("Configuration reloaded")
  61. def save(self):
  62. """Save current configuration to file"""
  63. save_config_dict(self.config.to_dict(), str(self.config_path))
  64. def update(self, updates: dict):
  65. """
  66. Update configuration with new values
  67. Args:
  68. updates: Dictionary of updates (e.g., {"llm": {"api_key": "xxx"}})
  69. """
  70. current = self.config.to_dict()
  71. # Deep merge
  72. def deep_merge(base: dict, updates: dict) -> dict:
  73. for key, value in updates.items():
  74. if key in base and isinstance(base[key], dict) and isinstance(value, dict):
  75. deep_merge(base[key], value)
  76. else:
  77. base[key] = value
  78. return base
  79. merged = deep_merge(current, updates)
  80. self.config = PixelleVideoConfig(**merged)
  81. def get(self, key: str, default: Any = None) -> Any:
  82. """Dict-like access (for backward compatibility)"""
  83. return self.config.to_dict().get(key, default)
  84. def validate(self) -> bool:
  85. """Validate configuration completeness"""
  86. return self.config.validate_required()
  87. def get_llm_config(self) -> dict:
  88. """Get LLM configuration as dict"""
  89. return {
  90. "api_key": self.config.llm.api_key,
  91. "base_url": self.config.llm.base_url,
  92. "model": self.config.llm.model,
  93. }
  94. def set_llm_config(self, api_key: str, base_url: str, model: str):
  95. """Set LLM configuration"""
  96. self.update({
  97. "llm": {
  98. "api_key": api_key,
  99. "base_url": base_url,
  100. "model": model,
  101. }
  102. })
  103. def get_comfyui_config(self) -> dict:
  104. """Get ComfyUI configuration as dict"""
  105. return {
  106. "comfyui_url": self.config.comfyui.comfyui_url,
  107. "comfyui_api_key": self.config.comfyui.comfyui_api_key,
  108. "runninghub_api_key": self.config.comfyui.runninghub_api_key,
  109. "runninghub_concurrent_limit": self.config.comfyui.runninghub_concurrent_limit,
  110. "runninghub_instance_type": self.config.comfyui.runninghub_instance_type,
  111. "tts": {
  112. "default_workflow": self.config.comfyui.tts.default_workflow,
  113. },
  114. "image": {
  115. "default_workflow": self.config.comfyui.image.default_workflow,
  116. "prompt_prefix": self.config.comfyui.image.prompt_prefix,
  117. },
  118. "video": {
  119. "default_workflow": self.config.comfyui.video.default_workflow,
  120. "prompt_prefix": self.config.comfyui.video.prompt_prefix,
  121. }
  122. }
  123. def set_comfyui_config(
  124. self,
  125. comfyui_url: Optional[str] = None,
  126. comfyui_api_key: Optional[str] = None,
  127. runninghub_api_key: Optional[str] = None,
  128. runninghub_concurrent_limit: Optional[int] = None,
  129. runninghub_instance_type: Optional[str] = None
  130. ):
  131. """Set ComfyUI global configuration"""
  132. updates = {}
  133. if comfyui_url is not None:
  134. updates["comfyui_url"] = comfyui_url
  135. if comfyui_api_key is not None:
  136. updates["comfyui_api_key"] = comfyui_api_key
  137. if runninghub_api_key is not None:
  138. updates["runninghub_api_key"] = runninghub_api_key
  139. if runninghub_concurrent_limit is not None:
  140. updates["runninghub_concurrent_limit"] = runninghub_concurrent_limit
  141. if runninghub_instance_type is not None:
  142. # Empty string means disable (treat as None for storage)
  143. updates["runninghub_instance_type"] = runninghub_instance_type if runninghub_instance_type else None
  144. if updates:
  145. self.update({"comfyui": updates})