返回 Pixelle-Video
files.py
根目录 / api / routers / files.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 File service endpoints
15
16 Provides access to generated files (videos, images, audio) and resource files.
17 """
18
19 from pathlib import Path
20 from fastapi import APIRouter, HTTPException
21 from fastapi.responses import FileResponse
22 from loguru import logger
23
24 router = APIRouter(prefix="/files", tags=["Files"])
25
26
27 @router.get("/{file_path:path}")
28 async def get_file(file_path: str):
29 """
30 Get file by path
31
32 Serves files from allowed directories:
33 - output/ - Generated files (videos, images, audio)
34 - workflows/ - ComfyUI workflow files
35 - templates/ - HTML templates
36 - bgm/ - Background music
37 - data/bgm/ - Custom background music
38 - data/templates/ - Custom templates
39 - resources/ - Other resources (images, fonts, etc.)
40
41 - **file_path**: File path relative to allowed directories
42
43 Examples:
44 - "abc123.mp4" → output/abc123.mp4
45 - "workflows/runninghub/image_flux.json" → workflows/runninghub/image_flux.json
46 - "templates/1080x1920/default.html" → templates/1080x1920/default.html
47 - "bgm/default.mp3" → bgm/default.mp3
48 - "resources/example.png" → resources/example.png
49
50 Returns file for download or preview.
51 """
52 try:
53 # Define allowed directories (in priority order)
54 allowed_prefixes = [
55 "output/",
56 "workflows/",
57 "templates/",
58 "bgm/",
59 "data/bgm/",
60 "data/templates/",
61 "resources/",
62 ]
63
64 # Check if path starts with allowed prefix, otherwise try output/
65 full_path = None
66 for prefix in allowed_prefixes:
67 if file_path.startswith(prefix):
68 full_path = file_path
69 break
70
71 # If no prefix matched, assume it's in output/ (backward compatibility)
72 if full_path is None:
73 full_path = f"output/{file_path}"
74
75 abs_path = Path.cwd() / full_path
76
77 if not abs_path.exists():
78 raise HTTPException(status_code=404, detail=f"File not found: {file_path}")
79
80 if not abs_path.is_file():
81 raise HTTPException(status_code=400, detail=f"Path is not a file: {file_path}")
82
83 # Security: only allow access to specified directories
84 try:
85 rel_path = abs_path.relative_to(Path.cwd())
86 rel_path_str = str(rel_path)
87
88 # Check if path starts with any allowed prefix
89 is_allowed = any(rel_path_str.startswith(prefix.rstrip('/')) for prefix in allowed_prefixes)
90
91 if not is_allowed:
92 raise HTTPException(
93 status_code=403,
94 detail=f"Access denied: only {', '.join(p.rstrip('/') for p in allowed_prefixes)} directories are accessible"
95 )
96 except ValueError:
97 raise HTTPException(status_code=403, detail="Access denied")
98
99 # Determine media type
100 suffix = abs_path.suffix.lower()
101 media_types = {
102 '.mp4': 'video/mp4',
103 '.mp3': 'audio/mpeg',
104 '.wav': 'audio/wav',
105 '.png': 'image/png',
106 '.jpg': 'image/jpeg',
107 '.jpeg': 'image/jpeg',
108 '.gif': 'image/gif',
109 '.html': 'text/html',
110 '.json': 'application/json',
111 }
112 media_type = media_types.get(suffix, 'application/octet-stream')
113
114 # Use inline disposition for browser preview
115 return FileResponse(
116 path=str(abs_path),
117 media_type=media_type,
118 headers={
119 "Content-Disposition": f'inline; filename="{abs_path.name}"'
120 }
121 )
122
123 except HTTPException:
124 raise
125 except Exception as e:
126 logger.error(f"File access error: {e}")
127 raise HTTPException(status_code=500, detail=str(e))
128
129
129 lines PYTHON