From 0f73069eb1bd82a50d2a08ffa5869e2902d44d5b Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 18 Jul 2026 14:21:19 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E6=94=BE=E5=AE=BDingest=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E7=BC=96=E7=A0=81=E6=8B=A6=E6=88=AA=20+=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=80=89=E7=B4=A0=E6=9D=90=E5=8E=BB=E6=8E=89=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0问题:用户上传常见视频格式(HEVC/VP9/AV1等)被ingest直接拦截, 素材库里看不到。渲染引擎才是处理编码差异的地方,ingest层不应该限制。 修改: 1. ingest._is_valid_media(): SUPPORTED_VIDEO_CODECS白名单大幅放宽(20+编码), 遇到非白名单编码只打info日志,不拦截 2. auto_select_video_assets(): 去掉编码过滤逻辑,只保留横竖屏过滤作为优化 3. 更新5个相关单元测试,验证编码不再被过滤 --- apps/api/app/api/routes/_helpers.py | 22 +--------- apps/worker/worker_app/tasks/ingest.py | 38 ++++++++--------- tests/unit/test_auto_select_assets.py | 57 ++++++++++++++++---------- 3 files changed, 55 insertions(+), 62 deletions(-) diff --git a/apps/api/app/api/routes/_helpers.py b/apps/api/app/api/routes/_helpers.py index 651ecb348..d6db4fe37 100755 --- a/apps/api/app/api/routes/_helpers.py +++ b/apps/api/app/api/routes/_helpers.py @@ -66,9 +66,6 @@ def auto_select_video_assets( Returns: 选中的素材 ID 列表,无可用素材时返回空列表 """ - # 明确不支持的视频编码(会导致渲染失败) - UNSUPPORTED_CODECS = {"hevc", "h265", "hev1", "hvc1", "vp9", "vp09", "av1", "av01"} - if not project_id: return [] @@ -96,27 +93,12 @@ def auto_select_video_assets( and a.mime_type.startswith("video") ] - # 过滤不支持的编码格式(HEVC/VP9/AV1 等会导致渲染失败) - # 优先读 asset.codec 字段,其次从 metadata 里取(兼容存量数据) - codec_filtered = [] - skipped_codec = 0 - for a in ready_videos: - codec = (a.codec or "").lower() - if not codec and a.metadata and isinstance(a.metadata, dict): - codec = str(a.metadata.get("codec", "")).lower() - if codec and codec in UNSUPPORTED_CODECS: - skipped_codec += 1 - continue - codec_filtered.append(a) - - if skipped_codec and logger: - logger.warning("自动选素材: 跳过 %d 个不支持编码的素材", skipped_codec) - # 过滤横屏素材(只保留竖屏/正方形) # 移动端短视频场景默认竖屏,横屏素材裁剪后画面不可用 + # 注意:这只是选素材优化,渲染引擎本身支持任何分辨率的素材 filtered_videos = [] skipped_landscape = 0 - for a in codec_filtered: + for a in ready_videos: width = a.width if hasattr(a, "width") and a.width else 0 height = a.height if hasattr(a, "height") and a.height else 0 if not width or not height: diff --git a/apps/worker/worker_app/tasks/ingest.py b/apps/worker/worker_app/tasks/ingest.py index 9a94df088..fd279566d 100755 --- a/apps/worker/worker_app/tasks/ingest.py +++ b/apps/worker/worker_app/tasks/ingest.py @@ -23,24 +23,21 @@ MIN_VIDEO_FILE_SIZE = 1024 # 1KB MIN_AUDIO_FILE_SIZE = 100 # 100B MIN_IMAGE_FILE_SIZE = 100 # 100B -# 支持的视频编码格式(白名单) -# 渲染引擎针对 H.264 优化,其他编码可能导致渲染失败或异常 -SUPPORTED_VIDEO_CODECS = {"h264", "avc1", "avc"} - -# 明确不支持的视频编码(用于日志提示) -UNSUPPORTED_VIDEO_CODECS = { - "hevc", - "h265", - "hev1", - "hvc1", # H.265 / HEVC - "vp9", - "vp09", # VP9 - "av1", - "av01", # AV1 - "vp8", - "vp08", # VP8 - "mpeg4", - "mp4v", # MPEG-4 +# 支持的视频编码格式(白名单,尽可能放宽) +# 渲染引擎会在 concat 前统一转码为 h264,因此只要 ffprobe 能识别的视频编码都允许 ingested +SUPPORTED_VIDEO_CODECS = { + "h264", "avc1", "avc", # H.264 / AVC + "hevc", "h265", "hev1", "hvc1", # H.265 / HEVC + "vp9", "vp09", # VP9 + "av1", "av01", # AV1 + "vp8", "vp08", # VP8 + "mpeg4", "mp4v", # MPEG-4 + "mpeg2video", "mpg2", # MPEG-2 + "wmv2", "wmv1", "vc1", # WMV / VC-1 + "flv1", "flv", "vp6f", # Flash / FLV + "theora", "ogg", # Theora + "prores", "prores_ks", "apcn", "apch", "apco", "apcs", "ap4h", "ap4x", # Apple ProRes + "dnxhd", "dnxhr", # DNxHD / DNxHR } @@ -203,10 +200,11 @@ def _is_valid_media(metadata: dict, media_type: str) -> bool: duration = float(metadata.get("duration", 0)) if size < MIN_VIDEO_FILE_SIZE or duration <= 0: return False - # 编码格式校验:仅支持 H.264,其他编码(HEVC/VP9/AV1等)渲染可能失败 + # 编码格式校验:只排除明确非视频的编码格式,只要 ffprobe 能识别的视频编码都允许 + # 渲染引擎会在 concat 前统一转码为 h264 yuv420p,ingest 层不再做严格的编码拦截 codec = str(metadata.get("codec", "")).lower() if codec and codec not in SUPPORTED_VIDEO_CODECS: - return False + logger.info("检测到非白名单视频编码 %s,仍允许 ingested,渲染层会统一转码", codec) return True if media_type == "audio": duration = float(metadata.get("duration", 0)) diff --git a/tests/unit/test_auto_select_assets.py b/tests/unit/test_auto_select_assets.py index c068be609..ed118e3eb 100755 --- a/tests/unit/test_auto_select_assets.py +++ b/tests/unit/test_auto_select_assets.py @@ -64,7 +64,7 @@ def _make_mock_lib_repo(): class TestAutoSelectVideoAssetsCodecFilter: - """自动选素材 — 编码格式过滤.""" + """自动选素材 — 编码格式不再过滤,渲染引擎统一转码.""" def test_h264_assets_selected(self): """H.264 编码素材 → 正常选中.""" @@ -87,8 +87,8 @@ class TestAutoSelectVideoAssetsCodecFilter: assert "a1" in result assert "a2" in result - def test_hevc_assets_filtered_by_codec_field(self): - """HEVC 编码素材(codec 字段有值) → 被过滤掉.""" + def test_hevc_assets_not_filtered_by_codec_field(self): + """HEVC 编码素材 → 不再被过滤,渲染引擎会统一转码.""" from app.api.routes._helpers import auto_select_video_assets assets = [ @@ -105,11 +105,14 @@ class TestAutoSelectVideoAssetsCodecFilter: asset_repo=asset_repo, ) - assert len(result) == 1 - assert result[0] == "a1" + # 所有编码的素材都保留,不再按编码过滤 + assert len(result) == 3 + assert "a1" in result + assert "a2" in result + assert "a3" in result - def test_hevc_assets_filtered_by_metadata(self): - """HEVC 编码素材(codec 字段为空,但 metadata 里有) → 被过滤掉(兼容存量数据).""" + def test_hevc_assets_not_filtered_by_metadata(self): + """HEVC 编码素材(metadata 里有) → 不再被过滤.""" from app.api.routes._helpers import auto_select_video_assets assets = [ @@ -126,14 +129,14 @@ class TestAutoSelectVideoAssetsCodecFilter: asset_repo=asset_repo, ) - assert len(result) == 1 - assert result[0] == "a1" + # 所有编码的素材都保留 + assert len(result) == 3 def test_codec_field_takes_precedence_over_metadata(self): """codec 字段优先于 metadata 里的 codec.""" from app.api.routes._helpers import auto_select_video_assets - # codec 字段是 h264,但 metadata 里是 hevc → 以 codec 字段为准(应该被选中) + # codec 字段是 h264,但 metadata 里是 hevc → 不影响,都能选中 assets = [ _make_asset("a1", codec="h264", metadata={"codec": "hevc"}), ] @@ -149,8 +152,8 @@ class TestAutoSelectVideoAssetsCodecFilter: assert len(result) == 1 assert result[0] == "a1" - def test_vp9_av1_assets_filtered(self): - """VP9 / AV1 编码素材 → 被过滤掉.""" + def test_vp9_av1_assets_not_filtered(self): + """VP9 / AV1 编码素材 → 不再被过滤,渲染引擎会统一转码.""" from app.api.routes._helpers import auto_select_video_assets assets = [ @@ -167,8 +170,11 @@ class TestAutoSelectVideoAssetsCodecFilter: asset_repo=asset_repo, ) - assert len(result) == 1 - assert result[0] == "a3" + # 所有编码的素材都保留 + assert len(result) == 3 + assert "a1" in result + assert "a2" in result + assert "a3" in result def test_missing_codec_kept(self): """codec 字段和 metadata 里都没有 → 暂时放过(兼容存量数据).""" @@ -190,7 +196,7 @@ class TestAutoSelectVideoAssetsCodecFilter: assert len(result) == 2 def test_codec_case_insensitive(self): - """编码格式过滤不区分大小写.""" + """不同编码格式的素材都保留,不再按编码过滤.""" from app.api.routes._helpers import auto_select_video_assets assets = [ @@ -206,8 +212,10 @@ class TestAutoSelectVideoAssetsCodecFilter: asset_repo=asset_repo, ) - assert len(result) == 1 - assert result[0] == "a2" + # 所有编码的素材都保留 + assert len(result) == 2 + assert "a1" in result + assert "a2" in result def test_only_ready_assets_selected(self): """只有 READY 状态的素材才会被选中.""" @@ -406,15 +414,15 @@ class TestAutoSelectVideoAssetsCodecFilter: assert "landscape_in_meta" not in result assert "portrait_in_meta" in result - def test_codec_and_landscape_combined_filter(self): - """编码过滤 + 横屏过滤同时生效.""" + def test_only_landscape_filtered_codec_not_filtered(self): + """只有横屏过滤生效,编码不再过滤.""" from app.api.routes._helpers import auto_select_video_assets assets = [ _make_asset("good", codec="h264", width=1080, height=1920), - _make_asset("bad_codec", codec="hevc", width=1080, height=1920), + _make_asset("hevc_portrait", codec="hevc", width=1080, height=1920), _make_asset("bad_landscape", codec="h264", width=1920, height=1080), - _make_asset("bad_both", codec="hevc", width=1920, height=1080), + _make_asset("hevc_landscape", codec="hevc", width=1920, height=1080), ] asset_repo = _make_mock_repo(assets) lib_repo = _make_mock_lib_repo() @@ -425,4 +433,9 @@ class TestAutoSelectVideoAssetsCodecFilter: asset_repo=asset_repo, ) - assert result == ["good"] + # 只有横屏被过滤,编码不再过滤 + assert len(result) == 2 + assert "good" in result + assert "hevc_portrait" in result + assert "bad_landscape" not in result + assert "hevc_landscape" not in result -- 2.54.0 From 041218afa69851ceeab2bb35985c947153516f83 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 18 Jul 2026 14:38:37 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(test):=20=E6=9B=B4=E6=96=B0ingest?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E6=A0=A1=E9=AA=8C=E6=B5=8B=E8=AF=95=E4=BB=A5?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E6=94=BE=E5=AE=BD=E5=90=8E=E7=9A=84=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HEVC/H265/VP9/AV1等常见编码从'无效'改为'有效' - 新增未知编码(mpeg4)也放行的测试 - 大小写不敏感测试断言从False改为True --- tests/unit/test_ingest_validation.py | 38 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/tests/unit/test_ingest_validation.py b/tests/unit/test_ingest_validation.py index 205b314e1..c16780248 100755 --- a/tests/unit/test_ingest_validation.py +++ b/tests/unit/test_ingest_validation.py @@ -99,45 +99,55 @@ class TestIsValidMedia: result = _is_valid_media({"size_bytes": 1000}, "text") assert result is False - def test_video_hevc_codec_invalid(self): - """HEVC/H.265 编码视频 → 无效(渲染不支持).""" + def test_video_hevc_codec_valid(self): + """HEVC/H.265 编码视频 → 有效(ingest层不拦截,渲染层统一转码).""" from worker_app.tasks.ingest import _is_valid_media result = _is_valid_media( {"size_bytes": 10 * 1024 * 1024, "duration": 30.0, "codec": "hevc"}, "video", ) - assert result is False + assert result is True - def test_video_h265_codec_invalid(self): - """h265 编码 → 无效.""" + def test_video_h265_codec_valid(self): + """h265 编码 → 有效.""" from worker_app.tasks.ingest import _is_valid_media result = _is_valid_media( {"size_bytes": 10 * 1024 * 1024, "duration": 30.0, "codec": "h265"}, "video", ) - assert result is False + assert result is True - def test_video_vp9_codec_invalid(self): - """VP9 编码 → 无效.""" + def test_video_vp9_codec_valid(self): + """VP9 编码 → 有效.""" from worker_app.tasks.ingest import _is_valid_media result = _is_valid_media( {"size_bytes": 10 * 1024 * 1024, "duration": 30.0, "codec": "vp9"}, "video", ) - assert result is False + assert result is True - def test_video_av1_codec_invalid(self): - """AV1 编码 → 无效.""" + def test_video_av1_codec_valid(self): + """AV1 编码 → 有效.""" from worker_app.tasks.ingest import _is_valid_media result = _is_valid_media( {"size_bytes": 10 * 1024 * 1024, "duration": 30.0, "codec": "av1"}, "video", ) - assert result is False + assert result is True + + def test_video_unknown_codec_valid(self): + """非白名单编码 → 仍有效(只打日志不拦截,渲染层负责统一转码).""" + from worker_app.tasks.ingest import _is_valid_media + + result = _is_valid_media( + {"size_bytes": 10 * 1024 * 1024, "duration": 30.0, "codec": "mpeg4"}, + "video", + ) + assert result is True def test_video_h264_codec_valid(self): """H.264 编码 → 有效.""" @@ -170,14 +180,14 @@ class TestIsValidMedia: assert result is True def test_video_codec_case_insensitive(self): - """编码格式校验不区分大小写.""" + """编码格式校验不区分大小写,HEVC大写也识别为有效.""" from worker_app.tasks.ingest import _is_valid_media result = _is_valid_media( {"size_bytes": 10 * 1024 * 1024, "duration": 30.0, "codec": "HEVC"}, "video", ) - assert result is False + assert result is True # ── extract_media_metadata 返回值签名测试 ──────────────────────────────────── -- 2.54.0 From 2062889828e909cbf2349cdc0923de51f42ad856 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 18 Jul 2026 14:40:46 +0800 Subject: [PATCH 3/3] =?UTF-8?q?style:=20black=E6=A0=BC=E5=BC=8F=E5=8C=96in?= =?UTF-8?q?gest.py=E7=99=BD=E5=90=8D=E5=8D=95=E9=9B=86=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/worker/worker_app/tasks/ingest.py | 47 +++++++++++++++++++------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/apps/worker/worker_app/tasks/ingest.py b/apps/worker/worker_app/tasks/ingest.py index fd279566d..a3e001e01 100755 --- a/apps/worker/worker_app/tasks/ingest.py +++ b/apps/worker/worker_app/tasks/ingest.py @@ -26,18 +26,41 @@ MIN_IMAGE_FILE_SIZE = 100 # 100B # 支持的视频编码格式(白名单,尽可能放宽) # 渲染引擎会在 concat 前统一转码为 h264,因此只要 ffprobe 能识别的视频编码都允许 ingested SUPPORTED_VIDEO_CODECS = { - "h264", "avc1", "avc", # H.264 / AVC - "hevc", "h265", "hev1", "hvc1", # H.265 / HEVC - "vp9", "vp09", # VP9 - "av1", "av01", # AV1 - "vp8", "vp08", # VP8 - "mpeg4", "mp4v", # MPEG-4 - "mpeg2video", "mpg2", # MPEG-2 - "wmv2", "wmv1", "vc1", # WMV / VC-1 - "flv1", "flv", "vp6f", # Flash / FLV - "theora", "ogg", # Theora - "prores", "prores_ks", "apcn", "apch", "apco", "apcs", "ap4h", "ap4x", # Apple ProRes - "dnxhd", "dnxhr", # DNxHD / DNxHR + "h264", + "avc1", + "avc", # H.264 / AVC + "hevc", + "h265", + "hev1", + "hvc1", # H.265 / HEVC + "vp9", + "vp09", # VP9 + "av1", + "av01", # AV1 + "vp8", + "vp08", # VP8 + "mpeg4", + "mp4v", # MPEG-4 + "mpeg2video", + "mpg2", # MPEG-2 + "wmv2", + "wmv1", + "vc1", # WMV / VC-1 + "flv1", + "flv", + "vp6f", # Flash / FLV + "theora", + "ogg", # Theora + "prores", + "prores_ks", + "apcn", + "apch", + "apco", + "apcs", + "ap4h", + "ap4x", # Apple ProRes + "dnxhd", + "dnxhr", # DNxHD / DNxHR } -- 2.54.0