File size: 6,156 Bytes
e054d0c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""
文件管理 API

文件上传、下载和管理 API 端点

API 列表:
- POST   /files           上传文件
- GET    /files           获取文件列表
- GET    /files/{file_id} 下载文件(或获取元数据)
- DELETE /files/{file_id} 删除文件
"""

from typing import Optional

from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
from fastapi.responses import Response

from ....models.schemas.file import (
    FileMetadata,
    FileUploadResponse,
    FileListResponse,
    FileDeleteResponse,
)
from ....models.schemas.common import ErrorResponse
from ....services.file_service import FileService
from ...deps import get_file_service

router = APIRouter()

# 允许的音频 MIME 类型
ALLOWED_AUDIO_TYPES = {
    "audio/wav",
    "audio/wave",
    "audio/x-wav",
    "audio/mpeg",
    "audio/mp3",
    "audio/mp4",
    "audio/aac",
    "audio/ogg",
    "audio/flac",
    "audio/x-flac",
    "audio/webm",
}

# 最大文件大小 (500MB)
MAX_FILE_SIZE = 500 * 1024 * 1024


@router.post(
    "",
    response_model=FileUploadResponse,
    summary="上传文件",
    description="""
上传音频文件用于训练。

**支持的音频格式**:
- WAV
- MP3
- FLAC
- OGG
- AAC
- WebM

**文件大小限制**: 500MB

**用途类型**:
- `training`: 训练音频(默认)
- `reference`: 参考音频
- `output`: 输出文件
""",
    responses={
        200: {"model": FileUploadResponse, "description": "文件上传成功"},
        400: {"model": ErrorResponse, "description": "文件格式或大小不合法"},
    },
)
async def upload_file(
    file: UploadFile = File(..., description="要上传的音频文件"),
    purpose: str = Query(
        "training",
        description="文件用途: training, reference, output"
    ),
    service: FileService = Depends(get_file_service),
) -> FileUploadResponse:
    """
    上传文件
    """
    # 验证用途
    if purpose not in ("training", "reference", "output"):
        raise HTTPException(
            status_code=400,
            detail="无效的用途类型,有效值: training, reference, output"
        )
    
    # 验证文件类型(可选,允许不明确类型的文件)
    content_type = file.content_type
    if content_type and content_type not in ALLOWED_AUDIO_TYPES:
        # 检查文件扩展名
        filename = file.filename or ""
        ext = filename.lower().split(".")[-1] if "." in filename else ""
        allowed_exts = {"wav", "mp3", "flac", "ogg", "aac", "webm", "m4a"}
        if ext not in allowed_exts:
            raise HTTPException(
                status_code=400,
                detail=f"不支持的文件类型: {content_type}。支持的格式: WAV, MP3, FLAC, OGG, AAC, WebM"
            )
    
    # 读取文件内容
    file_data = await file.read()
    
    # 验证文件大小
    if len(file_data) > MAX_FILE_SIZE:
        raise HTTPException(
            status_code=400,
            detail=f"文件过大,最大允许 {MAX_FILE_SIZE // (1024*1024)}MB"
        )
    
    # 验证文件不为空
    if len(file_data) == 0:
        raise HTTPException(
            status_code=400,
            detail="文件为空"
        )
    
    # 上传文件
    return await service.upload_file(
        file_data=file_data,
        filename=file.filename or "audio",
        content_type=content_type,
        purpose=purpose,
    )


@router.get(
    "",
    response_model=FileListResponse,
    summary="获取文件列表",
    description="获取已上传的文件列表,支持按用途筛选和分页。",
)
async def list_files(
    purpose: Optional[str] = Query(
        None,
        description="按用途筛选: training, reference, output"
    ),
    limit: int = Query(50, ge=1, le=100, description="每页数量"),
    offset: int = Query(0, ge=0, description="偏移量"),
    service: FileService = Depends(get_file_service),
) -> FileListResponse:
    """
    获取文件列表
    """
    return await service.list_files(purpose=purpose, limit=limit, offset=offset)


@router.get(
    "/{file_id}",
    summary="下载文件或获取元数据",
    description="""
根据请求类型返回文件内容或元数据。

- 默认返回文件内容(用于下载)
- 添加 `?metadata=true` 参数只返回元数据
""",
    responses={
        200: {
            "description": "文件内容(下载时)或元数据(metadata=true 时)",
        },
        404: {"model": ErrorResponse, "description": "文件不存在"},
    },
)
async def get_file(
    file_id: str,
    metadata: bool = Query(False, description="只返回元数据"),
    service: FileService = Depends(get_file_service),
):
    """
    下载文件或获取元数据
    """
    if metadata:
        # 只返回元数据
        file_metadata = await service.get_file(file_id)
        if not file_metadata:
            raise HTTPException(status_code=404, detail="文件不存在")
        return file_metadata
    else:
        # 下载文件
        result = await service.download_file(file_id)
        if not result:
            raise HTTPException(status_code=404, detail="文件不存在")
        
        file_data, filename, content_type = result
        
        return Response(
            content=file_data,
            media_type=content_type,
            headers={
                "Content-Disposition": f'attachment; filename="{filename}"',
                "Content-Length": str(len(file_data)),
            },
        )


@router.delete(
    "/{file_id}",
    response_model=FileDeleteResponse,
    summary="删除文件",
    description="删除指定的文件。",
    responses={
        200: {"model": FileDeleteResponse, "description": "删除结果"},
        404: {"model": ErrorResponse, "description": "文件不存在"},
    },
)
async def delete_file(
    file_id: str,
    service: FileService = Depends(get_file_service),
) -> FileDeleteResponse:
    """
    删除文件
    """
    result = await service.delete_file(file_id)
    if not result.success:
        raise HTTPException(status_code=404, detail="文件不存在或已删除")
    return result