From ce9e0e93c27c514d1612fa02a842660b670ad5a8 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:26 +0800 Subject: [PATCH 01/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dasset=5Flib?= =?UTF-8?q?raries.py=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=BD=93=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/asset_libraries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/api/routes/asset_libraries.py b/apps/api/app/api/routes/asset_libraries.py index 5b9841c0c..8cdf6e709 100644 --- a/apps/api/app/api/routes/asset_libraries.py +++ b/apps/api/app/api/routes/asset_libraries.py @@ -12,7 +12,7 @@ from app.schemas.asset_library import ( EnsureDefaultLibraryRequest, ListAssetLibrariesResponse, ) -from fastapi import APIRouter, Depends, HTTPException, Query, status +from fastapi import APIRouter, Depends, HTTPException, Query, status, Response from packages.application import ( CreateAssetLibraryCommand, @@ -146,7 +146,7 @@ def ensure_default_library( return _to_asset_library_response(created) -@router.delete("/{library_id}", status_code=status.HTTP_204_NO_CONTENT) +@router.delete("/{library_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response) def delete_asset_library( library_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), -- 2.54.0 From 6347cd117322604337d6dcfe2b399dbbfae97b2b Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:26 +0800 Subject: [PATCH 02/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dassets.py?= =?UTF-8?q?=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94=E4=BD=93?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/assets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py index 1ec5e7239..a3bd1e534 100644 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -19,7 +19,7 @@ from app.schemas.asset import ( UpdateAssetReviewRequest, ) from app.schemas.tag import TagAssetsRequest -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, HTTPException, Query, Response from packages.application import ( CreateAssetCommand, @@ -330,7 +330,7 @@ def update_asset( return _to_asset_response(updated) -@router.delete("/{asset_id}", status_code=204) +@router.delete("/{asset_id}", status_code=204, response_class=Response) def delete_asset( asset_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -369,7 +369,7 @@ def tag_asset( return _to_asset_response(updated) -@router.delete("/{asset_id}/tags/{tag_id}", status_code=204) +@router.delete("/{asset_id}/tags/{tag_id}", status_code=204, response_class=Response) def untag_asset( asset_id: str, tag_id: str, -- 2.54.0 From 747fb989b7632f465e11239947ba45a88ffa39f8 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:27 +0800 Subject: [PATCH 03/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dduplication?= =?UTF-8?q?.py=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=BD=93=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/duplication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/api/routes/duplication.py b/apps/api/app/api/routes/duplication.py index 427d369fa..bdf9094f9 100644 --- a/apps/api/app/api/routes/duplication.py +++ b/apps/api/app/api/routes/duplication.py @@ -239,7 +239,7 @@ def get_duplication_detail( return _to_detail_response(record) -@router.delete("/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_duplication_record( record_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -257,7 +257,7 @@ def delete_duplication_record( use_case = DeleteDuplicationRecordUseCase(duplication_repository) use_case.execute(record_id) - return Response(status_code=204) + return @router.post("/records/{record_id}/retry", response_model=DuplicationUploadResponse) -- 2.54.0 From cef499de76484f08170a2d7e2378cd9b04cfb5a7 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:28 +0800 Subject: [PATCH 04/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dedit=5Fplan?= =?UTF-8?q?s.py=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=BD=93=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/edit_plans.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/api/routes/edit_plans.py b/apps/api/app/api/routes/edit_plans.py index 9d7c9f2d3..863969edb 100755 --- a/apps/api/app/api/routes/edit_plans.py +++ b/apps/api/app/api/routes/edit_plans.py @@ -25,7 +25,7 @@ from app.auth import AuthenticatedUser, get_current_user from app.dependencies import get_db_session, get_project_repository from app.schemas.generation_task import GenerationTaskResponse from app.services import EditPlanService -from fastapi import APIRouter, Depends, HTTPException, Query, status +from fastapi import APIRouter, Depends, HTTPException, Query, status, Response from pydantic import BaseModel, Field from sqlalchemy.orm import Session @@ -421,7 +421,7 @@ def update_plan( return _to_response(result) -@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_plan( plan_id: str, db: Session = Depends(get_db_session), -- 2.54.0 From 1c2ee7dae94be11dc503008e0a49fc6d696932ee Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:28 +0800 Subject: [PATCH 05/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dfeature=5Ff?= =?UTF-8?q?lags.py=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=BD=93=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/feature_flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/app/api/routes/feature_flags.py b/apps/api/app/api/routes/feature_flags.py index c5d1c2822..a3dff32f0 100755 --- a/apps/api/app/api/routes/feature_flags.py +++ b/apps/api/app/api/routes/feature_flags.py @@ -173,7 +173,7 @@ async def update_feature_flag( raise HTTPException(status_code=500, detail=f"Failed to update flag: {exc}") -@router.delete("/{name}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{name}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) async def delete_feature_flag( name: str, _: bool = Depends(_verify_internal_api_key), -- 2.54.0 From 40a642b778802ac2c4bf9f97d22db8f62d1572d9 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:29 +0800 Subject: [PATCH 06/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dprojects.py?= =?UTF-8?q?=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94=E4=BD=93?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/projects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/app/api/routes/projects.py b/apps/api/app/api/routes/projects.py index 17be8f438..85313bcf3 100755 --- a/apps/api/app/api/routes/projects.py +++ b/apps/api/app/api/routes/projects.py @@ -7,7 +7,7 @@ from app.schemas.project import ( ListProjectsResponse, ProjectResponse, ) -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, status, Response from packages.application import ( CreateProjectCommand, @@ -72,7 +72,7 @@ def create_project( return _to_project_response(project) -@router.delete("/{project_id}") +@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response) def delete_project( project_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -88,4 +88,4 @@ def delete_project( ) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found") - return {"message": "Project deleted successfully"} + return -- 2.54.0 From d2f086a74082d0384d5acbb68ce810bba933836a Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:29 +0800 Subject: [PATCH 07/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dtags.py?= =?UTF-8?q?=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94=E4=BD=93?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/tags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/api/routes/tags.py b/apps/api/app/api/routes/tags.py index 7de3e278b..45da212a5 100644 --- a/apps/api/app/api/routes/tags.py +++ b/apps/api/app/api/routes/tags.py @@ -10,7 +10,7 @@ from app.schemas.tag import ( ListTagsResponse, TagResponse, ) -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Response from packages.domain import Tag @@ -52,7 +52,7 @@ def create_tag( return TagResponse(id=created.id, name=created.name, created_at=created.created_at) -@router.delete("/{tag_id}", status_code=204) +@router.delete("/{tag_id}", status_code=204, response_class=Response) def delete_tag( tag_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), -- 2.54.0 From 7d6a469f1a381972a59755b0a717df0d7f3e4918 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:31 +0800 Subject: [PATCH 08/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dtemplates.p?= =?UTF-8?q?y=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=BD=93=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/templates.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api/app/api/routes/templates.py b/apps/api/app/api/routes/templates.py index 6d5c5185f..1cfecd123 100644 --- a/apps/api/app/api/routes/templates.py +++ b/apps/api/app/api/routes/templates.py @@ -206,7 +206,7 @@ def update_template( return _to_response(template) -@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_template( template_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -217,7 +217,7 @@ def delete_template( deleted = use_case.execute(template_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") - return Response(status_code=204) + return @router.post("/{template_id}/toggle-favorite", response_model=ToggleFavoriteResponse) @@ -307,7 +307,7 @@ def create_category( ) -@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_category( category_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -318,4 +318,4 @@ def delete_category( deleted = use_case.execute(category_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found") - return Response(status_code=204) + return -- 2.54.0 From 0462979e145a37d5b24eb0093a6a350af4cfac1e Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:31 +0800 Subject: [PATCH 09/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dtitles.py?= =?UTF-8?q?=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94=E4=BD=93?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/titles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/api/routes/titles.py b/apps/api/app/api/routes/titles.py index f65f3cdea..154a2a6eb 100644 --- a/apps/api/app/api/routes/titles.py +++ b/apps/api/app/api/routes/titles.py @@ -138,7 +138,7 @@ def update_title( return _to_response(item) -@router.delete("/{title_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{title_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_title( title_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -149,4 +149,4 @@ def delete_title( deleted = use_case.execute(title_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Title not found") - return Response(status_code=204) + return -- 2.54.0 From 9511e51a9c9b9925974a0b4df11e0a49e648d398 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:32 +0800 Subject: [PATCH 10/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dtts.py?= =?UTF-8?q?=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94=E4=BD=93?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/tts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/api/routes/tts.py b/apps/api/app/api/routes/tts.py index 77f9991e2..40c665685 100755 --- a/apps/api/app/api/routes/tts.py +++ b/apps/api/app/api/routes/tts.py @@ -241,7 +241,7 @@ def get_tts_job_status( ) -@router.delete("/jobs/{job_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/jobs/{job_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_tts_job( job_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -253,7 +253,7 @@ def delete_tts_job( deleted = use_case.execute(job_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") - return Response(status_code=204) + return @router.post( -- 2.54.0 From 90279314430bd904ec42f8f5bd0ce0135aaa9678 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:32 +0800 Subject: [PATCH 11/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dvoice=5Fclo?= =?UTF-8?q?nes.py=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=BD=93=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/voice_clones.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/api/app/api/routes/voice_clones.py b/apps/api/app/api/routes/voice_clones.py index 8a138c12f..19fcf8a21 100644 --- a/apps/api/app/api/routes/voice_clones.py +++ b/apps/api/app/api/routes/voice_clones.py @@ -172,6 +172,7 @@ def get_voice_clone_status( "/{clone_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, + response_class=Response, ) def delete_voice_clone( clone_id: str, @@ -184,7 +185,7 @@ def delete_voice_clone( deleted = use_case.execute(clone_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") - return Response(status_code=204) + return @router.post("/{clone_id}/retry", response_model=VoiceCloneProfileResponse) -- 2.54.0 From 274c1e6a6388224d777d0b176a58aa2ad45b17ac Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 23:00:33 +0800 Subject: [PATCH 12/27] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8Dvoices.py?= =?UTF-8?q?=E4=B8=ADDELETE=E6=8E=A5=E5=8F=A3204=E5=93=8D=E5=BA=94=E4=BD=93?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加response_class=Response,确保204响应无body。 --- apps/api/app/api/routes/voices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/api/routes/voices.py b/apps/api/app/api/routes/voices.py index a8830a709..e44238f12 100755 --- a/apps/api/app/api/routes/voices.py +++ b/apps/api/app/api/routes/voices.py @@ -323,7 +323,7 @@ def update_voice( return _to_response(item, sign_url) -@router.delete("/{voice_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{voice_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_voice( voice_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -334,4 +334,4 @@ def delete_voice( deleted = use_case.execute(voice_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice not found") - return Response(status_code=204) + return -- 2.54.0 From 6a64fa4a2bd5b4890b909c571d249006550596da Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 13 Jul 2026 23:46:14 +0800 Subject: [PATCH 13/27] style: black format 3 files (unified_render_service, generation, check_migration_safety) --- apps/worker/video_processing/unified_render_service.py | 1 - apps/worker/worker_app/tasks/generation.py | 3 +-- scripts/check_migration_safety.py | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py index 8f43e0833..c482c88ad 100755 --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -981,4 +981,3 @@ class UnifiedRenderService: if clip.duration > 0: return min(clip.duration, clip.actual_duration) if clip.actual_duration > 0 else clip.duration return clip.actual_duration if clip.actual_duration > 0 else 0.0 - diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index 443ddc464..e64ca7ef5 100644 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -910,8 +910,7 @@ def _upload_and_record( key = normalize_storage_key(file_url) if not (bucket and bucket.object_exists(key)): raise RuntimeError( - f"OSS 上传后 URL 不可访问且 object_exists 失败: file_url={file_url}, " - f"storage_key={storage_key}" + f"OSS 上传后 URL 不可访问且 object_exists 失败: file_url={file_url}, " f"storage_key={storage_key}" ) logger.info("URL 校验失败但 object_exists 确认文件存在,视为上传成功: storage_key=%s", key) diff --git a/scripts/check_migration_safety.py b/scripts/check_migration_safety.py index 6ec4b784d..8038bb059 100644 --- a/scripts/check_migration_safety.py +++ b/scripts/check_migration_safety.py @@ -247,4 +247,3 @@ def main() -> int: if __name__ == "__main__": sys.exit(main()) - -- 2.54.0 From 14130a212365da9d24b75733792446e95bef77d7 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 00:09:41 +0800 Subject: [PATCH 14/27] =?UTF-8?q?fix(api):=20=E5=85=A8=E9=9D=A2=E4=BF=AE?= =?UTF-8?q?=E5=A4=8DDELETE=20204=E5=93=8D=E5=BA=94=E4=BD=93=20+=20isort?= =?UTF-8?q?=E5=85=A8=E9=87=8F=E6=95=B4=E7=90=86=20+=20flake8=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复所有14个DELETE接口204响应体问题(response_class=Response + response_model=None) - projects.py补充response_model=None(FastAPI从返回类型推断response_model导致204校验失败) - isort全量格式化15个文件(对齐black profile,line_length=120) - 修复assets.py flake8 E303(第77行前空行3→2) - black格式化3个文件:unified_render_service.py, generation.py, check_migration_safety.py Unit Tests: 1425 passed, 10 failed(10个失败均为已有问题,与204/isort无关) --- apps/api/app/api/routes/asset_libraries.py | 2 +- apps/api/app/api/routes/assets.py | 4 +--- apps/api/app/api/routes/chunked_upload.py | 3 +-- apps/api/app/api/routes/edit_plans.py | 5 +++-- apps/api/app/api/routes/edit_plans_generation.py | 2 +- apps/api/app/api/routes/generation_tasks.py | 3 +-- apps/api/app/api/routes/projects.py | 4 ++-- apps/api/app/api/routes/titles.py | 3 +-- apps/api/app/api/routes/upload.py | 3 +-- apps/api/app/api/routes/voices.py | 3 +-- apps/api/app/core/storage.py | 2 +- apps/api/app/services/job_service.py | 1 - apps/worker/video_processing/render_audio.py | 6 +++--- tests/unit/test_feature_flag.py | 1 - tests/unit/test_tts_oss_transfer.py | 1 - 15 files changed, 17 insertions(+), 26 deletions(-) mode change 100755 => 100644 apps/api/app/api/routes/edit_plans.py mode change 100755 => 100644 apps/api/app/api/routes/generation_tasks.py mode change 100755 => 100644 apps/api/app/api/routes/projects.py mode change 100755 => 100644 apps/api/app/api/routes/voices.py mode change 100755 => 100644 apps/api/app/services/job_service.py mode change 100755 => 100644 tests/unit/test_feature_flag.py diff --git a/apps/api/app/api/routes/asset_libraries.py b/apps/api/app/api/routes/asset_libraries.py index 8cdf6e709..6b7c297b6 100644 --- a/apps/api/app/api/routes/asset_libraries.py +++ b/apps/api/app/api/routes/asset_libraries.py @@ -12,7 +12,7 @@ from app.schemas.asset_library import ( EnsureDefaultLibraryRequest, ListAssetLibrariesResponse, ) -from fastapi import APIRouter, Depends, HTTPException, Query, status, Response +from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from packages.application import ( CreateAssetLibraryCommand, diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py index a3bd1e534..0861a2cfb 100644 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -1,6 +1,7 @@ import logging from typing import Any, Optional +from app.api.routes._helpers import check_project_access from app.auth import AuthenticatedUser, get_current_user from app.core.storage import get_storage_service from app.dependencies import ( @@ -27,8 +28,6 @@ from packages.application import ( ) from packages.domain import AssetStatus, ClassificationStatus -from app.api.routes._helpers import check_project_access - logger = logging.getLogger(__name__) router = APIRouter() @@ -74,7 +73,6 @@ def _to_asset_response(item, storage_service=None) -> AssetResponse: ) - @router.get("", response_model=ListAssetsResponse) def list_assets( library_id: Optional[str] = Query(None), diff --git a/apps/api/app/api/routes/chunked_upload.py b/apps/api/app/api/routes/chunked_upload.py index c9cd064b5..1db5a1647 100644 --- a/apps/api/app/api/routes/chunked_upload.py +++ b/apps/api/app/api/routes/chunked_upload.py @@ -13,6 +13,7 @@ from pathlib import Path from typing import Any from uuid import uuid4 +from app.api.routes._helpers import require_project_and_library from app.auth import AuthenticatedUser, get_current_user from app.core.celery_app import celery_app from app.core.storage import OSSStorageService, get_storage_service @@ -34,8 +35,6 @@ from fastapi.params import File from packages.application import GetProjectUseCase, SubmitIngestJobCommand, SubmitIngestJobUseCase -from app.api.routes._helpers import require_project_and_library - router = APIRouter() logger = logging.getLogger(__name__) diff --git a/apps/api/app/api/routes/edit_plans.py b/apps/api/app/api/routes/edit_plans.py old mode 100755 new mode 100644 index 863969edb..85ba23b6a --- a/apps/api/app/api/routes/edit_plans.py +++ b/apps/api/app/api/routes/edit_plans.py @@ -25,14 +25,15 @@ from app.auth import AuthenticatedUser, get_current_user from app.dependencies import get_db_session, get_project_repository from app.schemas.generation_task import GenerationTaskResponse from app.services import EditPlanService -from fastapi import APIRouter, Depends, HTTPException, Query, status, Response +from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from pydantic import BaseModel, Field from sqlalchemy.orm import Session -from ._helpers import check_project_access from packages.domain.config_schemas import normalize_plan_config from packages.domain.edit_plan import EditPlan, EditPlanStatus +from ._helpers import check_project_access + logger = logging.getLogger(__name__) router = APIRouter() diff --git a/apps/api/app/api/routes/edit_plans_generation.py b/apps/api/app/api/routes/edit_plans_generation.py index 3aae7abfa..7d7e37039 100644 --- a/apps/api/app/api/routes/edit_plans_generation.py +++ b/apps/api/app/api/routes/edit_plans_generation.py @@ -15,8 +15,8 @@ from app.api.routes._helpers import check_project_access from app.api.routes.edit_plans import ( ClipStatusItem, EditPlanGenerateResponse, - EditPlanGenerationStatusResponse, EditPlanGenerationsResponse, + EditPlanGenerationStatusResponse, ) from app.auth import AuthenticatedUser, get_current_user from app.core.celery_app import celery_app diff --git a/apps/api/app/api/routes/generation_tasks.py b/apps/api/app/api/routes/generation_tasks.py old mode 100755 new mode 100644 index 04d509b02..22c031f14 --- a/apps/api/app/api/routes/generation_tasks.py +++ b/apps/api/app/api/routes/generation_tasks.py @@ -3,6 +3,7 @@ import random import uuid from typing import Any +from app.api.routes._helpers import check_project_access from app.auth import AuthenticatedUser, get_current_user from app.core.storage import OSSStorageService, get_storage_service from app.core.task_enqueue import ( @@ -31,8 +32,6 @@ from app.schemas.generation_task import ( ) from fastapi import APIRouter, Depends, HTTPException -from app.api.routes._helpers import check_project_access - from packages.application import ( CreateGenerationTaskCommand, CreateGenerationTaskUseCase, diff --git a/apps/api/app/api/routes/projects.py b/apps/api/app/api/routes/projects.py old mode 100755 new mode 100644 index 85313bcf3..b873d5557 --- a/apps/api/app/api/routes/projects.py +++ b/apps/api/app/api/routes/projects.py @@ -7,7 +7,7 @@ from app.schemas.project import ( ListProjectsResponse, ProjectResponse, ) -from fastapi import APIRouter, Depends, HTTPException, status, Response +from fastapi import APIRouter, Depends, HTTPException, Response, status from packages.application import ( CreateProjectCommand, @@ -72,7 +72,7 @@ def create_project( return _to_project_response(project) -@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response) +@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_project( project_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), diff --git a/apps/api/app/api/routes/titles.py b/apps/api/app/api/routes/titles.py index 154a2a6eb..e81730aa3 100644 --- a/apps/api/app/api/routes/titles.py +++ b/apps/api/app/api/routes/titles.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Optional +from app.api.routes._helpers import get_user_plan from app.auth import AuthenticatedUser, get_current_user from app.dependencies import get_db_session, get_user_repository from app.schemas.title_library import ( @@ -28,8 +29,6 @@ from packages.application.title_library.use_cases import ( ) from packages.ports.user_repository import UserRepository -from app.api.routes._helpers import get_user_plan - router = APIRouter() diff --git a/apps/api/app/api/routes/upload.py b/apps/api/app/api/routes/upload.py index 97a83bfcd..c67add1eb 100644 --- a/apps/api/app/api/routes/upload.py +++ b/apps/api/app/api/routes/upload.py @@ -2,6 +2,7 @@ import logging from typing import Any from uuid import uuid4 +from app.api.routes._helpers import require_project_and_library from app.auth import AuthenticatedUser, get_current_user from app.config import get_settings from app.core.celery_app import celery_app @@ -23,8 +24,6 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, s from packages.application import SubmitIngestJobCommand, SubmitIngestJobUseCase -from app.api.routes._helpers import require_project_and_library - logger = logging.getLogger(__name__) router = APIRouter() diff --git a/apps/api/app/api/routes/voices.py b/apps/api/app/api/routes/voices.py old mode 100755 new mode 100644 index e44238f12..9a7d8e5d4 --- a/apps/api/app/api/routes/voices.py +++ b/apps/api/app/api/routes/voices.py @@ -7,6 +7,7 @@ from __future__ import annotations from typing import Literal, Optional +from app.api.routes._helpers import get_user_plan from app.auth import AuthenticatedUser, get_current_user from app.dependencies import get_audio_url_signer, get_db_session, get_user_repository from app.schemas.voice import ( @@ -39,8 +40,6 @@ from packages.application.voice_library.use_cases import ( from packages.domain.preset_voices import PRESET_VOICES from packages.ports.user_repository import UserRepository -from app.api.routes._helpers import get_user_plan - router = APIRouter() diff --git a/apps/api/app/core/storage.py b/apps/api/app/core/storage.py index 8c39c13f8..af20e14b9 100644 --- a/apps/api/app/core/storage.py +++ b/apps/api/app/core/storage.py @@ -5,8 +5,8 @@ This module keeps old import paths working so existing code does not need to change. """ +from packages.shared.storage import SharedStorageService as OSSStorageService from packages.shared.storage import ( - SharedStorageService as OSSStorageService, get_shared_storage_service, get_storage_service, ) diff --git a/apps/api/app/services/job_service.py b/apps/api/app/services/job_service.py old mode 100755 new mode 100644 index d24ee32eb..ee80937e2 --- a/apps/api/app/services/job_service.py +++ b/apps/api/app/services/job_service.py @@ -13,7 +13,6 @@ from __future__ import annotations import logging from typing import Any - from packages.application.jobs import ( CancelJobUseCase, CompleteJobCommand, diff --git a/apps/worker/video_processing/render_audio.py b/apps/worker/video_processing/render_audio.py index 6f114f5f6..286b404fd 100644 --- a/apps/worker/video_processing/render_audio.py +++ b/apps/worker/video_processing/render_audio.py @@ -16,15 +16,15 @@ import subprocess from dataclasses import dataclass, field from pathlib import Path -from video_processing.ffmpeg_utils import FFMPEG_BIN, probe_has_audio, run_ffmpeg - # 延迟导入避免循环依赖:unified_render_service 定义 ResolvedClip / RenderLayer, # 本模块提供音频函数供 unified_render_service 调用。 # 使用 from __future__ import annotations + TYPE_CHECKING 解决类型引用。 from typing import TYPE_CHECKING +from video_processing.ffmpeg_utils import FFMPEG_BIN, probe_has_audio, run_ffmpeg + if TYPE_CHECKING: - from video_processing.unified_render_service import ResolvedClip, RenderLayer + from video_processing.unified_render_service import RenderLayer, ResolvedClip logger = logging.getLogger(__name__) diff --git a/tests/unit/test_feature_flag.py b/tests/unit/test_feature_flag.py old mode 100755 new mode 100644 index a349cc3b2..8bab8c04e --- a/tests/unit/test_feature_flag.py +++ b/tests/unit/test_feature_flag.py @@ -7,7 +7,6 @@ from __future__ import annotations from unittest.mock import MagicMock - from packages.adapters.redis.feature_flag_store import ( FeatureFlagConfig, InMemoryFeatureFlagStore, diff --git a/tests/unit/test_tts_oss_transfer.py b/tests/unit/test_tts_oss_transfer.py index 67271ad44..99d5fc292 100644 --- a/tests/unit/test_tts_oss_transfer.py +++ b/tests/unit/test_tts_oss_transfer.py @@ -9,7 +9,6 @@ from __future__ import annotations from datetime import datetime, timezone from unittest.mock import MagicMock, patch - from packages.application.cosyvoice_service import CosyVoiceService from packages.application.tts_job.workflow import TTSWorkflowService from packages.domain.tts_job import TTSJob, TTSJobStatus -- 2.54.0 From 11b7cd1c51a5b04b9ac6342b2bfca2ef6414e9d1 Mon Sep 17 00:00:00 2001 From: ops-bot Date: Mon, 13 Jul 2026 22:48:03 +0800 Subject: [PATCH 15/27] =?UTF-8?q?fix(ci):=20=E7=A7=BB=E9=99=A4--break-syst?= =?UTF-8?q?em-packages=EF=BC=8C=E5=85=BC=E5=AE=B9=E6=97=A7Runner=20pip=202?= =?UTF-8?q?2.0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 69868e967..cadd65963 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -92,9 +92,9 @@ jobs: shell: sh run: | set -eu - python3 -m pip install --break-system-packages -q -r requirements-base.txt - python3 -m pip install --break-system-packages -q -r requirements.txt - python3 -m pip install --break-system-packages -q -r requirements-dev.txt + python3 -m pip install -q -r requirements-base.txt + python3 -m pip install -q -r requirements.txt + python3 -m pip install -q -r requirements-dev.txt python3 -m black --version python3 -m isort --version-number python3 -m flake8 --version @@ -221,9 +221,9 @@ jobs: shell: sh run: | set -eu - python3 -m pip install --break-system-packages -q -r requirements-base.txt - python3 -m pip install --break-system-packages -q -r requirements.txt - python3 -m pip install --break-system-packages -q -r requirements-dev.txt + python3 -m pip install -q -r requirements-base.txt + python3 -m pip install -q -r requirements.txt + python3 -m pip install -q -r requirements-dev.txt pytest --version - name: Run unit tests with coverage @@ -320,9 +320,9 @@ jobs: shell: sh run: | set -eu - python3 -m pip install --break-system-packages -q -r requirements-base.txt - python3 -m pip install --break-system-packages -q -r requirements.txt - python3 -m pip install --break-system-packages -q -r requirements-dev.txt + python3 -m pip install -q -r requirements-base.txt + python3 -m pip install -q -r requirements.txt + python3 -m pip install -q -r requirements-dev.txt pytest --version - name: Start Redis @@ -394,7 +394,7 @@ jobs: shell: sh run: | set -eu - pip install --break-system-packages -q pytest-rerunfailures + pip install -q pytest-rerunfailures PYTHONPATH="$PWD/apps/api:$PWD" python3 -m coverage run --append \ --source=apps/api/app,packages \ --omit="*/migrations/*,*/tests/*,*/test_*.py,*/site-packages/*" \ -- 2.54.0 From 12171cf9cb814b2346d78ce8c83c4d0cbd10cda6 Mon Sep 17 00:00:00 2001 From: ops-bot Date: Mon, 13 Jul 2026 22:48:26 +0800 Subject: [PATCH 16/27] =?UTF-8?q?fix(frontend):=20prettier=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=20Accounts.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/accounts/Accounts.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/web/src/pages/accounts/Accounts.tsx b/apps/web/src/pages/accounts/Accounts.tsx index d2b541b92..8252a30bb 100644 --- a/apps/web/src/pages/accounts/Accounts.tsx +++ b/apps/web/src/pages/accounts/Accounts.tsx @@ -13,11 +13,7 @@ import "./accounts.css"; /* ── 类型定义 ───────────────────────────────────────────── */ -export type PlatformId = - | "douyin" - | "kuaishou" - | "xiaohongshu" - | "wechat"; +export type PlatformId = "douyin" | "kuaishou" | "xiaohongshu" | "wechat"; export interface Platform { id: PlatformId; -- 2.54.0 From ea31cdc6482d02c885e1829caa9dec00e150c2b0 Mon Sep 17 00:00:00 2001 From: ops-bot Date: Mon, 13 Jul 2026 22:48:26 +0800 Subject: [PATCH 17/27] =?UTF-8?q?fix(frontend):=20prettier=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=20Dashboard.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/dashboard/Dashboard.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/web/src/pages/dashboard/Dashboard.tsx b/apps/web/src/pages/dashboard/Dashboard.tsx index 6b477c24d..95239e755 100644 --- a/apps/web/src/pages/dashboard/Dashboard.tsx +++ b/apps/web/src/pages/dashboard/Dashboard.tsx @@ -39,7 +39,11 @@ const Dashboard: React.FC = () => {

最近任务

-
@@ -51,7 +55,10 @@ const Dashboard: React.FC = () => {
{/* 使用统计 */} -
+

使用统计

@@ -66,13 +73,18 @@ const Dashboard: React.FC = () => {
{/* 公告 */} -
+

公告

- 官方 + + 官方 +

欢迎使用小应 SaaS 平台

-- 2.54.0 From 1f0060eb0cbdd95c88ac94f7007a873205e361d0 Mon Sep 17 00:00:00 2001 From: ops-bot Date: Mon, 13 Jul 2026 22:48:27 +0800 Subject: [PATCH 18/27] =?UTF-8?q?fix(frontend):=20prettier=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=20TitleLibrary.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/titles/TitleLibrary.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/web/src/pages/titles/TitleLibrary.tsx b/apps/web/src/pages/titles/TitleLibrary.tsx index 740f9f62c..1905472cb 100644 --- a/apps/web/src/pages/titles/TitleLibrary.tsx +++ b/apps/web/src/pages/titles/TitleLibrary.tsx @@ -416,7 +416,6 @@ const TitleLibrary: React.FC = () => { [deleteMutation], ); - /* 新建标题 */ const handleCreateTitle = () => { if (!newTitleContent.trim()) { @@ -503,7 +502,6 @@ const TitleLibrary: React.FC = () => { {cat.count} 条
-
))} @@ -614,8 +612,6 @@ const TitleLibrary: React.FC = () => { - - {/* ─── 新建标题弹窗 ─── */} Date: Mon, 13 Jul 2026 22:52:47 +0800 Subject: [PATCH 19/27] =?UTF-8?q?fix(format):=20black=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=20unified=5Frender=5Fservice.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unified_render_service.py | 76 ++++++++++++++----- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py index c482c88ad..f0ef3beea 100755 --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -238,10 +238,15 @@ class UnifiedRenderService: else: # 回退到带滤镜的直通渲染 pass_through_has_audio = self._render_pass_through( - layers, output_path, ass_path=ass_path, video_duration=video_duration + layers, + output_path, + ass_path=ass_path, + video_duration=video_duration, ) else: - filter_complex, input_args = self._build_filter_complex(layers, ass_path=ass_path) + filter_complex, input_args = self._build_filter_complex( + layers, ass_path=ass_path + ) self._execute_ffmpeg(filter_complex, input_args, video_only_path) t_video_end = time.time() @@ -326,7 +331,9 @@ class UnifiedRenderService: if not main_layer or not main_layer.clips: return 0.0 - total = sum(UnifiedRenderService._clip_effective_duration(c) for c in main_layer.clips) + total = sum( + UnifiedRenderService._clip_effective_duration(c) for c in main_layer.clips + ) # 减去转场重叠时间(粗略估算) n_clips = len(main_layer.clips) @@ -434,7 +441,10 @@ class UnifiedRenderService: return False, f"像素格式不是yuv420p: {info.get('pix_fmt', 'unknown')}" # 分辨率必须一致 - if info.get("width", 0) != self.output_width or info.get("height", 0) != self.output_height: + if ( + info.get("width", 0) != self.output_width + or info.get("height", 0) != self.output_height + ): return False, ( f"分辨率不匹配: " f"{info.get('width', 0)}x{info.get('height', 0)} " @@ -484,7 +494,9 @@ class UnifiedRenderService: role = layers[0].role # 判断是否满足 copy 条件 - can_copy, reason = self._can_use_stream_copy(clip, ass_path=ass_path, video_duration=video_duration) + can_copy, reason = self._can_use_stream_copy( + clip, ass_path=ass_path, video_duration=video_duration + ) if not can_copy: logger.info( "[unified-render] stream_copy 跳过: plan_id=%s reason=%s", @@ -512,7 +524,9 @@ class UnifiedRenderService: # 计算最终时长 final_duration = effective_duration - if video_duration > 0 and (final_duration <= 0 or final_duration > video_duration): + if video_duration > 0 and ( + final_duration <= 0 or final_duration > video_duration + ): final_duration = video_duration if final_duration > 0: command.extend(["-t", f"{final_duration:.3f}"]) @@ -549,7 +563,9 @@ class UnifiedRenderService: ) return True else: - logger.warning("[unified-render] stream_copy 输出为空: plan_id=%s", self.plan.id) + logger.warning( + "[unified-render] stream_copy 输出为空: plan_id=%s", self.plan.id + ) return False except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: logger.warning( @@ -612,7 +628,10 @@ class UnifiedRenderService: filters.append(f"scale={pip_w}:{pip_h}") else: # main / broll / background: 铺满裁剪 - filters.append(f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=increase") + filters.append( + f"scale={self.output_width}:{self.output_height}" + ":force_original_aspect_ratio=increase" + ) filters.append(f"crop={self.output_width}:{self.output_height}") filters.append("setpts=PTS-STARTPTS") @@ -628,7 +647,9 @@ class UnifiedRenderService: # 最终输出时长:取 clip 有效时长和 video_duration 的较小值 final_duration = effective_duration - if video_duration > 0 and (final_duration <= 0 or final_duration > video_duration): + if video_duration > 0 and ( + final_duration <= 0 or final_duration > video_duration + ): final_duration = video_duration command = [ @@ -727,7 +748,9 @@ class UnifiedRenderService: resolved.sort(key=lambda c: c.order) return resolved - def _group_clips_into_layers(self, resolved_clips: list[ResolvedClip]) -> list[RenderLayer]: + def _group_clips_into_layers( + self, resolved_clips: list[ResolvedClip] + ) -> list[RenderLayer]: """将 ResolvedClips 分组为 RenderLayers。 分组规则见 _resolve_layer_role 函数文档。 @@ -817,14 +840,16 @@ class UnifiedRenderService: filters.append(f"scale={pip_w}:{pip_h}") elif role == "background": filters.append( - f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=increase" + f"scale={self.output_width}:{self.output_height}" + ":force_original_aspect_ratio=increase" ) filters.append(f"crop={self.output_width}:{self.output_height}") else: # main / broll: 铺满裁剪(scale to cover + center crop) # 对齐链路A编辑器合成行为,与主流短视频平台一致 filters.append( - f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=increase" + f"scale={self.output_width}:{self.output_height}" + ":force_original_aspect_ratio=increase" ) filters.append(f"crop={self.output_width}:{self.output_height}") @@ -841,8 +866,13 @@ class UnifiedRenderService: layer_clip_indices = [all_clips.index(c) for c in layer.clips] layer_labels = [preprocessed_labels[i] for i in layer_clip_indices] # 使用 trim 后的有效时长,与 Step 1 的 trim=duration 保持一致 - layer_durations = [UnifiedRenderService._clip_effective_duration(all_clips[i]) for i in layer_clip_indices] - layer_transitions = [all_clips[i].transition_effect for i in layer_clip_indices] + layer_durations = [ + UnifiedRenderService._clip_effective_duration(all_clips[i]) + for i in layer_clip_indices + ] + layer_transitions = [ + all_clips[i].transition_effect for i in layer_clip_indices + ] if len(layer_labels) == 1: # 单 clip 层,直接使用预处理标签 @@ -873,7 +903,8 @@ class UnifiedRenderService: base_label = layer_output_labels[role] combined_label = f"combined_{role}" filter_parts.append( - f"[{final_video_label}][{base_label}]" f"overlay=(W-w)/2:(H-h)/2[{combined_label}]" + f"[{final_video_label}][{base_label}]" + f"overlay=(W-w)/2:(H-h)/2[{combined_label}]" ) final_video_label = combined_label else: @@ -898,13 +929,18 @@ class UnifiedRenderService: 20, ) combined_label = f"combined_{layer.role}" - filter_parts.append(f"[{final_video_label}][{overlay_label}]" f"overlay={x}:{y}[{combined_label}]") + filter_parts.append( + f"[{final_video_label}][{overlay_label}]" + f"overlay={x}:{y}[{combined_label}]" + ) final_video_label = combined_label # 叠加字幕(如有)+ 最终像素格式 if ass_path is not None: ass_filter_path = str(ass_path).replace("\\", "/").replace(":", "\\:") - filter_parts.append(f"[{final_video_label}]subtitles='{ass_filter_path}',format=yuv420p[final_video]") + filter_parts.append( + f"[{final_video_label}]subtitles='{ass_filter_path}',format=yuv420p[final_video]" + ) else: filter_parts.append(f"[{final_video_label}]format=yuv420p[final_video]") @@ -979,5 +1015,9 @@ class UnifiedRenderService: def _clip_effective_duration(clip: ResolvedClip) -> float: """计算 clip 的有效时长.""" if clip.duration > 0: - return min(clip.duration, clip.actual_duration) if clip.actual_duration > 0 else clip.duration + return ( + min(clip.duration, clip.actual_duration) + if clip.actual_duration > 0 + else clip.duration + ) return clip.actual_duration if clip.actual_duration > 0 else 0.0 -- 2.54.0 From 0974345215b2af5b39d5253d882622103bdeff12 Mon Sep 17 00:00:00 2001 From: ops-bot Date: Mon, 13 Jul 2026 22:52:49 +0800 Subject: [PATCH 20/27] =?UTF-8?q?fix(format):=20black=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=20generation.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/worker/worker_app/tasks/generation.py | 63 +++++++++++++++++----- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index e64ca7ef5..dec803ebb 100644 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -67,7 +67,11 @@ def _update_task_status(task_id: str, status_action: str, **kwargs) -> bool: action(**kwargs) repo.update(task) - logger.info("GenerationTask 状态更新成功: task_id=%s action=%s", task_id, status_action) + logger.info( + "GenerationTask 状态更新成功: task_id=%s action=%s", + task_id, + status_action, + ) return True finally: session.close() @@ -92,7 +96,11 @@ def _flush_logs(task_id: str, gen_task) -> None: try: from packages.adapters.sqlalchemy_impl.models import GenerationTaskModel - model = session.query(GenerationTaskModel).filter(GenerationTaskModel.id == task_id).first() + model = ( + session.query(GenerationTaskModel) + .filter(GenerationTaskModel.id == task_id) + .first() + ) if model: model.logs = gen_task.logs session.commit() @@ -405,7 +413,9 @@ def _download_library_assets( else: # 未指定 asset_ids:按 library 或 project 下载全部 ready 视频 if asset_library_id: - query = query.filter(AssetModel.asset_library_id == asset_library_id) + query = query.filter( + AssetModel.asset_library_id == asset_library_id + ) logger.info( "下载素材库全部视频: asset_library_id=%s", asset_library_id, @@ -420,7 +430,11 @@ def _download_library_assets( assets = query.order_by(AssetModel.created_at).all() if not assets: - mode_desc = f"素材库 {asset_library_id}" if asset_library_id else f"项目 {project_id}" + mode_desc = ( + f"素材库 {asset_library_id}" + if asset_library_id + else f"项目 {project_id}" + ) msg = f"未找到视频素材: {mode_desc}, asset_ids={asset_ids or 'all'}" logger.error(msg) raise RuntimeError(msg) @@ -458,7 +472,10 @@ def _download_library_assets( if not storage_key: failed_assets.append(f"{asset.name}({asset.id})") logger.warning( - "[task_id=%s] 素材缺少 file_url, 跳过: asset_id=%s name=%s", task_id, asset.id, asset.name + "[task_id=%s] 素材缺少 file_url, 跳过: asset_id=%s name=%s", + task_id, + asset.id, + asset.name, ) if gen_task: gen_task.append_log( @@ -472,7 +489,9 @@ def _download_library_assets( duration=0.0, ) if strict: - raise RuntimeError(f"素材缺少 file_url: asset_id={asset.id}, name={asset.name}") + raise RuntimeError( + f"素材缺少 file_url: asset_id={asset.id}, name={asset.name}" + ) continue ext = Path(storage_key).suffix or ".mp4" @@ -504,7 +523,12 @@ def _download_library_assets( ) else: failed_assets.append(f"{asset.name}({asset.id})") - logger.warning("[task_id=%s] Failed to download asset: %s (id=%s)", task_id, asset.name, asset.id) + logger.warning( + "[task_id=%s] Failed to download asset: %s (id=%s)", + task_id, + asset.name, + asset.id, + ) if gen_task: gen_task.append_log( "下载素材", @@ -517,7 +541,9 @@ def _download_library_assets( duration=round(asset_elapsed, 2), ) if strict: - raise RuntimeError(f"素材下载失败: asset_id={asset.id}, name={asset.name}") + raise RuntimeError( + f"素材下载失败: asset_id={asset.id}, name={asset.name}" + ) # 指定了 asset_ids 但全部下载失败 → 无论 strict 与否都报错 if asset_ids and not downloaded: @@ -826,7 +852,9 @@ def _render_video( # 选择渲染引擎 engine = _resolve_render_engine(user_id) if user_id else ENGINE_UNIFIED - logger.info("[task_id=%s] [渲染] 引擎选择: %s (user_id=%s)", task_id, engine, user_id) + logger.info( + "[task_id=%s] [渲染] 引擎选择: %s (user_id=%s)", task_id, engine, user_id + ) render_start = time.monotonic() render_output_path = temp_path / f"rendered-{task_id}.mp4" @@ -870,7 +898,9 @@ def _render_video( _mux_audio_track(render_output_path, voice_path, final_path) output_path = final_path except Exception as mux_err: - logger.warning("[task_id=%s] [混音] 音频混合失败,使用无音频版本: %s", task_id, mux_err) + logger.warning( + "[task_id=%s] [混音] 音频混合失败,使用无音频版本: %s", task_id, mux_err + ) output_path = render_output_path else: output_path = render_output_path @@ -899,7 +929,9 @@ def _upload_and_record( file_url = upload_to_oss(output_path, storage_key) upload_elapsed = time.monotonic() - upload_start if not file_url: - raise RuntimeError(f"OSS 上传失败: task_id={task_id}, storage_key={storage_key}") + raise RuntimeError( + f"OSS 上传失败: task_id={task_id}, storage_key={storage_key}" + ) # 校验 URL 可达性(P0-2: 私有 bucket 用预签名 + object_exists 降级) verify_url = get_signed_download_url(file_url, expires_seconds=300) or file_url @@ -912,7 +944,10 @@ def _upload_and_record( raise RuntimeError( f"OSS 上传后 URL 不可访问且 object_exists 失败: file_url={file_url}, " f"storage_key={storage_key}" ) - logger.info("URL 校验失败但 object_exists 确认文件存在,视为上传成功: storage_key=%s", key) + logger.info( + "URL 校验失败但 object_exists 确认文件存在,视为上传成功: storage_key=%s", + key, + ) logger.info( "[task_id=%s] [OSS上传] 成功: 耗时=%.1fs, file_url=%s", @@ -1008,7 +1043,9 @@ def generate_video(self, task_id: str) -> dict: _update_task_status(task_id, "mark_processing") try: - editing_mode = EditingMode(mode) if (mode := task_info["mode"]) else EditingMode.ONE_TAKE + editing_mode = ( + EditingMode(mode) if (mode := task_info["mode"]) else EditingMode.ONE_TAKE + ) except ValueError: editing_mode = EditingMode.ONE_TAKE -- 2.54.0 From 45da7b6f3ad34254b6c45449561b3a93f3413692 Mon Sep 17 00:00:00 2001 From: ops-bot Date: Mon, 13 Jul 2026 22:52:50 +0800 Subject: [PATCH 21/27] =?UTF-8?q?fix(format):=20black=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=20check=5Fmigration=5Fsafety.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/check_migration_safety.py | 42 +++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/scripts/check_migration_safety.py b/scripts/check_migration_safety.py index 8038bb059..48f9905b1 100644 --- a/scripts/check_migration_safety.py +++ b/scripts/check_migration_safety.py @@ -48,10 +48,19 @@ HIGH_RISK_PATTERNS = [ # 中风险模式:可能导致数据丢失或兼容性问题 MEDIUM_RISK_PATTERNS = [ - (r"op\.alter_column\([^)]*nullable\s*=\s*False", "新增 NOT NULL 约束 - 旧数据可能为空导致迁移失败"), + ( + r"op\.alter_column\([^)]*nullable\s*=\s*False", + "新增 NOT NULL 约束 - 旧数据可能为空导致迁移失败", + ), (r"op\.alter_column\([^)]*type_\s*=", "列类型变更 - 可能导致数据截断或转换失败"), - (r"\bop\.rename_table\(", "op.rename_table() - 重命名表,可能导致依赖该表的代码报错"), - (r"\bop\.rename_column\(", "op.rename_column() - 重命名列,可能导致依赖该列的代码报错"), + ( + r"\bop\.rename_table\(", + "op.rename_table() - 重命名表,可能导致依赖该表的代码报错", + ), + ( + r"\bop\.rename_column\(", + "op.rename_column() - 重命名列,可能导致依赖该列的代码报错", + ), (r"\bop\.drop_index\(", "op.drop_index() - 删除索引,可能影响查询性能"), (r"\bop\.drop_constraint\(", "op.drop_constraint() - 删除约束,可能影响数据完整性"), ] @@ -95,13 +104,24 @@ def get_new_migrations_via_diff(diff_target: str) -> List[Path]: """ try: result = subprocess.run( - ["git", "diff", "--name-only", "--diff-filter=A", diff_target, "HEAD", "--", "alembic/versions/"], + [ + "git", + "diff", + "--name-only", + "--diff-filter=A", + diff_target, + "HEAD", + "--", + "alembic/versions/", + ], cwd=str(REPO_ROOT), capture_output=True, text=True, check=True, ) - files = [line.strip() for line in result.stdout.strip().split("\n") if line.strip()] + files = [ + line.strip() for line in result.stdout.strip().split("\n") if line.strip() + ] return [REPO_ROOT / f for f in files] except subprocess.CalledProcessError as e: print(f"⚠️ git diff 失败({diff_target}):{e.stderr.strip()}") @@ -109,7 +129,9 @@ def get_new_migrations_via_diff(diff_target: str) -> List[Path]: return sorted(ALEMBIC_VERSIONS_DIR.glob("*.py")) -def find_new_migrations(since_revision: str | None = None, diff_against: str | None = None) -> List[Path]: +def find_new_migrations( + since_revision: str | None = None, diff_against: str | None = None +) -> List[Path]: """ 找出需要检查的迁移文件。 优先级:diff_against > since_revision > 全部 @@ -161,7 +183,9 @@ def analyze_migration(file_path: Path) -> Tuple[List[str], List[str], List[str]] def main() -> int: - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) parser.add_argument( "--since", default=os.getenv("MIGRATION_SINCE_REVISION"), @@ -224,7 +248,9 @@ def main() -> int: print() print("=" * 60) - print(f"检查结果:{len(all_safe)} 项安全 / {len(all_medium)} 项中风险 / {len(all_high)} 项高风险") + print( + f"检查结果:{len(all_safe)} 项安全 / {len(all_medium)} 项中风险 / {len(all_high)} 项高风险" + ) print() if all_high: -- 2.54.0 From 8191ca3909a22a657cb1e71364a419cefe62ba13 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 00:36:37 +0800 Subject: [PATCH 22/27] style: re-run black on 3 files after cherry-pick merge --- .../unified_render_service.py | 71 +++++-------------- apps/worker/worker_app/tasks/generation.py | 40 +++-------- scripts/check_migration_safety.py | 16 ++--- 3 files changed, 30 insertions(+), 97 deletions(-) diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py index f0ef3beea..2a0575af7 100755 --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -244,9 +244,7 @@ class UnifiedRenderService: video_duration=video_duration, ) else: - filter_complex, input_args = self._build_filter_complex( - layers, ass_path=ass_path - ) + filter_complex, input_args = self._build_filter_complex(layers, ass_path=ass_path) self._execute_ffmpeg(filter_complex, input_args, video_only_path) t_video_end = time.time() @@ -331,9 +329,7 @@ class UnifiedRenderService: if not main_layer or not main_layer.clips: return 0.0 - total = sum( - UnifiedRenderService._clip_effective_duration(c) for c in main_layer.clips - ) + total = sum(UnifiedRenderService._clip_effective_duration(c) for c in main_layer.clips) # 减去转场重叠时间(粗略估算) n_clips = len(main_layer.clips) @@ -441,10 +437,7 @@ class UnifiedRenderService: return False, f"像素格式不是yuv420p: {info.get('pix_fmt', 'unknown')}" # 分辨率必须一致 - if ( - info.get("width", 0) != self.output_width - or info.get("height", 0) != self.output_height - ): + if info.get("width", 0) != self.output_width or info.get("height", 0) != self.output_height: return False, ( f"分辨率不匹配: " f"{info.get('width', 0)}x{info.get('height', 0)} " @@ -494,9 +487,7 @@ class UnifiedRenderService: role = layers[0].role # 判断是否满足 copy 条件 - can_copy, reason = self._can_use_stream_copy( - clip, ass_path=ass_path, video_duration=video_duration - ) + can_copy, reason = self._can_use_stream_copy(clip, ass_path=ass_path, video_duration=video_duration) if not can_copy: logger.info( "[unified-render] stream_copy 跳过: plan_id=%s reason=%s", @@ -524,9 +515,7 @@ class UnifiedRenderService: # 计算最终时长 final_duration = effective_duration - if video_duration > 0 and ( - final_duration <= 0 or final_duration > video_duration - ): + if video_duration > 0 and (final_duration <= 0 or final_duration > video_duration): final_duration = video_duration if final_duration > 0: command.extend(["-t", f"{final_duration:.3f}"]) @@ -563,9 +552,7 @@ class UnifiedRenderService: ) return True else: - logger.warning( - "[unified-render] stream_copy 输出为空: plan_id=%s", self.plan.id - ) + logger.warning("[unified-render] stream_copy 输出为空: plan_id=%s", self.plan.id) return False except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: logger.warning( @@ -628,10 +615,7 @@ class UnifiedRenderService: filters.append(f"scale={pip_w}:{pip_h}") else: # main / broll / background: 铺满裁剪 - filters.append( - f"scale={self.output_width}:{self.output_height}" - ":force_original_aspect_ratio=increase" - ) + filters.append(f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=increase") filters.append(f"crop={self.output_width}:{self.output_height}") filters.append("setpts=PTS-STARTPTS") @@ -647,9 +631,7 @@ class UnifiedRenderService: # 最终输出时长:取 clip 有效时长和 video_duration 的较小值 final_duration = effective_duration - if video_duration > 0 and ( - final_duration <= 0 or final_duration > video_duration - ): + if video_duration > 0 and (final_duration <= 0 or final_duration > video_duration): final_duration = video_duration command = [ @@ -748,9 +730,7 @@ class UnifiedRenderService: resolved.sort(key=lambda c: c.order) return resolved - def _group_clips_into_layers( - self, resolved_clips: list[ResolvedClip] - ) -> list[RenderLayer]: + def _group_clips_into_layers(self, resolved_clips: list[ResolvedClip]) -> list[RenderLayer]: """将 ResolvedClips 分组为 RenderLayers。 分组规则见 _resolve_layer_role 函数文档。 @@ -840,16 +820,14 @@ class UnifiedRenderService: filters.append(f"scale={pip_w}:{pip_h}") elif role == "background": filters.append( - f"scale={self.output_width}:{self.output_height}" - ":force_original_aspect_ratio=increase" + f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=increase" ) filters.append(f"crop={self.output_width}:{self.output_height}") else: # main / broll: 铺满裁剪(scale to cover + center crop) # 对齐链路A编辑器合成行为,与主流短视频平台一致 filters.append( - f"scale={self.output_width}:{self.output_height}" - ":force_original_aspect_ratio=increase" + f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=increase" ) filters.append(f"crop={self.output_width}:{self.output_height}") @@ -866,13 +844,8 @@ class UnifiedRenderService: layer_clip_indices = [all_clips.index(c) for c in layer.clips] layer_labels = [preprocessed_labels[i] for i in layer_clip_indices] # 使用 trim 后的有效时长,与 Step 1 的 trim=duration 保持一致 - layer_durations = [ - UnifiedRenderService._clip_effective_duration(all_clips[i]) - for i in layer_clip_indices - ] - layer_transitions = [ - all_clips[i].transition_effect for i in layer_clip_indices - ] + layer_durations = [UnifiedRenderService._clip_effective_duration(all_clips[i]) for i in layer_clip_indices] + layer_transitions = [all_clips[i].transition_effect for i in layer_clip_indices] if len(layer_labels) == 1: # 单 clip 层,直接使用预处理标签 @@ -903,8 +876,7 @@ class UnifiedRenderService: base_label = layer_output_labels[role] combined_label = f"combined_{role}" filter_parts.append( - f"[{final_video_label}][{base_label}]" - f"overlay=(W-w)/2:(H-h)/2[{combined_label}]" + f"[{final_video_label}][{base_label}]" f"overlay=(W-w)/2:(H-h)/2[{combined_label}]" ) final_video_label = combined_label else: @@ -929,18 +901,13 @@ class UnifiedRenderService: 20, ) combined_label = f"combined_{layer.role}" - filter_parts.append( - f"[{final_video_label}][{overlay_label}]" - f"overlay={x}:{y}[{combined_label}]" - ) + filter_parts.append(f"[{final_video_label}][{overlay_label}]" f"overlay={x}:{y}[{combined_label}]") final_video_label = combined_label # 叠加字幕(如有)+ 最终像素格式 if ass_path is not None: ass_filter_path = str(ass_path).replace("\\", "/").replace(":", "\\:") - filter_parts.append( - f"[{final_video_label}]subtitles='{ass_filter_path}',format=yuv420p[final_video]" - ) + filter_parts.append(f"[{final_video_label}]subtitles='{ass_filter_path}',format=yuv420p[final_video]") else: filter_parts.append(f"[{final_video_label}]format=yuv420p[final_video]") @@ -1015,9 +982,5 @@ class UnifiedRenderService: def _clip_effective_duration(clip: ResolvedClip) -> float: """计算 clip 的有效时长.""" if clip.duration > 0: - return ( - min(clip.duration, clip.actual_duration) - if clip.actual_duration > 0 - else clip.duration - ) + return min(clip.duration, clip.actual_duration) if clip.actual_duration > 0 else clip.duration return clip.actual_duration if clip.actual_duration > 0 else 0.0 diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index dec803ebb..5d7f6e356 100644 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -96,11 +96,7 @@ def _flush_logs(task_id: str, gen_task) -> None: try: from packages.adapters.sqlalchemy_impl.models import GenerationTaskModel - model = ( - session.query(GenerationTaskModel) - .filter(GenerationTaskModel.id == task_id) - .first() - ) + model = session.query(GenerationTaskModel).filter(GenerationTaskModel.id == task_id).first() if model: model.logs = gen_task.logs session.commit() @@ -413,9 +409,7 @@ def _download_library_assets( else: # 未指定 asset_ids:按 library 或 project 下载全部 ready 视频 if asset_library_id: - query = query.filter( - AssetModel.asset_library_id == asset_library_id - ) + query = query.filter(AssetModel.asset_library_id == asset_library_id) logger.info( "下载素材库全部视频: asset_library_id=%s", asset_library_id, @@ -430,11 +424,7 @@ def _download_library_assets( assets = query.order_by(AssetModel.created_at).all() if not assets: - mode_desc = ( - f"素材库 {asset_library_id}" - if asset_library_id - else f"项目 {project_id}" - ) + mode_desc = f"素材库 {asset_library_id}" if asset_library_id else f"项目 {project_id}" msg = f"未找到视频素材: {mode_desc}, asset_ids={asset_ids or 'all'}" logger.error(msg) raise RuntimeError(msg) @@ -489,9 +479,7 @@ def _download_library_assets( duration=0.0, ) if strict: - raise RuntimeError( - f"素材缺少 file_url: asset_id={asset.id}, name={asset.name}" - ) + raise RuntimeError(f"素材缺少 file_url: asset_id={asset.id}, name={asset.name}") continue ext = Path(storage_key).suffix or ".mp4" @@ -541,9 +529,7 @@ def _download_library_assets( duration=round(asset_elapsed, 2), ) if strict: - raise RuntimeError( - f"素材下载失败: asset_id={asset.id}, name={asset.name}" - ) + raise RuntimeError(f"素材下载失败: asset_id={asset.id}, name={asset.name}") # 指定了 asset_ids 但全部下载失败 → 无论 strict 与否都报错 if asset_ids and not downloaded: @@ -852,9 +838,7 @@ def _render_video( # 选择渲染引擎 engine = _resolve_render_engine(user_id) if user_id else ENGINE_UNIFIED - logger.info( - "[task_id=%s] [渲染] 引擎选择: %s (user_id=%s)", task_id, engine, user_id - ) + logger.info("[task_id=%s] [渲染] 引擎选择: %s (user_id=%s)", task_id, engine, user_id) render_start = time.monotonic() render_output_path = temp_path / f"rendered-{task_id}.mp4" @@ -898,9 +882,7 @@ def _render_video( _mux_audio_track(render_output_path, voice_path, final_path) output_path = final_path except Exception as mux_err: - logger.warning( - "[task_id=%s] [混音] 音频混合失败,使用无音频版本: %s", task_id, mux_err - ) + logger.warning("[task_id=%s] [混音] 音频混合失败,使用无音频版本: %s", task_id, mux_err) output_path = render_output_path else: output_path = render_output_path @@ -929,9 +911,7 @@ def _upload_and_record( file_url = upload_to_oss(output_path, storage_key) upload_elapsed = time.monotonic() - upload_start if not file_url: - raise RuntimeError( - f"OSS 上传失败: task_id={task_id}, storage_key={storage_key}" - ) + raise RuntimeError(f"OSS 上传失败: task_id={task_id}, storage_key={storage_key}") # 校验 URL 可达性(P0-2: 私有 bucket 用预签名 + object_exists 降级) verify_url = get_signed_download_url(file_url, expires_seconds=300) or file_url @@ -1043,9 +1023,7 @@ def generate_video(self, task_id: str) -> dict: _update_task_status(task_id, "mark_processing") try: - editing_mode = ( - EditingMode(mode) if (mode := task_info["mode"]) else EditingMode.ONE_TAKE - ) + editing_mode = EditingMode(mode) if (mode := task_info["mode"]) else EditingMode.ONE_TAKE except ValueError: editing_mode = EditingMode.ONE_TAKE diff --git a/scripts/check_migration_safety.py b/scripts/check_migration_safety.py index 48f9905b1..747181e8f 100644 --- a/scripts/check_migration_safety.py +++ b/scripts/check_migration_safety.py @@ -119,9 +119,7 @@ def get_new_migrations_via_diff(diff_target: str) -> List[Path]: text=True, check=True, ) - files = [ - line.strip() for line in result.stdout.strip().split("\n") if line.strip() - ] + files = [line.strip() for line in result.stdout.strip().split("\n") if line.strip()] return [REPO_ROOT / f for f in files] except subprocess.CalledProcessError as e: print(f"⚠️ git diff 失败({diff_target}):{e.stderr.strip()}") @@ -129,9 +127,7 @@ def get_new_migrations_via_diff(diff_target: str) -> List[Path]: return sorted(ALEMBIC_VERSIONS_DIR.glob("*.py")) -def find_new_migrations( - since_revision: str | None = None, diff_against: str | None = None -) -> List[Path]: +def find_new_migrations(since_revision: str | None = None, diff_against: str | None = None) -> List[Path]: """ 找出需要检查的迁移文件。 优先级:diff_against > since_revision > 全部 @@ -183,9 +179,7 @@ def analyze_migration(file_path: Path) -> Tuple[List[str], List[str], List[str]] def main() -> int: - parser = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter - ) + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( "--since", default=os.getenv("MIGRATION_SINCE_REVISION"), @@ -248,9 +242,7 @@ def main() -> int: print() print("=" * 60) - print( - f"检查结果:{len(all_safe)} 项安全 / {len(all_medium)} 项中风险 / {len(all_high)} 项高风险" - ) + print(f"检查结果:{len(all_safe)} 项安全 / {len(all_medium)} 项中风险 / {len(all_high)} 项高风险") print() if all_high: -- 2.54.0 From f6bfaa5aaf5c86e2a4d94e7f94dbe6911114090b Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 00:45:23 +0800 Subject: [PATCH 23/27] =?UTF-8?q?fix(test):=20=E4=BF=AE=E5=A4=8Dedit=5Fpla?= =?UTF-8?q?n=5Fgeneration=20celery=20mock=E8=B7=AF=E5=BE=84=20+=20asset=5F?= =?UTF-8?q?library=E5=88=A0=E9=99=A4=E6=96=AD=E8=A8=80=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/test_asset_library_delete.py | 2 +- tests/unit/test_edit_plan_generation_api.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) mode change 100755 => 100644 tests/unit/test_edit_plan_generation_api.py diff --git a/tests/unit/test_asset_library_delete.py b/tests/unit/test_asset_library_delete.py index 9c4178be6..3b595b079 100644 --- a/tests/unit/test_asset_library_delete.py +++ b/tests/unit/test_asset_library_delete.py @@ -219,7 +219,7 @@ class TestDeleteAssetLibrary: response = client.delete("/api/v1/asset-libraries/lib-1") assert response.status_code == 403 - assert "Access denied" in response.json()["detail"] + assert "无权访问该项目" in response.json()["detail"] # 库未被删除 assert lib_repo.find_by_id("lib-1") is not None diff --git a/tests/unit/test_edit_plan_generation_api.py b/tests/unit/test_edit_plan_generation_api.py old mode 100755 new mode 100644 index 8f05cd720..1479514d9 --- a/tests/unit/test_edit_plan_generation_api.py +++ b/tests/unit/test_edit_plan_generation_api.py @@ -318,7 +318,7 @@ class TestGeneratePlan: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -406,7 +406,7 @@ class TestGeneratePlan: clip = _make_clip(plan.id, order=1, status=EditPlanClipStatus.READY) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -427,7 +427,7 @@ class TestGeneratePlan: clip = _make_clip(plan.id, order=i + 1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -580,7 +580,7 @@ class TestResponseSchema: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -623,7 +623,7 @@ class TestGeneratePlanErrorHandling: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: # 模拟 Celery 调度失败 mock_celery.send_task.side_effect = RuntimeError("Redis 连接超时") resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -647,7 +647,7 @@ class TestGeneratePlanErrorHandling: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task.side_effect = RuntimeError("调度失败") resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -668,7 +668,7 @@ class TestGeneratePlanErrorHandling: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task.side_effect = ConnectionError("Broker 不可达") resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") -- 2.54.0 From 1c98e6d8d0be21542d38e37dbae62906f73b5271 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 01:17:21 +0800 Subject: [PATCH 24/27] =?UTF-8?q?fix(ci):=20Unit/Integration=20Tests=20?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=85=BC=E5=AE=B9=E4=BF=AE=E5=A4=8D=20-=20ff?= =?UTF-8?q?mpeg=E5=AE=89=E8=A3=85=20+=20OSS=E5=8D=A0=E4=BD=8D=E5=8F=98?= =?UTF-8?q?=E9=87=8F=20+=20pip=E7=BB=9F=E4=B8=80=E7=94=A8python3=20-m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 40 +++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index cadd65963..444a2feff 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -169,6 +169,10 @@ jobs: env: USE_IN_MEMORY_DB: "true" + OSS_ACCESS_KEY_ID: placeholder + OSS_ACCESS_KEY_SECRET: placeholder + OSS_BUCKET: test-bucket + OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com steps: - name: Checkout code @@ -217,6 +221,21 @@ jobs: tar.extract(member, '.') PY + - name: Install ffmpeg + shell: sh + run: | + set -eu + if command -v apt-get > /dev/null 2>&1; then + apt-get update -qq && apt-get install -y -qq ffmpeg + elif command -v yum > /dev/null 2>&1; then + yum install -y -q epel-release 2>/dev/null || true + yum install -y -q ffmpeg 2>/dev/null || (yum install -y -q rpmfusion-free-release && yum install -y -q ffmpeg) + elif command -v dnf > /dev/null 2>&1; then + dnf install -y -q ffmpeg 2>/dev/null || echo "ffmpeg install via dnf failed, continuing" + else + echo "Warning: unknown package manager, skipping ffmpeg install" + fi + - name: Install dependencies shell: sh run: | @@ -260,6 +279,10 @@ jobs: env: DATABASE_URL: postgresql+psycopg://postgres:postgres@127.0.0.1:5432/xiaoxia_saas USE_IN_MEMORY_DB: "false" + OSS_ACCESS_KEY_ID: placeholder + OSS_ACCESS_KEY_SECRET: placeholder + OSS_BUCKET: test-bucket + OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com steps: - name: Checkout code @@ -325,6 +348,21 @@ jobs: python3 -m pip install -q -r requirements-dev.txt pytest --version + - name: Install ffmpeg + shell: sh + run: | + set -eu + if command -v apt-get > /dev/null 2>&1; then + apt-get update -qq && apt-get install -y -qq ffmpeg + elif command -v yum > /dev/null 2>&1; then + yum install -y -q epel-release 2>/dev/null || true + yum install -y -q ffmpeg 2>/dev/null || (yum install -y -q rpmfusion-free-release && yum install -y -q ffmpeg) + elif command -v dnf > /dev/null 2>&1; then + dnf install -y -q ffmpeg 2>/dev/null || echo "ffmpeg install via dnf failed, continuing" + else + echo "Warning: unknown package manager, skipping ffmpeg install" + fi + - name: Start Redis shell: sh run: | @@ -394,7 +432,7 @@ jobs: shell: sh run: | set -eu - pip install -q pytest-rerunfailures + python3 -m pip install -q pytest-rerunfailures PYTHONPATH="$PWD/apps/api:$PWD" python3 -m coverage run --append \ --source=apps/api/app,packages \ --omit="*/migrations/*,*/tests/*,*/test_*.py,*/site-packages/*" \ -- 2.54.0 From 83043ea4f039c3a079d2248a8386053c29f28c13 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 01:26:19 +0800 Subject: [PATCH 25/27] =?UTF-8?q?fix(ci):=20ffmpeg=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E5=AE=B9=E9=94=99=20+=20OSS=E5=8F=98=E9=87=8F=E5=90=8D?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20+=20config=E6=B5=8B=E8=AF=95=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E9=9A=94=E7=A6=BBenv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 46 ++++++++++++++++++++++++++--------- tests/unit/test_config_oss.py | 2 +- 2 files changed, 35 insertions(+), 13 deletions(-) mode change 100755 => 100644 .gitea/workflows/ci-cd.yml mode change 100644 => 100755 tests/unit/test_config_oss.py diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml old mode 100755 new mode 100644 index 444a2feff..50c56a4f7 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -171,7 +171,7 @@ jobs: USE_IN_MEMORY_DB: "true" OSS_ACCESS_KEY_ID: placeholder OSS_ACCESS_KEY_SECRET: placeholder - OSS_BUCKET: test-bucket + OSS_BUCKET_NAME: xiaoxia-autocut OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com steps: @@ -224,16 +224,27 @@ jobs: - name: Install ffmpeg shell: sh run: | - set -eu + set +e + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg already installed: $(ffmpeg -version | head -1)" + exit 0 + fi if command -v apt-get > /dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq ffmpeg elif command -v yum > /dev/null 2>&1; then - yum install -y -q epel-release 2>/dev/null || true - yum install -y -q ffmpeg 2>/dev/null || (yum install -y -q rpmfusion-free-release && yum install -y -q ffmpeg) + yum install -y -q epel-release 2>/dev/null + yum install -y -q ffmpeg 2>/dev/null + if [ $? -ne 0 ] && command -v dnf > /dev/null 2>&1; then + dnf install -y -q --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm 2>/dev/null + dnf install -y -q ffmpeg 2>/dev/null + fi elif command -v dnf > /dev/null 2>&1; then - dnf install -y -q ffmpeg 2>/dev/null || echo "ffmpeg install via dnf failed, continuing" + dnf install -y -q ffmpeg 2>/dev/null + fi + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg installed successfully: $(ffmpeg -version | head -1)" else - echo "Warning: unknown package manager, skipping ffmpeg install" + echo "Warning: ffmpeg installation failed or not available, some tests may be skipped" fi - name: Install dependencies @@ -281,7 +292,7 @@ jobs: USE_IN_MEMORY_DB: "false" OSS_ACCESS_KEY_ID: placeholder OSS_ACCESS_KEY_SECRET: placeholder - OSS_BUCKET: test-bucket + OSS_BUCKET_NAME: xiaoxia-autocut OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com steps: @@ -351,16 +362,27 @@ jobs: - name: Install ffmpeg shell: sh run: | - set -eu + set +e + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg already installed: $(ffmpeg -version | head -1)" + exit 0 + fi if command -v apt-get > /dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq ffmpeg elif command -v yum > /dev/null 2>&1; then - yum install -y -q epel-release 2>/dev/null || true - yum install -y -q ffmpeg 2>/dev/null || (yum install -y -q rpmfusion-free-release && yum install -y -q ffmpeg) + yum install -y -q epel-release 2>/dev/null + yum install -y -q ffmpeg 2>/dev/null + if [ $? -ne 0 ] && command -v dnf > /dev/null 2>&1; then + dnf install -y -q --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm 2>/dev/null + dnf install -y -q ffmpeg 2>/dev/null + fi elif command -v dnf > /dev/null 2>&1; then - dnf install -y -q ffmpeg 2>/dev/null || echo "ffmpeg install via dnf failed, continuing" + dnf install -y -q ffmpeg 2>/dev/null + fi + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg installed successfully: $(ffmpeg -version | head -1)" else - echo "Warning: unknown package manager, skipping ffmpeg install" + echo "Warning: ffmpeg installation failed or not available, some tests may be skipped" fi - name: Start Redis diff --git a/tests/unit/test_config_oss.py b/tests/unit/test_config_oss.py old mode 100644 new mode 100755 index 0ddcad23e..bf6259041 --- a/tests/unit/test_config_oss.py +++ b/tests/unit/test_config_oss.py @@ -37,7 +37,7 @@ def _fresh_settings(**env_overrides: dict[str, str]): "JWT_SECRET_KEY": "unit-test-secret-key-12345", **env_overrides, } - with patch.dict(os.environ, env, clear=False): + with patch.dict(os.environ, env, clear=True): Settings = _load_settings_class() return Settings() -- 2.54.0 From e8fcfe9b19ff7a1b6e5687f9790f28185786e6a3 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 01:32:16 +0800 Subject: [PATCH 26/27] =?UTF-8?q?fix(test):=20OSS=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=87=8D=E7=BD=AEshared=20config=20+=20=E9=9B=86=E6=88=90?= =?UTF-8?q?=E6=B5=8B=E8=AF=95password=5Freset=E8=B7=AF=E5=BE=84=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/integration/test_auth.py | 4 ++-- tests/unit/test_oss_direct_upload.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) mode change 100644 => 100755 tests/unit/test_oss_direct_upload.py diff --git a/tests/integration/test_auth.py b/tests/integration/test_auth.py index 97a8d6d02..36a83d66e 100755 --- a/tests/integration/test_auth.py +++ b/tests/integration/test_auth.py @@ -308,7 +308,7 @@ class TestPasswordReset: ) response = client.post( - "/api/v1/auth/password/forgot", + "/api/v1/auth/forgot-password", json={"email": test_email}, ) @@ -318,7 +318,7 @@ class TestPasswordReset: def test_request_password_reset_nonexistent_user(self): """测试请求不存在的用户密码重置""" response = client.post( - "/api/v1/auth/password/forgot", + "/api/v1/auth/forgot-password", json={"email": "nonexistent@example.com"}, ) diff --git a/tests/unit/test_oss_direct_upload.py b/tests/unit/test_oss_direct_upload.py old mode 100644 new mode 100755 index 57317b1d1..f040501d2 --- a/tests/unit/test_oss_direct_upload.py +++ b/tests/unit/test_oss_direct_upload.py @@ -6,11 +6,13 @@ from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api")) import app.config as app_config +import packages.shared.config as shared_config from app.core.storage import OSSStorageService def _reset_settings() -> None: app_config._settings = None + shared_config._settings = None def test_create_direct_upload_post_limits_key_and_size(monkeypatch): -- 2.54.0 From 0ce0f54a305f6f31233fb924f0da813d4ae4d729 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 01:37:40 +0800 Subject: [PATCH 27/27] style: isort fix test_oss_direct_upload.py --- tests/unit/test_oss_direct_upload.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) mode change 100755 => 100644 tests/unit/test_oss_direct_upload.py diff --git a/tests/unit/test_oss_direct_upload.py b/tests/unit/test_oss_direct_upload.py old mode 100755 new mode 100644 index f040501d2..9f9f7152b --- a/tests/unit/test_oss_direct_upload.py +++ b/tests/unit/test_oss_direct_upload.py @@ -6,9 +6,10 @@ from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api")) import app.config as app_config -import packages.shared.config as shared_config from app.core.storage import OSSStorageService +import packages.shared.config as shared_config + def _reset_settings() -> None: app_config._settings = None -- 2.54.0