tamar-file-hub-client 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- file_hub_client/client.py +24 -4
- file_hub_client/rpc/async_client.py +31 -4
- file_hub_client/rpc/gen/file_service_pb2.py +30 -12
- file_hub_client/rpc/gen/file_service_pb2_grpc.py +173 -0
- file_hub_client/rpc/interceptors.py +578 -580
- file_hub_client/rpc/protos/file_service.proto +68 -1
- file_hub_client/rpc/sync_client.py +31 -4
- file_hub_client/schemas/__init__.py +10 -0
- file_hub_client/schemas/context.py +171 -160
- file_hub_client/schemas/file.py +171 -126
- file_hub_client/services/file/async_blob_service.py +260 -8
- file_hub_client/services/file/async_file_service.py +217 -0
- file_hub_client/services/file/sync_blob_service.py +261 -8
- file_hub_client/services/file/sync_file_service.py +217 -0
- file_hub_client/utils/__init__.py +14 -0
- file_hub_client/utils/file_utils.py +186 -153
- file_hub_client/utils/ip_detector.py +226 -0
- file_hub_client/utils/logging.py +335 -318
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/METADATA +178 -2
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/RECORD +22 -21
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/WHEEL +0 -0
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/top_level.txt +0 -0
file_hub_client/schemas/file.py
CHANGED
@@ -1,126 +1,171 @@
|
|
1
|
-
"""
|
2
|
-
文件相关数据模型
|
3
|
-
"""
|
4
|
-
from datetime import datetime
|
5
|
-
from typing import Optional, Dict, List, Any
|
6
|
-
from pydantic import BaseModel, Field
|
7
|
-
|
8
|
-
|
9
|
-
class File(BaseModel):
|
10
|
-
"""文件信息模型"""
|
11
|
-
id: str = Field(..., description="文件ID")
|
12
|
-
folder_id: str = Field(..., description="所属文件夹ID")
|
13
|
-
file_name: str = Field(..., description="原始文件名")
|
14
|
-
file_type: str = Field(..., description="文件类型")
|
15
|
-
created_at: datetime = Field(..., description="创建时间")
|
16
|
-
updated_at: datetime = Field(..., description="更新时间")
|
17
|
-
|
18
|
-
class Config:
|
19
|
-
json_encoders = {
|
20
|
-
datetime: lambda v: v.isoformat()
|
21
|
-
}
|
22
|
-
|
23
|
-
|
24
|
-
class UploadFile(BaseModel):
|
25
|
-
"""上传文件信息模型"""
|
26
|
-
id: str = Field(..., description="上传文件ID")
|
27
|
-
folder_id: str = Field(..., description="所属文件夹ID")
|
28
|
-
storage_type: str = Field(..., description="存储类型")
|
29
|
-
stored_name: str = Field(..., description="存储文件名")
|
30
|
-
stored_path: str = Field(..., description="存储路径")
|
31
|
-
file_id: str = Field(..., description="所属文件ID")
|
32
|
-
file_name: str = Field(..., description="原始文件名")
|
33
|
-
file_size: int = Field(0, description="文件大小(字节)")
|
34
|
-
file_ext: str = Field(..., description="文件后缀")
|
35
|
-
mime_type: str = Field(..., description="MIME类型")
|
36
|
-
created_at: datetime = Field(..., description="创建时间")
|
37
|
-
updated_at: datetime = Field(..., description="更新时间")
|
38
|
-
|
39
|
-
class Config:
|
40
|
-
json_encoders = {
|
41
|
-
datetime: lambda v: v.isoformat()
|
42
|
-
}
|
43
|
-
|
44
|
-
|
45
|
-
class FileUploadResponse(BaseModel):
|
46
|
-
"""文件上传返回"""
|
47
|
-
file: File = Field(..., description="文件信息")
|
48
|
-
upload_file: UploadFile = Field(..., description="上传文件信息")
|
49
|
-
|
50
|
-
|
51
|
-
class UploadUrlResponse(BaseModel):
|
52
|
-
"""上传URL响应"""
|
53
|
-
file: File = Field(..., description="文件信息")
|
54
|
-
upload_file: UploadFile = Field(..., description="上传文件信息")
|
55
|
-
upload_url: str = Field(..., description="上传URL")
|
56
|
-
|
57
|
-
|
58
|
-
class ShareLinkRequest(BaseModel):
|
59
|
-
"""生成分享链接请求"""
|
60
|
-
file_id: str = Field(..., description="文件ID")
|
61
|
-
is_public: bool = Field(True, description="是否公开")
|
62
|
-
access_scope: str = Field("view", description="访问范围")
|
63
|
-
expire_seconds: int = Field(86400, description="过期时间(秒)")
|
64
|
-
max_access: Optional[int] = Field(None, description="最大访问次数")
|
65
|
-
password: Optional[str] = Field(None, description="访问密码")
|
66
|
-
|
67
|
-
|
68
|
-
class FileVisitRequest(BaseModel):
|
69
|
-
"""文件访问请求"""
|
70
|
-
file_share_id: str = Field(..., description="分享ID")
|
71
|
-
access_type: str = Field(..., description="访问类型")
|
72
|
-
access_duration: int = Field(..., description="访问时长")
|
73
|
-
metadata: Dict[str, Any] = Field(default_factory=dict, description="元数据")
|
74
|
-
|
75
|
-
|
76
|
-
class FileListRequest(BaseModel):
|
77
|
-
"""文件列表请求"""
|
78
|
-
folder_id: str = Field(..., description="文件夹ID")
|
79
|
-
file_name: Optional[str] = Field(None, description="文件名")
|
80
|
-
file_type: Optional[List[str]] = Field(None, description="文件类型")
|
81
|
-
created_by_role: Optional[str] = Field(None, description="创建者角色")
|
82
|
-
created_by: Optional[str] = Field(None, description="创建者")
|
83
|
-
page_size: int = Field(20, description="每页大小")
|
84
|
-
page: int = Field(1, description="页码")
|
85
|
-
|
86
|
-
|
87
|
-
class FileListResponse(BaseModel):
|
88
|
-
"""文件列表响应"""
|
89
|
-
files: List[File] = Field(default_factory=list, description="文件列表")
|
90
|
-
|
91
|
-
|
92
|
-
class GetFileResponse(BaseModel):
|
93
|
-
"""获取文件响应"""
|
94
|
-
file: File = Field(..., description="文件信息")
|
95
|
-
upload_file: Optional[UploadFile] = Field(None, description="上传文件信息")
|
96
|
-
|
97
|
-
|
98
|
-
class DownloadUrlInfo(BaseModel):
|
99
|
-
"""下载URL信息"""
|
100
|
-
file_id: str = Field(..., description="文件ID")
|
101
|
-
url: str = Field(..., description="下载URL")
|
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
|
-
|
1
|
+
"""
|
2
|
+
文件相关数据模型
|
3
|
+
"""
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import Optional, Dict, List, Any
|
6
|
+
from pydantic import BaseModel, Field
|
7
|
+
|
8
|
+
|
9
|
+
class File(BaseModel):
|
10
|
+
"""文件信息模型"""
|
11
|
+
id: str = Field(..., description="文件ID")
|
12
|
+
folder_id: str = Field(..., description="所属文件夹ID")
|
13
|
+
file_name: str = Field(..., description="原始文件名")
|
14
|
+
file_type: str = Field(..., description="文件类型")
|
15
|
+
created_at: datetime = Field(..., description="创建时间")
|
16
|
+
updated_at: datetime = Field(..., description="更新时间")
|
17
|
+
|
18
|
+
class Config:
|
19
|
+
json_encoders = {
|
20
|
+
datetime: lambda v: v.isoformat()
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
class UploadFile(BaseModel):
|
25
|
+
"""上传文件信息模型"""
|
26
|
+
id: str = Field(..., description="上传文件ID")
|
27
|
+
folder_id: str = Field(..., description="所属文件夹ID")
|
28
|
+
storage_type: str = Field(..., description="存储类型")
|
29
|
+
stored_name: str = Field(..., description="存储文件名")
|
30
|
+
stored_path: str = Field(..., description="存储路径")
|
31
|
+
file_id: str = Field(..., description="所属文件ID")
|
32
|
+
file_name: str = Field(..., description="原始文件名")
|
33
|
+
file_size: int = Field(0, description="文件大小(字节)")
|
34
|
+
file_ext: str = Field(..., description="文件后缀")
|
35
|
+
mime_type: str = Field(..., description="MIME类型")
|
36
|
+
created_at: datetime = Field(..., description="创建时间")
|
37
|
+
updated_at: datetime = Field(..., description="更新时间")
|
38
|
+
|
39
|
+
class Config:
|
40
|
+
json_encoders = {
|
41
|
+
datetime: lambda v: v.isoformat()
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
class FileUploadResponse(BaseModel):
|
46
|
+
"""文件上传返回"""
|
47
|
+
file: File = Field(..., description="文件信息")
|
48
|
+
upload_file: UploadFile = Field(..., description="上传文件信息")
|
49
|
+
|
50
|
+
|
51
|
+
class UploadUrlResponse(BaseModel):
|
52
|
+
"""上传URL响应"""
|
53
|
+
file: File = Field(..., description="文件信息")
|
54
|
+
upload_file: UploadFile = Field(..., description="上传文件信息")
|
55
|
+
upload_url: str = Field(..., description="上传URL")
|
56
|
+
|
57
|
+
|
58
|
+
class ShareLinkRequest(BaseModel):
|
59
|
+
"""生成分享链接请求"""
|
60
|
+
file_id: str = Field(..., description="文件ID")
|
61
|
+
is_public: bool = Field(True, description="是否公开")
|
62
|
+
access_scope: str = Field("view", description="访问范围")
|
63
|
+
expire_seconds: int = Field(86400, description="过期时间(秒)")
|
64
|
+
max_access: Optional[int] = Field(None, description="最大访问次数")
|
65
|
+
password: Optional[str] = Field(None, description="访问密码")
|
66
|
+
|
67
|
+
|
68
|
+
class FileVisitRequest(BaseModel):
|
69
|
+
"""文件访问请求"""
|
70
|
+
file_share_id: str = Field(..., description="分享ID")
|
71
|
+
access_type: str = Field(..., description="访问类型")
|
72
|
+
access_duration: int = Field(..., description="访问时长")
|
73
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="元数据")
|
74
|
+
|
75
|
+
|
76
|
+
class FileListRequest(BaseModel):
|
77
|
+
"""文件列表请求"""
|
78
|
+
folder_id: str = Field(..., description="文件夹ID")
|
79
|
+
file_name: Optional[str] = Field(None, description="文件名")
|
80
|
+
file_type: Optional[List[str]] = Field(None, description="文件类型")
|
81
|
+
created_by_role: Optional[str] = Field(None, description="创建者角色")
|
82
|
+
created_by: Optional[str] = Field(None, description="创建者")
|
83
|
+
page_size: int = Field(20, description="每页大小")
|
84
|
+
page: int = Field(1, description="页码")
|
85
|
+
|
86
|
+
|
87
|
+
class FileListResponse(BaseModel):
|
88
|
+
"""文件列表响应"""
|
89
|
+
files: List[File] = Field(default_factory=list, description="文件列表")
|
90
|
+
|
91
|
+
|
92
|
+
class GetFileResponse(BaseModel):
|
93
|
+
"""获取文件响应"""
|
94
|
+
file: File = Field(..., description="文件信息")
|
95
|
+
upload_file: Optional[UploadFile] = Field(None, description="上传文件信息")
|
96
|
+
|
97
|
+
|
98
|
+
class DownloadUrlInfo(BaseModel):
|
99
|
+
"""下载URL信息"""
|
100
|
+
file_id: str = Field(..., description="文件ID")
|
101
|
+
url: str = Field(..., description="下载URL")
|
102
|
+
mime_type: str = Field(..., description="MIME类型")
|
103
|
+
error: Optional[str] = Field(None, description="错误信息")
|
104
|
+
|
105
|
+
|
106
|
+
class BatchDownloadUrlResponse(BaseModel):
|
107
|
+
"""批量下载URL响应"""
|
108
|
+
download_urls: List[DownloadUrlInfo] = Field(default_factory=list, description="下载URL列表")
|
109
|
+
|
110
|
+
|
111
|
+
class GcsUrlInfo(BaseModel):
|
112
|
+
"""GCS URL信息"""
|
113
|
+
file_id: str = Field(..., description="文件ID")
|
114
|
+
gcs_url: str = Field(..., description="GCS URL")
|
115
|
+
mime_type: str = Field(..., description="MIME类型")
|
116
|
+
error: Optional[str] = Field(None, description="错误信息")
|
117
|
+
|
118
|
+
|
119
|
+
class GetGcsUrlResponse(BaseModel):
|
120
|
+
"""获取GCS URL响应"""
|
121
|
+
gcs_url: str = Field(..., description="GCS URL")
|
122
|
+
mime_type: str = Field(..., description="MIME类型")
|
123
|
+
|
124
|
+
|
125
|
+
class BatchGcsUrlResponse(BaseModel):
|
126
|
+
"""批量GCS URL响应"""
|
127
|
+
gcs_urls: List[GcsUrlInfo] = Field(default_factory=list, description="GCS URL列表")
|
128
|
+
|
129
|
+
|
130
|
+
# ========= 压缩服务相关模型 =========
|
131
|
+
|
132
|
+
class CompressedVariant(BaseModel):
|
133
|
+
"""压缩变体信息"""
|
134
|
+
variant_name: str = Field(..., description="变体名称")
|
135
|
+
variant_type: str = Field(..., description="变体类型")
|
136
|
+
media_type: str = Field(..., description="媒体类型")
|
137
|
+
width: int = Field(..., description="宽度")
|
138
|
+
height: int = Field(..., description="高度")
|
139
|
+
file_size: int = Field(..., description="文件大小")
|
140
|
+
format: str = Field(..., description="格式")
|
141
|
+
quality: Optional[int] = Field(None, description="质量")
|
142
|
+
duration: Optional[float] = Field(None, description="时长")
|
143
|
+
bitrate: Optional[int] = Field(None, description="比特率")
|
144
|
+
fps: Optional[int] = Field(None, description="帧率")
|
145
|
+
compression_ratio: float = Field(..., description="压缩比")
|
146
|
+
stored_path: str = Field(..., description="存储路径")
|
147
|
+
|
148
|
+
|
149
|
+
class CompressionStatusResponse(BaseModel):
|
150
|
+
"""压缩状态响应"""
|
151
|
+
status: str = Field(..., description="状态: pending, processing, completed, failed")
|
152
|
+
error_message: Optional[str] = Field(None, description="错误信息")
|
153
|
+
variants: List[CompressedVariant] = Field(default_factory=list, description="压缩变体列表")
|
154
|
+
|
155
|
+
|
156
|
+
class GetVariantsResponse(BaseModel):
|
157
|
+
"""获取变体响应"""
|
158
|
+
variants: List[CompressedVariant] = Field(default_factory=list, description="压缩变体列表")
|
159
|
+
|
160
|
+
|
161
|
+
class RecompressionResponse(BaseModel):
|
162
|
+
"""重新压缩响应"""
|
163
|
+
task_id: str = Field(..., description="任务ID")
|
164
|
+
status: str = Field(..., description="状态")
|
165
|
+
|
166
|
+
|
167
|
+
class VariantDownloadUrlResponse(BaseModel):
|
168
|
+
"""变体下载URL响应"""
|
169
|
+
url: str = Field(..., description="下载URL")
|
170
|
+
error: Optional[str] = Field(None, description="错误信息")
|
171
|
+
variant_info: Optional[CompressedVariant] = Field(None, description="变体详细信息")
|
@@ -11,7 +11,7 @@ from .base_file_service import BaseFileService
|
|
11
11
|
from ...enums import UploadMode
|
12
12
|
from ...errors import ValidationError
|
13
13
|
from ...rpc import AsyncGrpcClient
|
14
|
-
from ...schemas import FileUploadResponse, UploadUrlResponse, BatchDownloadUrlResponse, DownloadUrlInfo, GcsUrlInfo, GetGcsUrlResponse, BatchGcsUrlResponse
|
14
|
+
from ...schemas import FileUploadResponse, UploadUrlResponse, BatchDownloadUrlResponse, DownloadUrlInfo, GcsUrlInfo, GetGcsUrlResponse, BatchGcsUrlResponse, CompressionStatusResponse, GetVariantsResponse, RecompressionResponse, VariantDownloadUrlResponse, CompressedVariant
|
15
15
|
from ...utils import AsyncHttpUploader, AsyncHttpDownloader, retry_with_backoff, get_file_mime_type
|
16
16
|
|
17
17
|
|
@@ -121,7 +121,7 @@ class AsyncBlobService(BaseFileService):
|
|
121
121
|
request_id: Optional[str] = None,
|
122
122
|
**metadata
|
123
123
|
) -> FileUploadResponse:
|
124
|
-
"""
|
124
|
+
"""流式上传(GCS 直传)"""
|
125
125
|
|
126
126
|
# 获取上传URL,以及对应的文件和上传文件信息
|
127
127
|
upload_url_resp = await self.generate_upload_url(
|
@@ -145,11 +145,17 @@ class AsyncBlobService(BaseFileService):
|
|
145
145
|
upload_file=upload_url_resp.upload_file
|
146
146
|
)
|
147
147
|
|
148
|
+
# 构建HTTP头,包含Content-Type和固定的Cache-Control
|
149
|
+
headers = {
|
150
|
+
"Content-Type": mime_type,
|
151
|
+
"Cache-Control": "public, max-age=86400" # 24小时公共缓存
|
152
|
+
}
|
153
|
+
|
148
154
|
# 上传文件到对象存储
|
149
155
|
await self.http_uploader.upload(
|
150
156
|
url=upload_url_resp.upload_url,
|
151
157
|
content=content,
|
152
|
-
headers=
|
158
|
+
headers=headers,
|
153
159
|
total_size=file_size,
|
154
160
|
)
|
155
161
|
|
@@ -181,7 +187,8 @@ class AsyncBlobService(BaseFileService):
|
|
181
187
|
request_id: Optional[str] = None,
|
182
188
|
**metadata
|
183
189
|
) -> FileUploadResponse:
|
184
|
-
"""
|
190
|
+
"""断点续传实现(GCS 直传)"""
|
191
|
+
|
185
192
|
# 获取断点续传URL,以及对应的文件和上传文件信息
|
186
193
|
upload_url_resp = await self.generate_resumable_upload_url(
|
187
194
|
file_name=file_name,
|
@@ -204,6 +211,12 @@ class AsyncBlobService(BaseFileService):
|
|
204
211
|
upload_file=upload_url_resp.upload_file
|
205
212
|
)
|
206
213
|
|
214
|
+
# 构建HTTP头,包含Content-Type和固定的Cache-Control
|
215
|
+
headers = {
|
216
|
+
"Content-Type": mime_type,
|
217
|
+
"Cache-Control": "public, max-age=86400" # 24小时公共缓存
|
218
|
+
}
|
219
|
+
|
207
220
|
# 开启断点续传
|
208
221
|
upload_url = await self.http_uploader.start_resumable_session(
|
209
222
|
url=upload_url_resp.upload_url,
|
@@ -215,7 +228,7 @@ class AsyncBlobService(BaseFileService):
|
|
215
228
|
await self.http_uploader.upload(
|
216
229
|
url=upload_url,
|
217
230
|
content=content,
|
218
|
-
headers=
|
231
|
+
headers=headers,
|
219
232
|
total_size=file_size,
|
220
233
|
)
|
221
234
|
|
@@ -413,6 +426,8 @@ class AsyncBlobService(BaseFileService):
|
|
413
426
|
|
414
427
|
Note:
|
415
428
|
必须提供 file 或 url 参数之一
|
429
|
+
|
430
|
+
Cache-Control 头在 GCS 直传模式(STREAM/RESUMABLE)下自动设置为 "public, max-age=86400"
|
416
431
|
"""
|
417
432
|
# 参数验证:必须提供 file 或 url 之一
|
418
433
|
if file is None and not url:
|
@@ -468,7 +483,7 @@ class AsyncBlobService(BaseFileService):
|
|
468
483
|
|
469
484
|
# 根据上传模式执行不同的上传逻辑
|
470
485
|
if mode == UploadMode.NORMAL:
|
471
|
-
# 普通上传(通过gRPC
|
486
|
+
# 普通上传(通过gRPC)- 不需要 Cache-Control
|
472
487
|
return await self._upload_file(
|
473
488
|
file_name=extracted_file_name,
|
474
489
|
content=content,
|
@@ -483,7 +498,7 @@ class AsyncBlobService(BaseFileService):
|
|
483
498
|
)
|
484
499
|
|
485
500
|
elif mode == UploadMode.STREAM:
|
486
|
-
#
|
501
|
+
# 流式上传(目前使用直传实现)- 需要 Cache-Control
|
487
502
|
return await self._upload_stream(
|
488
503
|
file_name=extracted_file_name,
|
489
504
|
content=content,
|
@@ -500,7 +515,7 @@ class AsyncBlobService(BaseFileService):
|
|
500
515
|
)
|
501
516
|
|
502
517
|
elif mode == UploadMode.RESUMABLE:
|
503
|
-
# 断点续传
|
518
|
+
# 断点续传 - 需要 Cache-Control
|
504
519
|
return await self._upload_resumable(
|
505
520
|
file_name=extracted_file_name,
|
506
521
|
content=content,
|
@@ -588,6 +603,30 @@ class AsyncBlobService(BaseFileService):
|
|
588
603
|
chunk_size=chunk_size,
|
589
604
|
)
|
590
605
|
|
606
|
+
async def download_to_bytes(
|
607
|
+
self,
|
608
|
+
file_id: str,
|
609
|
+
*,
|
610
|
+
request_id: Optional[str] = None,
|
611
|
+
**metadata
|
612
|
+
) -> bytes:
|
613
|
+
"""
|
614
|
+
下载文件并返回字节数据
|
615
|
+
|
616
|
+
Args:
|
617
|
+
file_id: 文件ID
|
618
|
+
request_id: 请求ID(可选,如果不提供则自动生成)
|
619
|
+
**metadata: 额外的元数据
|
620
|
+
|
621
|
+
Returns:
|
622
|
+
文件的字节数据
|
623
|
+
"""
|
624
|
+
# 获取下载URL
|
625
|
+
download_url = await self.generate_download_url(file_id, request_id=request_id, **metadata)
|
626
|
+
|
627
|
+
# 下载到内存并返回字节数据
|
628
|
+
return await self.http_downloader.download(url=download_url, save_path=None)
|
629
|
+
|
591
630
|
async def batch_generate_download_url(
|
592
631
|
self,
|
593
632
|
file_ids: List[str],
|
@@ -631,6 +670,7 @@ class AsyncBlobService(BaseFileService):
|
|
631
670
|
download_urls.append(DownloadUrlInfo(
|
632
671
|
file_id=url_info.file_id,
|
633
672
|
url=url_info.url,
|
673
|
+
mime_type=url_info.mime_type,
|
634
674
|
error=url_info.error if url_info.HasField('error') else None
|
635
675
|
))
|
636
676
|
|
@@ -710,3 +750,215 @@ class AsyncBlobService(BaseFileService):
|
|
710
750
|
))
|
711
751
|
|
712
752
|
return BatchGcsUrlResponse(gcs_urls=gcs_urls)
|
753
|
+
|
754
|
+
async def get_compression_status(
|
755
|
+
self,
|
756
|
+
file_id: str,
|
757
|
+
*,
|
758
|
+
request_id: Optional[str] = None,
|
759
|
+
**metadata
|
760
|
+
) -> CompressionStatusResponse:
|
761
|
+
"""
|
762
|
+
获取文件压缩状态
|
763
|
+
|
764
|
+
Args:
|
765
|
+
file_id: 文件ID
|
766
|
+
request_id: 请求ID,用于追踪
|
767
|
+
**metadata: 额外的gRPC元数据
|
768
|
+
|
769
|
+
Returns:
|
770
|
+
CompressionStatusResponse: 压缩状态响应
|
771
|
+
"""
|
772
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
773
|
+
|
774
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
775
|
+
|
776
|
+
request = file_service_pb2.CompressionStatusRequest(file_id=file_id)
|
777
|
+
|
778
|
+
# 构建元数据
|
779
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
780
|
+
|
781
|
+
response = await stub.GetCompressionStatus(request, metadata=grpc_metadata)
|
782
|
+
|
783
|
+
# 转换压缩变体
|
784
|
+
variants = []
|
785
|
+
for variant in response.variants:
|
786
|
+
variants.append(CompressedVariant(
|
787
|
+
variant_name=variant.variant_name,
|
788
|
+
variant_type=variant.variant_type,
|
789
|
+
media_type=variant.media_type,
|
790
|
+
width=variant.width,
|
791
|
+
height=variant.height,
|
792
|
+
file_size=variant.file_size,
|
793
|
+
format=variant.format,
|
794
|
+
quality=variant.quality if variant.quality else None,
|
795
|
+
duration=variant.duration if variant.duration else None,
|
796
|
+
bitrate=variant.bitrate if variant.bitrate else None,
|
797
|
+
fps=variant.fps if variant.fps else None,
|
798
|
+
compression_ratio=variant.compression_ratio,
|
799
|
+
stored_path=variant.stored_path
|
800
|
+
))
|
801
|
+
|
802
|
+
return CompressionStatusResponse(
|
803
|
+
status=response.status,
|
804
|
+
error_message=response.error_message if response.error_message else None,
|
805
|
+
variants=variants
|
806
|
+
)
|
807
|
+
|
808
|
+
async def get_compressed_variants(
|
809
|
+
self,
|
810
|
+
file_id: str,
|
811
|
+
*,
|
812
|
+
variant_type: Optional[str] = None,
|
813
|
+
request_id: Optional[str] = None,
|
814
|
+
**metadata
|
815
|
+
) -> GetVariantsResponse:
|
816
|
+
"""
|
817
|
+
获取文件的压缩变体
|
818
|
+
|
819
|
+
Args:
|
820
|
+
file_id: 文件ID
|
821
|
+
variant_type: 变体类型(image, video, thumbnail)
|
822
|
+
request_id: 请求ID,用于追踪
|
823
|
+
**metadata: 额外的gRPC元数据
|
824
|
+
|
825
|
+
Returns:
|
826
|
+
GetVariantsResponse: 压缩变体响应
|
827
|
+
"""
|
828
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
829
|
+
|
830
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
831
|
+
|
832
|
+
request = file_service_pb2.GetVariantsRequest(file_id=file_id)
|
833
|
+
if variant_type:
|
834
|
+
request.variant_type = variant_type
|
835
|
+
|
836
|
+
# 构建元数据
|
837
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
838
|
+
|
839
|
+
response = await stub.GetCompressedVariants(request, metadata=grpc_metadata)
|
840
|
+
|
841
|
+
# 转换压缩变体
|
842
|
+
variants = []
|
843
|
+
for variant in response.variants:
|
844
|
+
variants.append(CompressedVariant(
|
845
|
+
variant_name=variant.variant_name,
|
846
|
+
variant_type=variant.variant_type,
|
847
|
+
media_type=variant.media_type,
|
848
|
+
width=variant.width,
|
849
|
+
height=variant.height,
|
850
|
+
file_size=variant.file_size,
|
851
|
+
format=variant.format,
|
852
|
+
quality=variant.quality if variant.quality else None,
|
853
|
+
duration=variant.duration if variant.duration else None,
|
854
|
+
bitrate=variant.bitrate if variant.bitrate else None,
|
855
|
+
fps=variant.fps if variant.fps else None,
|
856
|
+
compression_ratio=variant.compression_ratio,
|
857
|
+
stored_path=variant.stored_path
|
858
|
+
))
|
859
|
+
|
860
|
+
return GetVariantsResponse(variants=variants)
|
861
|
+
|
862
|
+
async def trigger_recompression(
|
863
|
+
self,
|
864
|
+
file_id: str,
|
865
|
+
*,
|
866
|
+
force_reprocess: bool = False,
|
867
|
+
request_id: Optional[str] = None,
|
868
|
+
**metadata
|
869
|
+
) -> RecompressionResponse:
|
870
|
+
"""
|
871
|
+
触发文件重新压缩
|
872
|
+
|
873
|
+
Args:
|
874
|
+
file_id: 文件ID
|
875
|
+
force_reprocess: 是否强制重新处理
|
876
|
+
request_id: 请求ID,用于追踪
|
877
|
+
**metadata: 额外的gRPC元数据
|
878
|
+
|
879
|
+
Returns:
|
880
|
+
RecompressionResponse: 重新压缩响应
|
881
|
+
"""
|
882
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
883
|
+
|
884
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
885
|
+
|
886
|
+
request = file_service_pb2.RecompressionRequest(
|
887
|
+
file_id=file_id,
|
888
|
+
force_reprocess=force_reprocess
|
889
|
+
)
|
890
|
+
|
891
|
+
# 构建元数据
|
892
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
893
|
+
|
894
|
+
response = await stub.TriggerRecompression(request, metadata=grpc_metadata)
|
895
|
+
|
896
|
+
return RecompressionResponse(
|
897
|
+
task_id=response.task_id,
|
898
|
+
status=response.status
|
899
|
+
)
|
900
|
+
|
901
|
+
async def generate_variant_download_url(
|
902
|
+
self,
|
903
|
+
file_id: str,
|
904
|
+
variant_name: str,
|
905
|
+
*,
|
906
|
+
expire_seconds: int = 3600,
|
907
|
+
is_cdn: bool = False,
|
908
|
+
request_id: Optional[str] = None,
|
909
|
+
**metadata
|
910
|
+
) -> VariantDownloadUrlResponse:
|
911
|
+
"""
|
912
|
+
生成变体下载URL
|
913
|
+
|
914
|
+
Args:
|
915
|
+
file_id: 文件ID
|
916
|
+
variant_name: 变体名称(large/medium/small/thumbnail)
|
917
|
+
expire_seconds: 过期时间(秒)
|
918
|
+
is_cdn: 是否使用CDN
|
919
|
+
request_id: 请求ID,用于追踪
|
920
|
+
**metadata: 额外的gRPC元数据
|
921
|
+
|
922
|
+
Returns:
|
923
|
+
VariantDownloadUrlResponse: 变体下载URL响应
|
924
|
+
"""
|
925
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
926
|
+
|
927
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
928
|
+
|
929
|
+
request = file_service_pb2.VariantDownloadUrlRequest(
|
930
|
+
file_id=file_id,
|
931
|
+
variant_name=variant_name,
|
932
|
+
expire_seconds=expire_seconds,
|
933
|
+
is_cdn=is_cdn
|
934
|
+
)
|
935
|
+
|
936
|
+
# 构建元数据
|
937
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
938
|
+
|
939
|
+
response = await stub.GenerateVariantDownloadUrl(request, metadata=grpc_metadata)
|
940
|
+
|
941
|
+
# 转换变体信息
|
942
|
+
variant_info = None
|
943
|
+
if response.variant_info:
|
944
|
+
variant_info = CompressedVariant(
|
945
|
+
variant_name=response.variant_info.variant_name,
|
946
|
+
variant_type=response.variant_info.variant_type,
|
947
|
+
media_type=response.variant_info.media_type,
|
948
|
+
width=response.variant_info.width,
|
949
|
+
height=response.variant_info.height,
|
950
|
+
file_size=response.variant_info.file_size,
|
951
|
+
format=response.variant_info.format,
|
952
|
+
quality=response.variant_info.quality if response.variant_info.quality else None,
|
953
|
+
duration=response.variant_info.duration if response.variant_info.duration else None,
|
954
|
+
bitrate=response.variant_info.bitrate if response.variant_info.bitrate else None,
|
955
|
+
fps=response.variant_info.fps if response.variant_info.fps else None,
|
956
|
+
compression_ratio=response.variant_info.compression_ratio,
|
957
|
+
stored_path=response.variant_info.stored_path
|
958
|
+
)
|
959
|
+
|
960
|
+
return VariantDownloadUrlResponse(
|
961
|
+
url=response.url,
|
962
|
+
error=response.error if response.error else None,
|
963
|
+
variant_info=variant_info
|
964
|
+
)
|