diff --git a/frontend/app.js b/frontend/app.js index 636df0d..6c67b1e 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -77,7 +77,14 @@ function renderPreview(file) { imagePreview.hidden = true; openSourceButton.hidden = false; if (file.type.startsWith("video/")) { + videoPreview.preload = "metadata"; + videoPreview.onloadedmetadata = () => { + if (Number.isFinite(videoPreview.duration) && videoPreview.duration > 0.2) { + videoPreview.currentTime = 0.1; + } + }; videoPreview.src = currentObjectUrl; + videoPreview.load(); videoPreview.hidden = false; openSourceButton.textContent = "放大查看"; sourcePaneTitle.textContent = "查看原始视频"; @@ -280,7 +287,14 @@ openSourceButton.addEventListener("click", () => { sourceVideo.hidden = true; sourceImage.hidden = true; if (selectedFile.type.startsWith("video/")) { + sourceVideo.preload = "metadata"; + sourceVideo.onloadedmetadata = () => { + if (Number.isFinite(sourceVideo.duration) && sourceVideo.duration > 0.2) { + sourceVideo.currentTime = 0.1; + } + }; sourceVideo.src = currentObjectUrl; + sourceVideo.load(); sourceVideo.hidden = false; } else { sourceImage.src = currentObjectUrl; diff --git a/scripts/generate_sample.py b/scripts/generate_sample.py index e089c73..552fa67 100644 --- a/scripts/generate_sample.py +++ b/scripts/generate_sample.py @@ -1,6 +1,8 @@ from __future__ import annotations from pathlib import Path +import shutil +import subprocess import cv2 import numpy as np @@ -44,15 +46,45 @@ def make_frame(index: int, width: int = 640, height: int = 420) -> np.ndarray: def main() -> None: SAMPLE_DIR.mkdir(parents=True, exist_ok=True) video_path = SAMPLE_DIR / "synthetic_guidewire.mp4" + raw_video_path = SAMPLE_DIR / "synthetic_guidewire.raw.mp4" image_path = SAMPLE_DIR / "synthetic_guidewire.png" width, height = 640, 420 - writer = cv2.VideoWriter(str(video_path), cv2.VideoWriter_fourcc(*"mp4v"), 12.0, (width, height)) + writer = cv2.VideoWriter(str(raw_video_path), cv2.VideoWriter_fourcc(*"mp4v"), 12.0, (width, height)) for index in range(72): frame = make_frame(index, width, height) if index == 12: cv2.imwrite(str(image_path), frame) writer.write(frame) writer.release() + + ffmpeg = shutil.which("ffmpeg") + if ffmpeg: + subprocess.run( + [ + ffmpeg, + "-y", + "-i", + str(raw_video_path), + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + "-preset", + "veryfast", + "-crf", + "20", + str(video_path), + ], + check=True, + capture_output=True, + text=True, + ) + raw_video_path.unlink(missing_ok=True) + else: + raw_video_path.replace(video_path) + print(video_path) print(image_path) diff --git a/storage/samples/synthetic_guidewire.mp4 b/storage/samples/synthetic_guidewire.mp4 index 3612147..d917b35 100644 Binary files a/storage/samples/synthetic_guidewire.mp4 and b/storage/samples/synthetic_guidewire.mp4 differ diff --git a/工程分析/实现方案-2026-05-18-19-41-29.md b/工程分析/实现方案-2026-05-18-19-41-29.md new file mode 100644 index 0000000..053d9ac --- /dev/null +++ b/工程分析/实现方案-2026-05-18-19-41-29.md @@ -0,0 +1,24 @@ +# 实现方案 + +开始时间:2026-05-18-19-41-29 + +## 修改内容 + +1. `scripts/generate_sample.py` + - 先用 OpenCV 生成临时 `mp4v` 视频。 + - 如果系统存在 `ffmpeg`,自动转码为 `libx264`、`yuv420p`、`faststart` 的浏览器友好 MP4。 + - 如果没有 `ffmpeg`,退回保留 OpenCV 输出,保证脚本仍可运行。 + +2. `frontend/app.js` + - 视频预览设置 `preload = "metadata"`。 + - 设置 `src` 后调用 `load()`。 + - `loadedmetadata` 后尝试跳到 `0.1s`,帮助浏览器绘制首个可见帧。 + - 原始视频弹窗也复用相同逻辑。 + +3. 重新生成样例 + - 执行 `bash scripts/generate_sample.sh`。 + - 使用 `ffprobe` 确认编码为 H.264。 + +## 说明 + +这次问题不是用户操作错误,而是样例视频编码格式与浏览器播放兼容性不够稳。 diff --git a/工程分析/测试方案-2026-05-18-19-41-29.md b/工程分析/测试方案-2026-05-18-19-41-29.md new file mode 100644 index 0000000..fc23c63 --- /dev/null +++ b/工程分析/测试方案-2026-05-18-19-41-29.md @@ -0,0 +1,27 @@ +# 测试方案 + +开始时间:2026-05-18-19-41-29 + +## 测试项 + +- `bash scripts/generate_sample.sh` +- `ffprobe` 检查 `storage/samples/synthetic_guidewire.mp4` 编码。 +- `pytest -q` +- `curl -s http://127.0.0.1:8001/api/health` +- `curl -s http://127.0.0.1:8001/api/samples` +- 使用样例视频调用 `/api/segment`,确认后端仍可读取视频。 + +## 验收标准 + +- 样例视频编码为 H.264 或至少返回 `codec_tag_string=avc1`。 +- 点击“加载样例”后左侧原始视频面板能显示视频画面。 +- 分割接口仍可处理样例视频。 + +## 执行结果 + +- `bash scripts/generate_sample.sh`:通过,重新生成样例视频和样例图像。 +- `ffprobe storage/samples/synthetic_guidewire.mp4`:通过,返回 `codec_name=h264`、`codec_tag_string=avc1`、`pix_fmt=yuv420p`。 +- `pytest -q`:通过,4 个测试全部通过。 +- `/api/samples`:通过,返回新的样例视频大小 `638484` 字节。 +- `/api/segment` 使用新样例视频:通过,返回 `job_id=da013f73c636` 和 3 帧分割结果。 +- `git diff --check`:通过,无空白格式错误。 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index b247582..871a714 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.md @@ -93,3 +93,15 @@ B. 产生问题原因:上一版将原始媒体查看入口作为辅助操作 C. 解决问题方案:将右侧工作区拆成左右两个 `workspace-pane`:左侧固定展示原始视频/图像,右侧展示分割进度、摘要和结果帧;保留“放大查看”作为左侧面板内的辅助操作。 D. 后续如何避免问题:医学影像交互页面应优先考虑“原始输入”和“算法输出”并列对照,而不是把原始输入藏在结果区域的附属操作中。 + +## 2026-05-18-19-41-29 样例视频浏览器不显示画面 + +### 1. 样例视频控件显示但画面不渲染 + +A. 具体问题:用户点击“加载样例”后,左侧“查看原始视频”出现视频控件,但看不到原始视频画面。 + +B. 产生问题原因:内置样例视频由 OpenCV 直接写出,编码为 `mpeg4/mp4v`;Chrome 对这种 MP4 兼容性不如 H.264 稳定,可能只显示控件不显示画面。 + +C. 解决问题方案:修改样例生成脚本,生成后用 `ffmpeg` 转码为 `libx264`、`yuv420p`、`faststart` MP4;前端视频加载后调用 `load()` 并在 metadata 加载后轻微 seek 到 `0.1s`。 + +D. 后续如何避免问题:面向网页播放的样例视频统一转为 H.264/yuv420p,并用 Chrome 实际播放或截图验证,而不是只验证 OpenCV 能读取。 diff --git a/工程分析/需求分析-2026-05-18-19-41-29.md b/工程分析/需求分析-2026-05-18-19-41-29.md new file mode 100644 index 0000000..8c3131a --- /dev/null +++ b/工程分析/需求分析-2026-05-18-19-41-29.md @@ -0,0 +1,23 @@ +# 需求分析 + +开始时间:2026-05-18-19-41-29 + +## 用户反馈 + +用户点击“加载样例”后,左侧“查看原始视频”面板出现视频控件,但没有显示原始视频画面。 + +## 问题判断 + +通过 `ffprobe` 检查当前样例视频: + +- `codec_name=mpeg4` +- `codec_tag_string=mp4v` + +Chrome 对 OpenCV 直接写出的 `mp4v` MP4 兼容性不稳定,可能出现视频控件可见但首帧/画面不渲染的情况。 + +## 本轮目标 + +- 将内置样例视频改为浏览器兼容性更好的 H.264/yuv420p。 +- 重新生成 `storage/samples/synthetic_guidewire.mp4`。 +- 前端加载视频后主动触发 `load()` 并轻微 seek,帮助浏览器显示首帧。 +- 验证样例视频编码和系统功能。