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