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