tamar-file-hub-client 0.1.2__py3-none-any.whl → 0.1.4__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 +70 -52
- file_hub_client/rpc/gen/file_service_pb2_grpc.py +173 -0
- file_hub_client/rpc/protos/file_service.proto +68 -0
- 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 +44 -0
- file_hub_client/services/file/async_blob_service.py +278 -8
- file_hub_client/services/file/async_file_service.py +217 -0
- file_hub_client/services/file/sync_blob_service.py +279 -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
- {tamar_file_hub_client-0.1.2.dist-info → tamar_file_hub_client-0.1.4.dist-info}/METADATA +187 -1
- {tamar_file_hub_client-0.1.2.dist-info → tamar_file_hub_client-0.1.4.dist-info}/RECORD +20 -19
- {tamar_file_hub_client-0.1.2.dist-info → tamar_file_hub_client-0.1.4.dist-info}/WHEEL +0 -0
- {tamar_file_hub_client-0.1.2.dist-info → tamar_file_hub_client-0.1.4.dist-info}/top_level.txt +0 -0
@@ -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
|
|
@@ -39,6 +39,7 @@ class AsyncBlobService(BaseFileService):
|
|
39
39
|
mime_type: Optional[str] = None,
|
40
40
|
is_temporary: Optional[bool] = False,
|
41
41
|
expire_seconds: Optional[int] = None,
|
42
|
+
keep_original_filename: Optional[bool] = False,
|
42
43
|
request_id: Optional[str] = None,
|
43
44
|
**metadata
|
44
45
|
) -> FileUploadResponse:
|
@@ -53,6 +54,7 @@ class AsyncBlobService(BaseFileService):
|
|
53
54
|
mime_type: MIME类型
|
54
55
|
is_temporary: 是否为临时文件
|
55
56
|
expire_seconds: 过期秒数
|
57
|
+
keep_original_filename: 是否保留原始文件名(默认False)
|
56
58
|
request_id: 请求ID(可选,如果不提供则自动生成)
|
57
59
|
**metadata: 额外的元数据(如 x-org-id, x-user-id 等)
|
58
60
|
|
@@ -86,6 +88,7 @@ class AsyncBlobService(BaseFileService):
|
|
86
88
|
mime_type=mime_type or "application/octet-stream",
|
87
89
|
is_temporary=is_temporary,
|
88
90
|
expire_seconds=expire_seconds if is_temporary and expire_seconds else None,
|
91
|
+
keep_original_filename=keep_original_filename,
|
89
92
|
)
|
90
93
|
|
91
94
|
if folder_id:
|
@@ -114,10 +117,11 @@ class AsyncBlobService(BaseFileService):
|
|
114
117
|
file_hash: str,
|
115
118
|
is_temporary: Optional[bool] = False,
|
116
119
|
expire_seconds: Optional[int] = None,
|
120
|
+
keep_original_filename: Optional[bool] = False,
|
117
121
|
request_id: Optional[str] = None,
|
118
122
|
**metadata
|
119
123
|
) -> FileUploadResponse:
|
120
|
-
"""
|
124
|
+
"""流式上传(GCS 直传)"""
|
121
125
|
|
122
126
|
# 获取上传URL,以及对应的文件和上传文件信息
|
123
127
|
upload_url_resp = await self.generate_upload_url(
|
@@ -129,6 +133,7 @@ class AsyncBlobService(BaseFileService):
|
|
129
133
|
file_hash=file_hash,
|
130
134
|
is_temporary=is_temporary,
|
131
135
|
expire_seconds=expire_seconds if is_temporary and expire_seconds else None,
|
136
|
+
keep_original_filename=keep_original_filename,
|
132
137
|
request_id=request_id,
|
133
138
|
**metadata
|
134
139
|
)
|
@@ -140,11 +145,17 @@ class AsyncBlobService(BaseFileService):
|
|
140
145
|
upload_file=upload_url_resp.upload_file
|
141
146
|
)
|
142
147
|
|
148
|
+
# 构建HTTP头,包含Content-Type和固定的Cache-Control
|
149
|
+
headers = {
|
150
|
+
"Content-Type": mime_type,
|
151
|
+
"Cache-Control": "public, max-age=86400" # 24小时公共缓存
|
152
|
+
}
|
153
|
+
|
143
154
|
# 上传文件到对象存储
|
144
155
|
await self.http_uploader.upload(
|
145
156
|
url=upload_url_resp.upload_url,
|
146
157
|
content=content,
|
147
|
-
headers=
|
158
|
+
headers=headers,
|
148
159
|
total_size=file_size,
|
149
160
|
)
|
150
161
|
|
@@ -172,10 +183,12 @@ class AsyncBlobService(BaseFileService):
|
|
172
183
|
file_hash: str,
|
173
184
|
is_temporary: Optional[bool] = False,
|
174
185
|
expire_seconds: Optional[int] = None,
|
186
|
+
keep_original_filename: Optional[bool] = False,
|
175
187
|
request_id: Optional[str] = None,
|
176
188
|
**metadata
|
177
189
|
) -> FileUploadResponse:
|
178
|
-
"""
|
190
|
+
"""断点续传实现(GCS 直传)"""
|
191
|
+
|
179
192
|
# 获取断点续传URL,以及对应的文件和上传文件信息
|
180
193
|
upload_url_resp = await self.generate_resumable_upload_url(
|
181
194
|
file_name=file_name,
|
@@ -186,6 +199,7 @@ class AsyncBlobService(BaseFileService):
|
|
186
199
|
file_hash=file_hash,
|
187
200
|
is_temporary=is_temporary,
|
188
201
|
expire_seconds=expire_seconds if is_temporary and expire_seconds else None,
|
202
|
+
keep_original_filename=keep_original_filename,
|
189
203
|
request_id=request_id,
|
190
204
|
**metadata
|
191
205
|
)
|
@@ -197,6 +211,12 @@ class AsyncBlobService(BaseFileService):
|
|
197
211
|
upload_file=upload_url_resp.upload_file
|
198
212
|
)
|
199
213
|
|
214
|
+
# 构建HTTP头,包含Content-Type和固定的Cache-Control
|
215
|
+
headers = {
|
216
|
+
"Content-Type": mime_type,
|
217
|
+
"Cache-Control": "public, max-age=86400" # 24小时公共缓存
|
218
|
+
}
|
219
|
+
|
200
220
|
# 开启断点续传
|
201
221
|
upload_url = await self.http_uploader.start_resumable_session(
|
202
222
|
url=upload_url_resp.upload_url,
|
@@ -208,7 +228,7 @@ class AsyncBlobService(BaseFileService):
|
|
208
228
|
await self.http_uploader.upload(
|
209
229
|
url=upload_url,
|
210
230
|
content=content,
|
211
|
-
headers=
|
231
|
+
headers=headers,
|
212
232
|
total_size=file_size,
|
213
233
|
)
|
214
234
|
|
@@ -256,6 +276,7 @@ class AsyncBlobService(BaseFileService):
|
|
256
276
|
file_hash: Optional[str] = None,
|
257
277
|
is_temporary: Optional[bool] = False,
|
258
278
|
expire_seconds: Optional[int] = None,
|
279
|
+
keep_original_filename: Optional[bool] = False,
|
259
280
|
request_id: Optional[str] = None,
|
260
281
|
**metadata
|
261
282
|
) -> UploadUrlResponse:
|
@@ -271,6 +292,8 @@ class AsyncBlobService(BaseFileService):
|
|
271
292
|
file_hash: 文件哈希
|
272
293
|
is_temporary: 是否为临时文件
|
273
294
|
expire_seconds: 过期秒数
|
295
|
+
keep_original_filename: 是否保留原始文件名(默认False)
|
296
|
+
request_id: 请求ID(可选,如果不提供则自动生成)
|
274
297
|
**metadata: 额外的元数据(如 x-org-id, x-user-id 等)
|
275
298
|
|
276
299
|
Returns:
|
@@ -288,6 +311,7 @@ class AsyncBlobService(BaseFileService):
|
|
288
311
|
file_hash=file_hash,
|
289
312
|
is_temporary=is_temporary,
|
290
313
|
expire_seconds=expire_seconds if is_temporary and expire_seconds else None,
|
314
|
+
keep_original_filename=keep_original_filename,
|
291
315
|
)
|
292
316
|
|
293
317
|
if folder_id:
|
@@ -314,6 +338,7 @@ class AsyncBlobService(BaseFileService):
|
|
314
338
|
file_hash: str = None,
|
315
339
|
is_temporary: Optional[bool] = False,
|
316
340
|
expire_seconds: Optional[int] = None,
|
341
|
+
keep_original_filename: Optional[bool] = False,
|
317
342
|
request_id: Optional[str] = None,
|
318
343
|
**metadata
|
319
344
|
) -> UploadUrlResponse:
|
@@ -329,6 +354,7 @@ class AsyncBlobService(BaseFileService):
|
|
329
354
|
file_hash: 文件哈希
|
330
355
|
is_temporary: 是否为临时文件
|
331
356
|
expire_seconds: 过期秒数
|
357
|
+
keep_original_filename: 是否保留原始文件名(默认False)
|
332
358
|
request_id: 请求ID(可选,如果不提供则自动生成)
|
333
359
|
**metadata: 额外的元数据(如 x-org-id, x-user-id 等)
|
334
360
|
|
@@ -349,6 +375,7 @@ class AsyncBlobService(BaseFileService):
|
|
349
375
|
file_hash=file_hash,
|
350
376
|
is_temporary=is_temporary,
|
351
377
|
expire_seconds=expire_seconds if is_temporary and expire_seconds else None,
|
378
|
+
keep_original_filename=keep_original_filename,
|
352
379
|
)
|
353
380
|
|
354
381
|
if folder_id:
|
@@ -373,6 +400,7 @@ class AsyncBlobService(BaseFileService):
|
|
373
400
|
mode: Optional[UploadMode] = UploadMode.NORMAL,
|
374
401
|
is_temporary: Optional[bool] = False,
|
375
402
|
expire_seconds: Optional[int] = None,
|
403
|
+
keep_original_filename: Optional[bool] = False,
|
376
404
|
url: Optional[str] = None,
|
377
405
|
file_name: Optional[str] = None,
|
378
406
|
request_id: Optional[str] = None,
|
@@ -387,6 +415,7 @@ class AsyncBlobService(BaseFileService):
|
|
387
415
|
mode: 上传模式(NORMAL/DIRECT/RESUMABLE/STREAM)
|
388
416
|
is_temporary: 是否为临时文件
|
389
417
|
expire_seconds: 过期秒数
|
418
|
+
keep_original_filename: 是否保留原始文件名(默认False)
|
390
419
|
url: 要下载并上传的URL(可选)
|
391
420
|
file_name: 当使用url参数时指定的文件名(可选)
|
392
421
|
request_id: 请求ID(可选,如果不提供则自动生成)
|
@@ -397,6 +426,8 @@ class AsyncBlobService(BaseFileService):
|
|
397
426
|
|
398
427
|
Note:
|
399
428
|
必须提供 file 或 url 参数之一
|
429
|
+
|
430
|
+
Cache-Control 头在 GCS 直传模式(STREAM/RESUMABLE)下自动设置为 "public, max-age=86400"
|
400
431
|
"""
|
401
432
|
# 参数验证:必须提供 file 或 url 之一
|
402
433
|
if file is None and not url:
|
@@ -452,7 +483,7 @@ class AsyncBlobService(BaseFileService):
|
|
452
483
|
|
453
484
|
# 根据上传模式执行不同的上传逻辑
|
454
485
|
if mode == UploadMode.NORMAL:
|
455
|
-
# 普通上传(通过gRPC
|
486
|
+
# 普通上传(通过gRPC)- 不需要 Cache-Control
|
456
487
|
return await self._upload_file(
|
457
488
|
file_name=extracted_file_name,
|
458
489
|
content=content,
|
@@ -461,12 +492,13 @@ class AsyncBlobService(BaseFileService):
|
|
461
492
|
mime_type=mime_type,
|
462
493
|
is_temporary=is_temporary,
|
463
494
|
expire_seconds=expire_seconds,
|
495
|
+
keep_original_filename=keep_original_filename,
|
464
496
|
request_id=request_id,
|
465
497
|
**metadata
|
466
498
|
)
|
467
499
|
|
468
500
|
elif mode == UploadMode.STREAM:
|
469
|
-
#
|
501
|
+
# 流式上传(目前使用直传实现)- 需要 Cache-Control
|
470
502
|
return await self._upload_stream(
|
471
503
|
file_name=extracted_file_name,
|
472
504
|
content=content,
|
@@ -477,12 +509,13 @@ class AsyncBlobService(BaseFileService):
|
|
477
509
|
file_hash=file_hash,
|
478
510
|
is_temporary=is_temporary,
|
479
511
|
expire_seconds=expire_seconds,
|
512
|
+
keep_original_filename=keep_original_filename,
|
480
513
|
request_id=request_id,
|
481
514
|
**metadata
|
482
515
|
)
|
483
516
|
|
484
517
|
elif mode == UploadMode.RESUMABLE:
|
485
|
-
# 断点续传
|
518
|
+
# 断点续传 - 需要 Cache-Control
|
486
519
|
return await self._upload_resumable(
|
487
520
|
file_name=extracted_file_name,
|
488
521
|
content=content,
|
@@ -493,6 +526,7 @@ class AsyncBlobService(BaseFileService):
|
|
493
526
|
file_hash=file_hash,
|
494
527
|
is_temporary=is_temporary,
|
495
528
|
expire_seconds=expire_seconds,
|
529
|
+
keep_original_filename=keep_original_filename,
|
496
530
|
request_id=request_id,
|
497
531
|
**metadata
|
498
532
|
)
|
@@ -569,6 +603,30 @@ class AsyncBlobService(BaseFileService):
|
|
569
603
|
chunk_size=chunk_size,
|
570
604
|
)
|
571
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
|
+
|
572
630
|
async def batch_generate_download_url(
|
573
631
|
self,
|
574
632
|
file_ids: List[str],
|
@@ -691,3 +749,215 @@ class AsyncBlobService(BaseFileService):
|
|
691
749
|
))
|
692
750
|
|
693
751
|
return BatchGcsUrlResponse(gcs_urls=gcs_urls)
|
752
|
+
|
753
|
+
async def get_compression_status(
|
754
|
+
self,
|
755
|
+
file_id: str,
|
756
|
+
*,
|
757
|
+
request_id: Optional[str] = None,
|
758
|
+
**metadata
|
759
|
+
) -> CompressionStatusResponse:
|
760
|
+
"""
|
761
|
+
获取文件压缩状态
|
762
|
+
|
763
|
+
Args:
|
764
|
+
file_id: 文件ID
|
765
|
+
request_id: 请求ID,用于追踪
|
766
|
+
**metadata: 额外的gRPC元数据
|
767
|
+
|
768
|
+
Returns:
|
769
|
+
CompressionStatusResponse: 压缩状态响应
|
770
|
+
"""
|
771
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
772
|
+
|
773
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
774
|
+
|
775
|
+
request = file_service_pb2.CompressionStatusRequest(file_id=file_id)
|
776
|
+
|
777
|
+
# 构建元数据
|
778
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
779
|
+
|
780
|
+
response = await stub.GetCompressionStatus(request, metadata=grpc_metadata)
|
781
|
+
|
782
|
+
# 转换压缩变体
|
783
|
+
variants = []
|
784
|
+
for variant in response.variants:
|
785
|
+
variants.append(CompressedVariant(
|
786
|
+
variant_name=variant.variant_name,
|
787
|
+
variant_type=variant.variant_type,
|
788
|
+
media_type=variant.media_type,
|
789
|
+
width=variant.width,
|
790
|
+
height=variant.height,
|
791
|
+
file_size=variant.file_size,
|
792
|
+
format=variant.format,
|
793
|
+
quality=variant.quality if variant.quality else None,
|
794
|
+
duration=variant.duration if variant.duration else None,
|
795
|
+
bitrate=variant.bitrate if variant.bitrate else None,
|
796
|
+
fps=variant.fps if variant.fps else None,
|
797
|
+
compression_ratio=variant.compression_ratio,
|
798
|
+
stored_path=variant.stored_path
|
799
|
+
))
|
800
|
+
|
801
|
+
return CompressionStatusResponse(
|
802
|
+
status=response.status,
|
803
|
+
error_message=response.error_message if response.error_message else None,
|
804
|
+
variants=variants
|
805
|
+
)
|
806
|
+
|
807
|
+
async def get_compressed_variants(
|
808
|
+
self,
|
809
|
+
file_id: str,
|
810
|
+
*,
|
811
|
+
variant_type: Optional[str] = None,
|
812
|
+
request_id: Optional[str] = None,
|
813
|
+
**metadata
|
814
|
+
) -> GetVariantsResponse:
|
815
|
+
"""
|
816
|
+
获取文件的压缩变体
|
817
|
+
|
818
|
+
Args:
|
819
|
+
file_id: 文件ID
|
820
|
+
variant_type: 变体类型(image, video, thumbnail)
|
821
|
+
request_id: 请求ID,用于追踪
|
822
|
+
**metadata: 额外的gRPC元数据
|
823
|
+
|
824
|
+
Returns:
|
825
|
+
GetVariantsResponse: 压缩变体响应
|
826
|
+
"""
|
827
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
828
|
+
|
829
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
830
|
+
|
831
|
+
request = file_service_pb2.GetVariantsRequest(file_id=file_id)
|
832
|
+
if variant_type:
|
833
|
+
request.variant_type = variant_type
|
834
|
+
|
835
|
+
# 构建元数据
|
836
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
837
|
+
|
838
|
+
response = await stub.GetCompressedVariants(request, metadata=grpc_metadata)
|
839
|
+
|
840
|
+
# 转换压缩变体
|
841
|
+
variants = []
|
842
|
+
for variant in response.variants:
|
843
|
+
variants.append(CompressedVariant(
|
844
|
+
variant_name=variant.variant_name,
|
845
|
+
variant_type=variant.variant_type,
|
846
|
+
media_type=variant.media_type,
|
847
|
+
width=variant.width,
|
848
|
+
height=variant.height,
|
849
|
+
file_size=variant.file_size,
|
850
|
+
format=variant.format,
|
851
|
+
quality=variant.quality if variant.quality else None,
|
852
|
+
duration=variant.duration if variant.duration else None,
|
853
|
+
bitrate=variant.bitrate if variant.bitrate else None,
|
854
|
+
fps=variant.fps if variant.fps else None,
|
855
|
+
compression_ratio=variant.compression_ratio,
|
856
|
+
stored_path=variant.stored_path
|
857
|
+
))
|
858
|
+
|
859
|
+
return GetVariantsResponse(variants=variants)
|
860
|
+
|
861
|
+
async def trigger_recompression(
|
862
|
+
self,
|
863
|
+
file_id: str,
|
864
|
+
*,
|
865
|
+
force_reprocess: bool = False,
|
866
|
+
request_id: Optional[str] = None,
|
867
|
+
**metadata
|
868
|
+
) -> RecompressionResponse:
|
869
|
+
"""
|
870
|
+
触发文件重新压缩
|
871
|
+
|
872
|
+
Args:
|
873
|
+
file_id: 文件ID
|
874
|
+
force_reprocess: 是否强制重新处理
|
875
|
+
request_id: 请求ID,用于追踪
|
876
|
+
**metadata: 额外的gRPC元数据
|
877
|
+
|
878
|
+
Returns:
|
879
|
+
RecompressionResponse: 重新压缩响应
|
880
|
+
"""
|
881
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
882
|
+
|
883
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
884
|
+
|
885
|
+
request = file_service_pb2.RecompressionRequest(
|
886
|
+
file_id=file_id,
|
887
|
+
force_reprocess=force_reprocess
|
888
|
+
)
|
889
|
+
|
890
|
+
# 构建元数据
|
891
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
892
|
+
|
893
|
+
response = await stub.TriggerRecompression(request, metadata=grpc_metadata)
|
894
|
+
|
895
|
+
return RecompressionResponse(
|
896
|
+
task_id=response.task_id,
|
897
|
+
status=response.status
|
898
|
+
)
|
899
|
+
|
900
|
+
async def generate_variant_download_url(
|
901
|
+
self,
|
902
|
+
file_id: str,
|
903
|
+
variant_name: str,
|
904
|
+
*,
|
905
|
+
expire_seconds: int = 3600,
|
906
|
+
is_cdn: bool = False,
|
907
|
+
request_id: Optional[str] = None,
|
908
|
+
**metadata
|
909
|
+
) -> VariantDownloadUrlResponse:
|
910
|
+
"""
|
911
|
+
生成变体下载URL
|
912
|
+
|
913
|
+
Args:
|
914
|
+
file_id: 文件ID
|
915
|
+
variant_name: 变体名称(large/medium/small/thumbnail)
|
916
|
+
expire_seconds: 过期时间(秒)
|
917
|
+
is_cdn: 是否使用CDN
|
918
|
+
request_id: 请求ID,用于追踪
|
919
|
+
**metadata: 额外的gRPC元数据
|
920
|
+
|
921
|
+
Returns:
|
922
|
+
VariantDownloadUrlResponse: 变体下载URL响应
|
923
|
+
"""
|
924
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
925
|
+
|
926
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
927
|
+
|
928
|
+
request = file_service_pb2.VariantDownloadUrlRequest(
|
929
|
+
file_id=file_id,
|
930
|
+
variant_name=variant_name,
|
931
|
+
expire_seconds=expire_seconds,
|
932
|
+
is_cdn=is_cdn
|
933
|
+
)
|
934
|
+
|
935
|
+
# 构建元数据
|
936
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
937
|
+
|
938
|
+
response = await stub.GenerateVariantDownloadUrl(request, metadata=grpc_metadata)
|
939
|
+
|
940
|
+
# 转换变体信息
|
941
|
+
variant_info = None
|
942
|
+
if response.variant_info:
|
943
|
+
variant_info = CompressedVariant(
|
944
|
+
variant_name=response.variant_info.variant_name,
|
945
|
+
variant_type=response.variant_info.variant_type,
|
946
|
+
media_type=response.variant_info.media_type,
|
947
|
+
width=response.variant_info.width,
|
948
|
+
height=response.variant_info.height,
|
949
|
+
file_size=response.variant_info.file_size,
|
950
|
+
format=response.variant_info.format,
|
951
|
+
quality=response.variant_info.quality if response.variant_info.quality else None,
|
952
|
+
duration=response.variant_info.duration if response.variant_info.duration else None,
|
953
|
+
bitrate=response.variant_info.bitrate if response.variant_info.bitrate else None,
|
954
|
+
fps=response.variant_info.fps if response.variant_info.fps else None,
|
955
|
+
compression_ratio=response.variant_info.compression_ratio,
|
956
|
+
stored_path=response.variant_info.stored_path
|
957
|
+
)
|
958
|
+
|
959
|
+
return VariantDownloadUrlResponse(
|
960
|
+
url=response.url,
|
961
|
+
error=response.error if response.error else None,
|
962
|
+
variant_info=variant_info
|
963
|
+
)
|
@@ -12,6 +12,11 @@ from ...schemas import (
|
|
12
12
|
File,
|
13
13
|
FileListResponse,
|
14
14
|
GetFileResponse,
|
15
|
+
CompressionStatusResponse,
|
16
|
+
GetVariantsResponse,
|
17
|
+
RecompressionResponse,
|
18
|
+
VariantDownloadUrlResponse,
|
19
|
+
CompressedVariant,
|
15
20
|
)
|
16
21
|
from ...errors import FileNotFoundError
|
17
22
|
|
@@ -275,3 +280,215 @@ class AsyncFileService(BaseFileService):
|
|
275
280
|
files = [self._convert_file_info(f) for f in response.files]
|
276
281
|
|
277
282
|
return FileListResponse(files=files)
|
283
|
+
|
284
|
+
async def get_compression_status(
|
285
|
+
self,
|
286
|
+
file_id: str,
|
287
|
+
*,
|
288
|
+
request_id: Optional[str] = None,
|
289
|
+
**metadata
|
290
|
+
) -> CompressionStatusResponse:
|
291
|
+
"""
|
292
|
+
获取文件压缩状态
|
293
|
+
|
294
|
+
Args:
|
295
|
+
file_id: 文件ID
|
296
|
+
request_id: 请求ID,用于追踪
|
297
|
+
**metadata: 额外的gRPC元数据
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
CompressionStatusResponse: 压缩状态响应
|
301
|
+
"""
|
302
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
303
|
+
|
304
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
305
|
+
|
306
|
+
request = file_service_pb2.CompressionStatusRequest(file_id=file_id)
|
307
|
+
|
308
|
+
# 构建元数据
|
309
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
310
|
+
|
311
|
+
response = await stub.GetCompressionStatus(request, metadata=grpc_metadata)
|
312
|
+
|
313
|
+
# 转换压缩变体
|
314
|
+
variants = []
|
315
|
+
for variant in response.variants:
|
316
|
+
variants.append(CompressedVariant(
|
317
|
+
variant_name=variant.variant_name,
|
318
|
+
variant_type=variant.variant_type,
|
319
|
+
media_type=variant.media_type,
|
320
|
+
width=variant.width,
|
321
|
+
height=variant.height,
|
322
|
+
file_size=variant.file_size,
|
323
|
+
format=variant.format,
|
324
|
+
quality=variant.quality if variant.quality else None,
|
325
|
+
duration=variant.duration if variant.duration else None,
|
326
|
+
bitrate=variant.bitrate if variant.bitrate else None,
|
327
|
+
fps=variant.fps if variant.fps else None,
|
328
|
+
compression_ratio=variant.compression_ratio,
|
329
|
+
stored_path=variant.stored_path
|
330
|
+
))
|
331
|
+
|
332
|
+
return CompressionStatusResponse(
|
333
|
+
status=response.status,
|
334
|
+
error_message=response.error_message if response.error_message else None,
|
335
|
+
variants=variants
|
336
|
+
)
|
337
|
+
|
338
|
+
async def get_compressed_variants(
|
339
|
+
self,
|
340
|
+
file_id: str,
|
341
|
+
*,
|
342
|
+
variant_type: Optional[str] = None,
|
343
|
+
request_id: Optional[str] = None,
|
344
|
+
**metadata
|
345
|
+
) -> GetVariantsResponse:
|
346
|
+
"""
|
347
|
+
获取文件的压缩变体
|
348
|
+
|
349
|
+
Args:
|
350
|
+
file_id: 文件ID
|
351
|
+
variant_type: 变体类型(image, video, thumbnail)
|
352
|
+
request_id: 请求ID,用于追踪
|
353
|
+
**metadata: 额外的gRPC元数据
|
354
|
+
|
355
|
+
Returns:
|
356
|
+
GetVariantsResponse: 压缩变体响应
|
357
|
+
"""
|
358
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
359
|
+
|
360
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
361
|
+
|
362
|
+
request = file_service_pb2.GetVariantsRequest(file_id=file_id)
|
363
|
+
if variant_type:
|
364
|
+
request.variant_type = variant_type
|
365
|
+
|
366
|
+
# 构建元数据
|
367
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
368
|
+
|
369
|
+
response = await stub.GetCompressedVariants(request, metadata=grpc_metadata)
|
370
|
+
|
371
|
+
# 转换压缩变体
|
372
|
+
variants = []
|
373
|
+
for variant in response.variants:
|
374
|
+
variants.append(CompressedVariant(
|
375
|
+
variant_name=variant.variant_name,
|
376
|
+
variant_type=variant.variant_type,
|
377
|
+
media_type=variant.media_type,
|
378
|
+
width=variant.width,
|
379
|
+
height=variant.height,
|
380
|
+
file_size=variant.file_size,
|
381
|
+
format=variant.format,
|
382
|
+
quality=variant.quality if variant.quality else None,
|
383
|
+
duration=variant.duration if variant.duration else None,
|
384
|
+
bitrate=variant.bitrate if variant.bitrate else None,
|
385
|
+
fps=variant.fps if variant.fps else None,
|
386
|
+
compression_ratio=variant.compression_ratio,
|
387
|
+
stored_path=variant.stored_path
|
388
|
+
))
|
389
|
+
|
390
|
+
return GetVariantsResponse(variants=variants)
|
391
|
+
|
392
|
+
async def trigger_recompression(
|
393
|
+
self,
|
394
|
+
file_id: str,
|
395
|
+
*,
|
396
|
+
force_reprocess: bool = False,
|
397
|
+
request_id: Optional[str] = None,
|
398
|
+
**metadata
|
399
|
+
) -> RecompressionResponse:
|
400
|
+
"""
|
401
|
+
触发文件重新压缩
|
402
|
+
|
403
|
+
Args:
|
404
|
+
file_id: 文件ID
|
405
|
+
force_reprocess: 是否强制重新处理
|
406
|
+
request_id: 请求ID,用于追踪
|
407
|
+
**metadata: 额外的gRPC元数据
|
408
|
+
|
409
|
+
Returns:
|
410
|
+
RecompressionResponse: 重新压缩响应
|
411
|
+
"""
|
412
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
413
|
+
|
414
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
415
|
+
|
416
|
+
request = file_service_pb2.RecompressionRequest(
|
417
|
+
file_id=file_id,
|
418
|
+
force_reprocess=force_reprocess
|
419
|
+
)
|
420
|
+
|
421
|
+
# 构建元数据
|
422
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
423
|
+
|
424
|
+
response = await stub.TriggerRecompression(request, metadata=grpc_metadata)
|
425
|
+
|
426
|
+
return RecompressionResponse(
|
427
|
+
task_id=response.task_id,
|
428
|
+
status=response.status
|
429
|
+
)
|
430
|
+
|
431
|
+
async def generate_variant_download_url(
|
432
|
+
self,
|
433
|
+
file_id: str,
|
434
|
+
variant_name: str,
|
435
|
+
*,
|
436
|
+
expire_seconds: int = 3600,
|
437
|
+
is_cdn: bool = False,
|
438
|
+
request_id: Optional[str] = None,
|
439
|
+
**metadata
|
440
|
+
) -> VariantDownloadUrlResponse:
|
441
|
+
"""
|
442
|
+
生成变体下载URL
|
443
|
+
|
444
|
+
Args:
|
445
|
+
file_id: 文件ID
|
446
|
+
variant_name: 变体名称(large/medium/small/thumbnail)
|
447
|
+
expire_seconds: 过期时间(秒)
|
448
|
+
is_cdn: 是否使用CDN
|
449
|
+
request_id: 请求ID,用于追踪
|
450
|
+
**metadata: 额外的gRPC元数据
|
451
|
+
|
452
|
+
Returns:
|
453
|
+
VariantDownloadUrlResponse: 变体下载URL响应
|
454
|
+
"""
|
455
|
+
from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
|
456
|
+
|
457
|
+
stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
|
458
|
+
|
459
|
+
request = file_service_pb2.VariantDownloadUrlRequest(
|
460
|
+
file_id=file_id,
|
461
|
+
variant_name=variant_name,
|
462
|
+
expire_seconds=expire_seconds,
|
463
|
+
is_cdn=is_cdn
|
464
|
+
)
|
465
|
+
|
466
|
+
# 构建元数据
|
467
|
+
grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
|
468
|
+
|
469
|
+
response = await stub.GenerateVariantDownloadUrl(request, metadata=grpc_metadata)
|
470
|
+
|
471
|
+
# 转换变体信息
|
472
|
+
variant_info = None
|
473
|
+
if response.variant_info:
|
474
|
+
variant_info = CompressedVariant(
|
475
|
+
variant_name=response.variant_info.variant_name,
|
476
|
+
variant_type=response.variant_info.variant_type,
|
477
|
+
media_type=response.variant_info.media_type,
|
478
|
+
width=response.variant_info.width,
|
479
|
+
height=response.variant_info.height,
|
480
|
+
file_size=response.variant_info.file_size,
|
481
|
+
format=response.variant_info.format,
|
482
|
+
quality=response.variant_info.quality if response.variant_info.quality else None,
|
483
|
+
duration=response.variant_info.duration if response.variant_info.duration else None,
|
484
|
+
bitrate=response.variant_info.bitrate if response.variant_info.bitrate else None,
|
485
|
+
fps=response.variant_info.fps if response.variant_info.fps else None,
|
486
|
+
compression_ratio=response.variant_info.compression_ratio,
|
487
|
+
stored_path=response.variant_info.stored_path
|
488
|
+
)
|
489
|
+
|
490
|
+
return VariantDownloadUrlResponse(
|
491
|
+
url=response.url,
|
492
|
+
error=response.error if response.error else None,
|
493
|
+
variant_info=variant_info
|
494
|
+
)
|