From 1920b4bd9d19e569a2a662fa0dbf1fa1d0a3221c Mon Sep 17 00:00:00 2001 From: "shuohigh@gmail.com" Date: Wed, 18 Jun 2025 16:57:15 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix=20:=20=E4=BF=AE=E5=A4=8Dhls=E7=9B=B4?= =?UTF-8?q?=E6=92=AD=E8=BD=ACmp4=E7=BC=BA=E5=B0=91=E7=9A=84=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BowongModalFunctions/router/google.py | 17 +++++++++++++++- src/BowongModalFunctions/utils/HTTPUtils.py | 6 +++++- src/BowongModalFunctions/utils/VideoUtils.py | 21 +++++++++----------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/BowongModalFunctions/router/google.py b/src/BowongModalFunctions/router/google.py index 7569cdb..de37ece 100644 --- a/src/BowongModalFunctions/router/google.py +++ b/src/BowongModalFunctions/router/google.py @@ -9,6 +9,7 @@ from loguru import logger import httpx from fastapi import APIRouter, UploadFile, Header, HTTPException, Depends from pydantic import BaseModel, Field +from pydantic import computed_field from starlette import status from starlette.responses import JSONResponse @@ -34,8 +35,18 @@ class BundleHeaders(BaseModel): x_baggage: str = Field(description="Sentry Transaction baggage", default=None) +class GoogleAuthHeaders(BaseModel): + Authorization: str = Field(description="Google Auth Bearer Token") + + @computed_field(description="Google Auth Token") + @property + def auth_token(self) -> str: + return self.Authorization[len("Bearer "):] + + @router.post("/upload", - summary="上传文件到Google File", + summary="上传文件到Google AI Studio提供的File API, 将被VertexAI新规范替代,统一使用Google CloudStorage存储", + deprecated=True, description="上传文件到Google File, 换取Google File URI, 不同Google API Key之间的URI不互通, 最多可为每个项目存储 20 GB 的文件,每个文件的大小上限为 2 GB。文件会存储 48 小时") async def upload_file_multipart(file: UploadFile, headers: Annotated[GoogleAPIKeyHeaders, Header()]): @@ -80,6 +91,10 @@ async def upload_file_multipart(file: UploadFile, return JSONResponse(content=upload_response.json(), status_code=upload_response.status_code) +@router.post("/vertex-ai/upload", summary="上传文件到Vertex AI可访问的Google cloud storage", ) +async def upload_file(file: UploadFile, headers: Annotated[GoogleAuthHeaders, Header()]): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + @router.get("/status", summary="获取已上传文件的处理状态") async def uploaded_file_status(filename: str, headers: Annotated[GoogleAPIKeyHeaders, Header()]): diff --git a/src/BowongModalFunctions/utils/HTTPUtils.py b/src/BowongModalFunctions/utils/HTTPUtils.py index e885c56..efa41ba 100644 --- a/src/BowongModalFunctions/utils/HTTPUtils.py +++ b/src/BowongModalFunctions/utils/HTTPUtils.py @@ -1,4 +1,4 @@ -from typing import Union, Dict, Any +from typing import Union, Dict, Any, BinaryIO import backoff import httpx @@ -126,3 +126,7 @@ class GoogleAuthUtils: data=params) response.raise_for_status() return GoogleAuthUtils.GoogleAuthResponse.model_validate_json(response.text) + + # @staticmethod + # async def google_upload_file(file_stream: BinaryIO, google_api_key: str, bucket_name: str): + # diff --git a/src/BowongModalFunctions/utils/VideoUtils.py b/src/BowongModalFunctions/utils/VideoUtils.py index df63ca8..1860193 100644 --- a/src/BowongModalFunctions/utils/VideoUtils.py +++ b/src/BowongModalFunctions/utils/VideoUtils.py @@ -455,7 +455,7 @@ class VideoUtils: duration = seek_tail - seek_head logger.info(f"Only using {seek_head}s --> {seek_tail}s = {duration}s") local_m3u8_path, temp_dir, diff = await VideoUtils.convert_m3u8_to_local_source(media_path, head=seek_head, - tail=seek_tail) + tail=seek_tail) logger.info(f"local_playlist: {local_m3u8_path}") for segment in media_markers: @@ -557,13 +557,17 @@ class VideoUtils: @staticmethod async def convert_m3u8_to_local_source(media_stream_url: str, - head: Optional[float] = None, - tail: Optional[float] = None) -> tuple[str, str, TimeDelta]: + head: float = 0, + tail: float = 86400, # 使用24H时长替代♾️ + temp_dir: str = None) -> tuple[str, str, TimeDelta]: """ 转换m3u8为本地来源 """ # 创建临时目录存储TS片段 - temp_dir = tempfile.mkdtemp() + if temp_dir: + os.makedirs(temp_dir, exist_ok=True) + else: + temp_dir = tempfile.mkdtemp() from m3u8 import SegmentList, Segment try: # 1. 下载m3u8文件 @@ -578,10 +582,6 @@ class VideoUtils: max_head = origin_time + timedelta(seconds=tail) logger.info(f"min: {min_head}, max: {max_head}") for segment in playlist.segments: - if not head: - head = 0 - if not tail: - tail = 86400 # 使用24H时长替代♾️ if min_head - timedelta(seconds=segment.duration) <= segment.current_program_date_time <= max_head: logger.info(f"duration: {segment.duration}, head: {segment.current_program_date_time}") duration += segment.duration @@ -600,13 +600,10 @@ class VideoUtils: playlist.is_endlist = True for url in ts_urls: tasks.append(VideoUtils.async_download_file(url.absolute_uri, f"{temp_dir}/{url.uri}")) - await asyncio.gather(*tasks) - # 4. 修改m3u8文件指向本地TS片段 local_m3u8_path = os.path.join(temp_dir, "local.m3u8") playlist.dump(local_m3u8_path) - return local_m3u8_path, temp_dir, diff except Exception as e: logger.exception(e) @@ -631,7 +628,7 @@ class VideoUtils: os.makedirs(os.path.dirname(output_path), exist_ok=True) try: - local_m3u8_path, temp_dir = await VideoUtils.convert_m3u8_to_local_source(media_stream_url=media_stream_url) + local_m3u8_path, temp_dir, diff = await VideoUtils.convert_m3u8_to_local_source(media_stream_url=media_stream_url) # 使用ffmpeg合并TS片段 ffmpeg_cmd = VideoUtils.async_ffmpeg_init() ffmpeg_cmd.input(local_m3u8_path, From 14764ff5346e3ce92ef90a45c0f51d1dbe3f476a Mon Sep 17 00:00:00 2001 From: "shuohigh@gmail.com" Date: Wed, 18 Jun 2025 17:27:57 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix=20:=20=E4=BF=AE=E5=A4=8Dffmpeg=5Fmedia?= =?UTF-8?q?=5Fconcat=E4=BD=BF=E7=94=A8=E7=9A=84=E9=9F=B3=E9=A2=91=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=A0=BC=E5=BC=8F=20fltp=20->=20s16=E4=BB=A5=E7=A1=AE?= =?UTF-8?q?=E4=BF=9Daac=E9=9F=B3=E9=A2=91=E7=BC=96=E7=A0=81=E7=9A=84?= =?UTF-8?q?=E5=8F=AF=E7=94=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BowongModalFunctions/utils/VideoUtils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BowongModalFunctions/utils/VideoUtils.py b/src/BowongModalFunctions/utils/VideoUtils.py index 1860193..5ee2dc4 100644 --- a/src/BowongModalFunctions/utils/VideoUtils.py +++ b/src/BowongModalFunctions/utils/VideoUtils.py @@ -686,7 +686,9 @@ class VideoUtils: f"pad={target_width}:{target_height}:(ow-iw)/2:(oh-ih)/2," f"setsar=1:1," # 新增强制设置SAR f"fps=30,format=yuv420p[v{i}]", - f"[{i}:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo[a{i}]", + # 修改音频过滤器,确保输出为AAC兼容格式 + # f"[{i}:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo[a{i}]", + f"[{i}:a]aformat=sample_fmts=s16:sample_rates=44100:channel_layouts=stereo[a{i}]", ] ) # 3. 准备处理后的视频流和音频流的连接字符串