diff --git a/src/BowongModalFunctions/models/media_model.py b/src/BowongModalFunctions/models/media_model.py index b4407dd..48309a0 100644 --- a/src/BowongModalFunctions/models/media_model.py +++ b/src/BowongModalFunctions/models/media_model.py @@ -11,11 +11,13 @@ from pydantic import (BaseModel, Field, field_validator, ValidationError, from pydantic.json_schema import JsonSchemaValue from ..config import WorkerConfig from ..utils.TimeUtils import TimeDelta +from ..utils.VideoUtils import VideoMetadata config = WorkerConfig() s3_region = config.S3_region s3_bucket_name = config.S3_bucket_name +s3_mount_point = config.S3_mount_dir class MediaProtocol(str, Enum): @@ -45,6 +47,8 @@ class MediaSource(BaseModel): expired_at: Optional[datetime] = Field(description="缓存过期时间点, 为None时不会过期(过期处理WIP)", default=None) downloader_id: Optional[str] = Field(description="正在处理下载的Downloader ID", default=None) progress: int = Field(description="缓存进度", default=0) + metadata: Optional[VideoMetadata] = Field(description="媒体元数据", default=None) + content_length: Optional[int] = Field(description="媒体文件大小", default=None) @classmethod def from_str(cls, media_url: str) -> 'MediaSource': @@ -148,6 +152,18 @@ class MediaSource(BaseModel): def file_extension(self) -> Optional[str]: return os.path.basename(self.urn).split('.')[-1] + @computed_field(description="是否本地可用") + @property + def local_available(self) -> bool: + if self.status == MediaCacheStatus.ready: + return os.path.exists(self.local_mount_path) + return False + + @computed_field(description="本地挂载地址") + @property + def local_mount_path(self) -> str: + return f"{s3_mount_point}/{self.cache_filepath}" + @field_serializer('expired_at') def serialize_datetime(self, value: Optional[datetime], info: SerializationInfo) -> Optional[str]: if value: diff --git a/src/BowongModalFunctions/utils/KVCache.py b/src/BowongModalFunctions/utils/KVCache.py index 3fb0dfa..26c40db 100644 --- a/src/BowongModalFunctions/utils/KVCache.py +++ b/src/BowongModalFunctions/utils/KVCache.py @@ -4,7 +4,10 @@ from typing import Optional, List import httpx import modal from loguru import logger -from ..models.media_model import MediaSource + +from .VideoUtils import VideoMetadata, VideoUtils +from ..models.media_model import MediaSource, MediaProtocol + cf_account_id = os.environ.get("CF_ACCOUNT_ID") cf_kv_api_token = os.environ.get("CF_KV_API_TOKEN") cf_kv_namespace_id = os.environ.get("CF_KV_NAMESPACE_ID") @@ -20,9 +23,22 @@ class KVCache: cache_json = self.kv.get(urn) if not cache_json: return None - return MediaSource.model_validate_json(cache_json) + media = MediaSource.model_validate_json(cache_json) + if media.local_available: + try: + media.metadata = VideoUtils.ffprobe_media_metadata(media.local_mount_path) + media.content_length = os.path.getsize(media.local_mount_path) + except Exception as e: + logger.exception(e) + return media def set_cache(self, media: MediaSource): + if media.local_available: + try: + media.metadata = VideoUtils.ffprobe_media_metadata(media.local_mount_path) + media.content_length = os.path.getsize(media.local_mount_path) + except Exception as e: + logger.exception(e) cache_json = media.model_dump_json() self.kv.put(media.urn, cache_json) diff --git a/src/BowongModalFunctions/utils/SentryUtils.py b/src/BowongModalFunctions/utils/SentryUtils.py index 192d0f9..1e941f8 100644 --- a/src/BowongModalFunctions/utils/SentryUtils.py +++ b/src/BowongModalFunctions/utils/SentryUtils.py @@ -28,7 +28,9 @@ class SentryUtils: logger.info(f"sentry-trace={sentry_trace_id}, baggage={sentry_baggage}") if sentry_trace_id and sentry_baggage: transaction = sentry_sdk.continue_trace(environ_or_headers={"sentry-trace": sentry_trace_id, - "baggage": sentry_baggage, }) + "baggage": sentry_baggage, }, + op=op, + name=name) else: transaction = sentry_sdk.start_transaction(op='modal.function', name='Modal Function直接调用') with transaction: diff --git a/src/BowongModalFunctions/utils/VideoUtils.py b/src/BowongModalFunctions/utils/VideoUtils.py index 1aee8c5..cd60750 100644 --- a/src/BowongModalFunctions/utils/VideoUtils.py +++ b/src/BowongModalFunctions/utils/VideoUtils.py @@ -306,8 +306,7 @@ class VideoUtils: ffmpeg_cmd = VideoUtils.async_ffmpeg_init() ffmpeg_cmd.input(media_path) filter_complex: List[str] = [] - outputs: List[Tuple[str, VideoMetadata]] = [] - + temp_outputs: List[str] = [] if not output_path: output_path = FileUtils.file_path_extend(media_path, "slice") os.makedirs(os.path.dirname(output_path), exist_ok=True) @@ -341,10 +340,11 @@ class VideoUtils: crf=16, r=30, ) - video_metadata = VideoUtils.ffprobe_media_metadata(segment_output_path) - outputs.append((segment_output_path, video_metadata)) + temp_outputs.append(segment_output_path) await ffmpeg_cmd.execute() + outputs: List[Tuple[str, VideoMetadata]] = [(output, VideoUtils.ffprobe_media_metadata(output)) for output in + temp_outputs] return outputs @staticmethod @@ -369,8 +369,8 @@ class VideoUtils: reconnect_streamed="1", reconnect_delay_max="5") filter_complex: List[str] = [] - outputs: List[Tuple[str, VideoMetadata]] = [] + temp_outputs: List[str] = [] if not output_path: output_path = FileUtils.file_path_extend(media_path, "slice") os.makedirs(os.path.dirname(output_path), exist_ok=True) @@ -402,10 +402,10 @@ class VideoUtils: acodec="aac", crf=16, r=30, ) - video_metadata = VideoUtils.ffprobe_media_metadata(output_filepath) - outputs.append((output_filepath, video_metadata)) - + temp_outputs.append(output_filepath) await ffmpeg_cmd.execute() + outputs: List[Tuple[str, VideoMetadata]] = [(output, VideoUtils.ffprobe_media_metadata(output)) for output in + temp_outputs] return outputs @staticmethod