Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DEVELOPMENT_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1348,3 +1348,9 @@
- 最终安全交叉复审把静态媒体 CORS 与管理 API 写入 Origin 拆开;抖音/B站创作者中心仍可按白名单读取受控媒体,但不能据此跨站写本地管理 API。
- 新增人工 Job 去重、Worker 完成、事务回滚、旧 lease fencing、DB 已提交后接管、单元 checkpoint 复用/计费不确定、自动 checkpoint 共存、坏长直播结构、质量门禁和父进程收尾测试。最终独立全量验收 `785 passed, 3 deselected`;Ruff、Compileall、`app.js`/`publish-center.js` 语法、三套 Compose 合并配置和 `git diff --check` 全部通过。
- Pytest 使用进程级 `niuma-pytest-*\data\test_workflow.sqlite3`,未触碰活动库。活动服务继续由 `127.0.0.1:8001` 的 Uvicorn PID `56576` 持有;只读数据库检查为 `integrity_check=ok`、`foreign_key_check=0`。活动库哈希随常驻服务 WAL 写入发生变化,未为取得静态哈希而停止正式服务。

## 2026-08-26 修复中文路径视频预检误判

- 修复 Windows 默认 GBK 解码 FFprobe/FFmpeg UTF-8 输出时,中文文件路径触发解码异常并被误报为“源文件没有视频轨”的问题。
- 媒体创建预检与首尾解码抽样统一显式使用 UTF-8,并以替换非法字节的方式保留可诊断输出;真正缺少视频轨或音轨的素材仍继续拒绝创建。
- 增加中文文件名编码回归和真正无视频轨回归测试;不修改上传目录、任务数据库、页面结构或原始视频文件。
7 changes: 7 additions & 0 deletions NEXT_STEPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1104,3 +1104,10 @@
2. 先用一条短测试文字稿执行 Codex 分析,核对候选片段 JSON、时间范围和封面时间;这一步会消耗 Codex 额度,需人工发起。
3. 在发送中心只对一条未发布草稿点击 AI 补齐,确认标题、话题、简介通过现有内容安全规则;不要点击真实发布。
4. 若 Codex 不可用,可在系统状态页手动选择远程 AI 或本地模型;旧 API 配置和历史任务保持不变。

## 2026-08-26 中文路径视频创建验收

1. 打开 `http://127.0.0.1:8001/tasks/new`,选择包含中文目录或中文文件名的正常 MP4 视频。
2. 创建任务时不应再出现“源文件没有视频轨”;系统应继续完成视频轨、音轨、时长、解码能力和磁盘空间预检。
3. 纯音频文件、真正没有视频轨或音轨的素材仍应显示对应拦截原因,不会被当作有效视频任务创建。
4. 本次验收只创建处理任务,不会自动绕过登录、验证码、平台风控或执行真实投稿。
4 changes: 4 additions & 0 deletions app/services/media_preflight_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ def _run_decode_sample(path: Path, start_seconds: float) -> None:
command,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=settings.ffprobe_timeout,
check=False,
)
Expand Down Expand Up @@ -94,6 +96,8 @@ def probe_media(path_value: str | Path) -> dict:
],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=settings.ffprobe_timeout,
check=False,
)
Expand Down
77 changes: 72 additions & 5 deletions tests/test_long_live_foundation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,33 @@
from app.services.transcription_checkpoint_service import TranscriptionCheckpoint


def _ffprobe_payload(*, duration: float = 3600, include_audio: bool = True) -> str:
streams = [
{"codec_type": "video", "codec_name": "h264", "width": 1920, "height": 1080, "avg_frame_rate": "30/1"},
]
def _ffprobe_payload(
*,
duration: float = 3600,
include_video: bool = True,
include_audio: bool = True,
filename: str = "",
) -> str:
streams = []
if include_video:
streams.append(
{
"codec_type": "video",
"codec_name": "h264",
"width": 1920,
"height": 1080,
"avg_frame_rate": "30/1",
}
)
if include_audio:
streams.append({"codec_type": "audio", "codec_name": "aac", "channels": 2, "sample_rate": "48000"})
return json.dumps({"streams": streams, "format": {"duration": str(duration), "size": "1048576"}})
return json.dumps(
{
"streams": streams,
"format": {"duration": str(duration), "size": "1048576", "filename": filename},
},
ensure_ascii=False,
)


def test_media_preflight_collects_streams_and_six_hour_warning(monkeypatch, tmp_path):
Expand Down Expand Up @@ -62,6 +82,53 @@ def test_media_preflight_rejects_missing_audio(monkeypatch, tmp_path):
probe_media(source)


def test_media_preflight_uses_utf8_for_chinese_filename(monkeypatch, tmp_path):
source = tmp_path / "康熙来了.mp4"
source.write_bytes(b"video")
calls = []

def fake_run(command, **kwargs):
calls.append((command, kwargs))
if "-show_streams" in command:
return SimpleNamespace(
returncode=0,
stdout=_ffprobe_payload(duration=20, filename=str(source)),
stderr="",
)
return SimpleNamespace(returncode=0, stdout="", stderr="")

monkeypatch.setattr("app.services.media_preflight_service.shutil.which", lambda name: name)
monkeypatch.setattr("app.services.media_preflight_service.subprocess.run", fake_run)
monkeypatch.setattr(
"app.services.media_preflight_service.shutil.disk_usage",
lambda _path: SimpleNamespace(free=100 * 1024 ** 3),
)

result = preflight_media(source, total_output_limit=12)

assert result.video_codec == "h264"
assert len(calls) == 3
assert all(kwargs["encoding"] == "utf-8" for _command, kwargs in calls)
assert all(kwargs["errors"] == "replace" for _command, kwargs in calls)


def test_media_preflight_rejects_missing_video(monkeypatch, tmp_path):
source = tmp_path / "audio-only.mp4"
source.write_bytes(b"audio")
monkeypatch.setattr("app.services.media_preflight_service.shutil.which", lambda name: name)
monkeypatch.setattr(
"app.services.media_preflight_service.subprocess.run",
lambda *_args, **_kwargs: SimpleNamespace(
returncode=0,
stdout=_ffprobe_payload(include_video=False),
stderr="",
),
)

with pytest.raises(ValueError, match="没有视频轨"):
probe_media(source)


def test_transcription_checkpoint_resumes_and_invalidates_on_source_change(tmp_path):
init_db()
task_id = "checkpoint-foundation-test"
Expand Down