返回 Pixelle-Video
resources.py
根目录 / api / routers / resources.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 Resource discovery endpoints
15
16 Provides endpoints to discover available workflows, templates, and BGM.
17 """
18
19 from pathlib import Path
20 from fastapi import APIRouter, HTTPException
21 from loguru import logger
22
23 from api.dependencies import PixelleVideoDep
24 from api.schemas.resources import (
25 WorkflowInfo,
26 WorkflowListResponse,
27 TemplateInfo,
28 TemplateListResponse,
29 BGMInfo,
30 BGMListResponse,
31 )
32 from pixelle_video.utils.os_util import list_resource_files, get_root_path, get_data_path
33 from pixelle_video.utils.template_util import get_all_templates_with_info
34
35 router = APIRouter(prefix="/resources", tags=["Resources"])
36
37
38 @router.get("/workflows/tts", response_model=WorkflowListResponse)
39 async def list_tts_workflows(pixelle_video: PixelleVideoDep):
40 """
41 List available TTS workflows
42
43 Returns list of TTS workflows from both RunningHub and self-hosted sources.
44
45 Example response:
46 ```json
47 {
48 "workflows": [
49 {
50 "name": "tts_edge.json",
51 "display_name": "tts_edge.json - Runninghub",
52 "source": "runninghub",
53 "path": "workflows/runninghub/tts_edge.json",
54 "key": "runninghub/tts_edge.json",
55 "workflow_id": "123456"
56 }
57 ]
58 }
59 ```
60 """
61 try:
62 # Get all workflows from TTS service
63 all_workflows = pixelle_video.tts.list_workflows()
64
65 # Filter to TTS workflows only (filename starts with "tts_")
66 tts_workflows = [
67 WorkflowInfo(**wf)
68 for wf in all_workflows
69 if wf["name"].startswith("tts_")
70 ]
71
72 return WorkflowListResponse(workflows=tts_workflows)
73
74 except Exception as e:
75 logger.error(f"List TTS workflows error: {e}")
76 raise HTTPException(status_code=500, detail=str(e))
77
78
79 @router.get("/workflows/media", response_model=WorkflowListResponse)
80 async def list_media_workflows(pixelle_video: PixelleVideoDep):
81 """
82 List available media workflows (both image and video)
83
84 Returns list of all media workflows from both RunningHub and self-hosted sources.
85
86 Example response:
87 ```json
88 {
89 "workflows": [
90 {
91 "name": "image_flux.json",
92 "display_name": "image_flux.json - Runninghub",
93 "source": "runninghub",
94 "path": "workflows/runninghub/image_flux.json",
95 "key": "runninghub/image_flux.json",
96 "workflow_id": "123456"
97 },
98 {
99 "name": "video_wan2.1.json",
100 "display_name": "video_wan2.1.json - Runninghub",
101 "source": "runninghub",
102 "path": "workflows/runninghub/video_wan2.1.json",
103 "key": "runninghub/video_wan2.1.json",
104 "workflow_id": "123457"
105 }
106 ]
107 }
108 ```
109 """
110 try:
111 # Get all workflows from media service (includes both image and video)
112 all_workflows = pixelle_video.media.list_workflows()
113
114 media_workflows = [WorkflowInfo(**wf) for wf in all_workflows]
115
116 return WorkflowListResponse(workflows=media_workflows)
117
118 except Exception as e:
119 logger.error(f"List media workflows error: {e}")
120 raise HTTPException(status_code=500, detail=str(e))
121
122
123 # Keep old endpoint for backward compatibility
124 @router.get("/workflows/image", response_model=WorkflowListResponse)
125 async def list_image_workflows(pixelle_video: PixelleVideoDep):
126 """
127 List available image workflows (deprecated, use /workflows/media instead)
128
129 This endpoint is kept for backward compatibility but will filter to image_ workflows only.
130 """
131 try:
132 all_workflows = pixelle_video.media.list_workflows()
133
134 # Filter to image workflows only (filename starts with "image_")
135 image_workflows = [
136 WorkflowInfo(**wf)
137 for wf in all_workflows
138 if wf["name"].startswith("image_")
139 ]
140
141 return WorkflowListResponse(workflows=image_workflows)
142
143 except Exception as e:
144 logger.error(f"List image workflows error: {e}")
145 raise HTTPException(status_code=500, detail=str(e))
146
147
148 @router.get("/templates", response_model=TemplateListResponse)
149 async def list_templates():
150 """
151 List available video templates
152
153 Returns list of HTML templates grouped by size (portrait, landscape, square).
154 Templates are merged from both default (templates/) and custom (data/templates/) directories.
155
156 Example response:
157 ```json
158 {
159 "templates": [
160 {
161 "name": "default.html",
162 "display_name": "default.html",
163 "size": "1080x1920",
164 "width": 1080,
165 "height": 1920,
166 "orientation": "portrait",
167 "path": "templates/1080x1920/default.html",
168 "key": "1080x1920/default.html"
169 }
170 ]
171 }
172 ```
173 """
174 try:
175 # Get all templates with info
176 all_templates = get_all_templates_with_info()
177
178 # Convert to API response format
179 templates = []
180 for t in all_templates:
181 templates.append(TemplateInfo(
182 name=t.display_info.name,
183 display_name=t.display_info.name,
184 size=t.display_info.size,
185 width=t.display_info.width,
186 height=t.display_info.height,
187 orientation=t.display_info.orientation,
188 path=t.template_path,
189 key=t.template_path
190 ))
191
192 return TemplateListResponse(templates=templates)
193
194 except Exception as e:
195 logger.error(f"List templates error: {e}")
196 raise HTTPException(status_code=500, detail=str(e))
197
198
199 @router.get("/bgm", response_model=BGMListResponse)
200 async def list_bgm():
201 """
202 List available background music files
203
204 Returns list of BGM files merged from both default (bgm/) and custom (data/bgm/) directories.
205 Custom files take precedence over default files with the same name.
206
207 Supported formats: mp3, wav, flac, m4a, aac, ogg
208
209 Example response:
210 ```json
211 {
212 "bgm_files": [
213 {
214 "name": "default.mp3",
215 "path": "bgm/default.mp3",
216 "source": "default"
217 },
218 {
219 "name": "happy.mp3",
220 "path": "data/bgm/happy.mp3",
221 "source": "custom"
222 }
223 ]
224 }
225 ```
226 """
227 try:
228 # Supported audio extensions
229 audio_extensions = ('.mp3', '.wav', '.flac', '.m4a', '.aac', '.ogg')
230
231 # Collect BGM files from both locations
232 bgm_files_dict = {} # {filename: {"path": str, "source": str}}
233
234 # Scan default bgm/ directory
235 default_bgm_dir = Path(get_root_path("bgm"))
236 if default_bgm_dir.exists() and default_bgm_dir.is_dir():
237 for item in default_bgm_dir.iterdir():
238 if item.is_file() and item.suffix.lower() in audio_extensions:
239 bgm_files_dict[item.name] = {
240 "path": f"bgm/{item.name}",
241 "source": "default"
242 }
243
244 # Scan custom data/bgm/ directory (overrides default)
245 custom_bgm_dir = Path(get_data_path("bgm"))
246 if custom_bgm_dir.exists() and custom_bgm_dir.is_dir():
247 for item in custom_bgm_dir.iterdir():
248 if item.is_file() and item.suffix.lower() in audio_extensions:
249 bgm_files_dict[item.name] = {
250 "path": f"data/bgm/{item.name}",
251 "source": "custom"
252 }
253
254 # Convert to response format
255 bgm_files = [
256 BGMInfo(
257 name=name,
258 path=info["path"],
259 source=info["source"]
260 )
261 for name, info in sorted(bgm_files_dict.items())
262 ]
263
264 return BGMListResponse(bgm_files=bgm_files)
265
266 except Exception as e:
267 logger.error(f"List BGM error: {e}")
268 raise HTTPException(status_code=500, detail=str(e))
269
270
270 lines PYTHON