fix(studio): 回放视图里选了不是记录的视频时,明确说明原因并给出可行路径
- What: `_load_replay_video()` 先用与 `list_recordings()` 相同的判据确认"这是一条记录"(视频同目录 必须有 `forces.csv`);不是记录时不再把该目录当成记录打开,而是给出原因(状态栏 + 2D 面板常驻文案)、 在 rail 上保留该视频路径,并把「从视频重算力值」置为可用;`_can_recompute()` 统一"重算有没有东西可读" (记录或已选视频),`_sync_menu_checks`/`_refresh_replay_state`/`_on_recompute_toggled` 共用它; 回放按钮在"已选视频但不是记录"时给出对应 tooltip;`_start_playback()` 遇到零长度时间轴时改为明确提示, 不再静默取消勾选。 - Why: 用户报告"历史回放面板,选择视频后按回放键没有反应"。复现确认:`RecordingReader` 容忍空目录, 所以随便一段视频所在目录都会被"成功"打开成一条空记录,`duration_s()==0`,按回放→`_start_playback` 直接静默 return——按钮点了像没点。一条记录的定义(录像 + forces.csv + session.json)此前只写在 `list_recordings` 里,回放入口没有用同一个判据。 - Verification: 复现脚本三种状态(未选/裸视频/记录内视频)行为符合预期;新增三项 UI 用例 (裸视频被拒且说明原因并能重算、记录内视频可打开且能播、缺视频段的记录给出提示); apps/studio 全量通过;ui_check 576 项 0 失败(新增 6 项覆盖同一行为);locale_check 0 缺失。 - Not in this change: 让回放直接播放"非记录"的视频(那需要一条没有力值的纯视频通道;现在用「从视频重算」 覆盖这个需求);`RecordingReader` 对空目录的容忍行为(它要允许正在写入、尚未写 session.json 的记录)。
This commit is contained in:
@@ -2992,6 +2992,61 @@ def main() -> int:
|
||||
f"{replay_window.toolbar.source_summary.text()!r}",
|
||||
)
|
||||
|
||||
# A video that is not a recording must be refused *with a reason*, because the
|
||||
# reader tolerates an empty folder: accepting it left the transport playing a
|
||||
# zero-length timeline, which looked like "pressing 回放 does nothing".
|
||||
import cv2 as _cv2
|
||||
import numpy as _np
|
||||
|
||||
bare_video = replay_root / "bare_video.mp4"
|
||||
bare_writer = _cv2.VideoWriter(
|
||||
str(bare_video), _cv2.VideoWriter_fourcc(*"mp4v"), 30.0, (64, 48)
|
||||
)
|
||||
for _index in range(10):
|
||||
bare_writer.write(_np.zeros((48, 64, 3), dtype=_np.uint8))
|
||||
bare_writer.release()
|
||||
other_window = StudioMainWindow(
|
||||
prefs={"language": "zh_CN", "theme": "dark", "sensor_type": "box"}
|
||||
)
|
||||
other_window.resize(1280, 720)
|
||||
other_window.show()
|
||||
app.processEvents()
|
||||
other_window.brand_bar.btn_playback.click()
|
||||
app.processEvents()
|
||||
check(
|
||||
other_window._load_replay_video(bare_video) is False,
|
||||
"a video with no forces.csv beside it was accepted as a recording",
|
||||
)
|
||||
app.processEvents()
|
||||
check(
|
||||
not other_window.replay.is_loaded(),
|
||||
"an empty folder was opened as a recording",
|
||||
)
|
||||
check(
|
||||
not other_window.toolbar.btn_run.isEnabled(),
|
||||
"the transport is offered although there is nothing to play",
|
||||
)
|
||||
check(
|
||||
"forces.csv" in other_window.toolbar.btn_run.toolTip()
|
||||
or "记录" in other_window.toolbar.btn_run.toolTip(),
|
||||
f"the transport does not say why it cannot play: "
|
||||
f"{other_window.toolbar.btn_run.toolTip()!r}",
|
||||
)
|
||||
check(
|
||||
other_window.act_recompute.isEnabled(),
|
||||
"a chosen video cannot be recomputed either: the operator has no way to view it",
|
||||
)
|
||||
check(
|
||||
"forces.csv" in other_window.arrow_panel.image_label.text()
|
||||
or "记录" in other_window.arrow_panel.image_label.text(),
|
||||
f"the panel does not say what is missing: "
|
||||
f"{other_window.arrow_panel.image_label.text()!r}",
|
||||
)
|
||||
other_window.close()
|
||||
app.processEvents()
|
||||
replay_window.activateWindow()
|
||||
app.processEvents()
|
||||
|
||||
replay = replay_window.replay
|
||||
replay_plots = replay_window.force_plots
|
||||
check(
|
||||
|
||||
Reference in New Issue
Block a user