diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce4199c6..9f1ee15e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,19 +2,19 @@ name: CI on: push: - branches: [v2] + branches: [v2, v2.1] pull_request: - branches: [v2] + branches: [v2, v2.1] jobs: lint: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v7 with: version: "latest" @@ -36,27 +36,34 @@ jobs: test: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest + env: + QT_QPA_PLATFORM: offscreen strategy: fail-fast: false matrix: python-version: ["3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v7 with: version: "latest" - name: Set up Python ${{ matrix.python-version }} run: uv python install ${{ matrix.python-version }} + - name: Install Qt runtime dependencies + run: | + sudo apt-get update + sudo apt-get install -y libegl1 + - name: Install dependencies run: uv sync --all-extras - name: Run tests - run: uv run pytest tests/ -v --tb=short + run: make test all-checks-pass: name: All Checks Pass diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..19d61966 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,123 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + ensure-release: + name: Ensure Release + Notes + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Prepare release notes + run: | + cat > RELEASE_NOTES.md <<'EOF' + ## TRDrop v2.1.0-beta.3 Release Notes + + ### Major updates + - Improved FPS/frametime overlay readability across 1/2/3/4-video layouts. + - Fixed multi-video frametime placement so each frametime plot stays within its own video region and no longer overlaps or gets hidden behind the framerate plot. + - Fixed frametime plot rendering so the history curve is visible instead of appearing as an empty chart box. + - Tuned dense 3/4-video layouts by reducing frametime axis label size and making framerate axes/curves thicker for clearer readability. + - Enabled frametime overlay by default and enabled profile generation by default in the main UI/default preset. + - Aligned the default test path with CI by excluding GUI-marked tests from `make test` and adding a separate `make test-gui` target. + - Improved headless Linux CI stability for PyQt-based tests by adding the required Qt/EGL runtime setup. + + ### macOS usage (ZIP-only in this release) + 1. Download and extract `TRDrop-macos-universal.zip`. + 2. In Terminal, run: + `chmod +x TRDrop-macos-universal` + 3. Start the app: + `./TRDrop-macos-universal` + 4. If blocked by Gatekeeper, right-click → Open, or allow it in Privacy & Security. + + --- + + ## TRDrop v2.1.0-beta.3 更新说明 + + ### 主要功能更新 + - 优化了 1/2/3/4 视频布局下 FPS/frametime 图表的可读性。 + - 修复多视频场景下 frametime 图表位置错误的问题,确保每个 frametime 图都显示在各自视频区域内,不再与 framerate 图重叠或被遮挡。 + - 修复 frametime 图表“只有框没有曲线”的问题,历史曲线现在可以正常显示。 + - 针对 3/4 视频密集布局,缩小了 frametime 坐标数字,并加粗了 framerate 坐标线和曲线,显示更清晰。 + - 主界面与默认预设现在默认开启 frametime 图表,并默认勾选 profile 生成功能。 + - 让默认测试路径与 CI 保持一致:`make test` 默认排除 GUI 测试,并新增 `make test-gui` 用于单独运行 GUI 测试。 + - 为无头 Linux 环境下的 PyQt 测试补充了 Qt/EGL 运行依赖,提升 CI 稳定性。 + + ### macOS 使用说明(本次仅提供 ZIP 包) + 1. 下载 `TRDrop-macos-universal.zip` 并解压。 + 2. 终端进入解压目录后执行: + `chmod +x TRDrop-macos-universal` + 3. 启动: + `./TRDrop-macos-universal` + 4. 若被 Gatekeeper 拦截:右键应用选择“打开”,或在「系统设置 → 隐私与安全性」点击“仍要打开”。 + EOF + + - name: Create or update GitHub release (official gh CLI) + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + run: | + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TAG" --notes-file RELEASE_NOTES.md + else + gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TAG" --notes-file RELEASE_NOTES.md + fi + + build-and-upload: + name: Build (${{ matrix.os }}) + needs: ensure-release + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest] + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "latest" + + - name: Set up Python + run: uv python install 3.12 + + - name: Install dependencies + run: uv sync --all-extras + + - name: Build with PyInstaller spec + run: uv run pyinstaller trdrop.spec --noconfirm --clean + + - name: Prepare artifact (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path out | Out-Null + Copy-Item dist/TRDrop.exe out/TRDrop-windows-x64.exe + + - name: Prepare artifact (macOS) + if: runner.os == 'macOS' + shell: bash + run: | + mkdir -p out + cp dist/TRDrop TRDrop-macos-universal + chmod +x TRDrop-macos-universal + zip -9 out/TRDrop-macos-universal.zip TRDrop-macos-universal + + - name: Upload build outputs to release (official gh CLI) + shell: bash + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + run: | + gh release upload "$TAG" out/* --repo "$GITHUB_REPOSITORY" --clobber diff --git a/.gitignore b/.gitignore index 03adb83b..7ca150df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ __pycache__/ *.py[cod] *.so +*.exe .venv/ *.egg-info/ dist/ @@ -20,3 +21,24 @@ examples/*.csv # macOS .DS_Store +*.mp4 +*.csv + +# User preset (use trdrop_preset.example.yaml as template) +trdrop_preset.yaml + +# AI and planning files +trdrop_profile.summary.txt +implementation_plan.md +task.md +walkthrough.md +diff.txt +head_version.py +.cursorrules +.claude/ + +# transient icon conversion workspace +a.iconset/ +*.iconset/ + +!uv.lock diff --git a/HDR_PLAN.md b/HDR_PLAN.md new file mode 100644 index 00000000..dfcd4c90 --- /dev/null +++ b/HDR_PLAN.md @@ -0,0 +1,68 @@ +# TRDrop HDR 支持方案 (HDR_PLAN.md) + +当前 TRDrop 导入 HDR 视频输出泛白、呈灰色的问题,主要是因为视频为 10-bit BT.2020 广色域与 PQ/HLG 伽马曲线,而 TRDrop 整个底层管线在强行按 8-bit BT.709 线性读取与合成。 + +要实现对 HDR 视频的支持,以下是两套完整的技术评估与改造方案: + +--- + +## 方案 A: HDR 转 SDR 映射(Tonemapping)—— 推荐 ✅ + +此方案投入产出比最高,不会破坏现有核心逻辑。只在视频“入口处”做一层滤镜拦截,将 HDR 色调映射(Tone Map)回通用的标准 8-bit SDR。 + +### 1. 适用场景 +* 快速修复“灰屏”问题。 +* 生成的测定视频只需要在普通的手机、显示器、B站上观看,不需要炫技展示原版 HDR 高光。 +* 追求代码修改最小化、性能影响最小化。 + +### 2. 需要改动的代码 +**文件位置**: `src/trdrop/video/reader.py` +**改动内容**: +* 在 PyAV 获取解码帧之前,引入 `av.filter.Graph` 构建一个 FFmpeg 的滤镜网络。 +* 动态检测输入视频的像素格式(如 `yuv420p10le`)。 +* 如果是 HDR 视频,则令其穿过类似 `zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=rgb24` (针对 PQ)的滤镜管道。 +* 输出端直接吐出处理好的 `rgb24` 帧数据,喂给下游。 + +### 3. 后续影响 +* **分析引擎 (`engine`)**: 无需改动,继续用 8-bit `np.uint8`。 +* **合成器 (`compositor/OSD`)**: 无需改动,原本的 (255,255,255) 就是 SDR 体系下的纯白。 +* **导出端 (`export`)**: 无需改动,继续输出 8-bit SDR 的 `yuv420p`。 + +--- + +## 方案 B: 全链路原生 HDR 透传处理(极高难度) + +真正的专业极客向方案,保持原视频的 10-bit 甚至 12-bit 高光动态范围不变,将测评数据(FPS、Frametime、图表)直接在由 `0-65535` 重构出来的广色域空间中绘制进去。 + +### 1. 适用场景 +* 想将最终生成的评测视频放在顶级的 HDR 电视或 OLED 显示器上展示。 +* 保留画质原汁原味的高保真追求。 + +### 2. 大面积的底层重构 +要达成全链路 HDR,以下四个模块全部需要重写: + +#### ⚙️ 第一关:入水管道升维 (`reader.py`) +* 获取数据流时不能再强制转化为 `format="rgb24"`(8-bit)。 +* 必须将其提取为 `rgb48le`(16-bit packed RGB)或是完整的 10-bit `gbrp10le` 平面源。 +* NumPy 的数据结构全面升级为 `dtype=np.uint16`。 +* 必须**提取并保存**原视频的 HDR 静态/动态元数据 (Mastering Display Color Volume, Content Light Level - MaxCLL/MaxFALL)。 + +#### 🔬 第二关:特征对比算法升级 (`analysis`) +* SSIM、MSE 差异比对、Template Matching 等 OpenCV 计算虽然支持 `uint16` 矩阵,但之前基于 `0-255` 取值范围所设定的 `duplicate_threshold`(重复帧阈值)等全部会失效,必须建立两套阈值系统去分别适配 SDR 和 HDR 视频。 + +#### 🎨 第三关:色彩管理与画布重绘 (`compositor`) +* **痛点**:这是最麻烦的地方,必须手动调配 HDR 色彩锚点。 +* 以前你在画布上画一条绿线或写白字,给的颜色是 `(0, 255, 0)` 或 `(255, 255, 255)`。 +* 现在画布变成了一张巨大无比的网 (`0-65535`),且它遵从并不是人眼线性的 PQ/HLG 曲线。 +* 如果你在 PQ 伽马下给一条白线 `(65535, 65535, 65535)`,这根线在屏幕上会放射出极为刺眼的 `10,000 Nits` 光芒。 +* 如果你维持给 `(255, 255, 255)`,那么这根线对应的大概只有 `0.02 Nits`,几乎是**纯黑**的。 +* **改法**:必须依据 PQ Transfer 查表,将 OSD 的 UI 亮度锁定在 “Paper White”(常规漫反射白,约 200 nits)级别,推算出对应的 16-bit 色值(大概是 `~38000` 左右),用这个特调过的色彩常量进行 `cv2.putText` 或曲线绘制。 + +#### 📦 第四关:打包与元数据回填 (`export`) +* 输出部分的代码 `streaming_video.py` 必须将格式切换为 `yuv420p10le` 开始 10-bit 编码压制。 +* 仅仅压制还不够,必须向新容器(Container)和流(Stream)中重新注入刚才第一关提取的 HDR Metadata(Color primaries: BT.2020,Color trc: SMPTE 2084,Color space: BT.2020nc)以及相关的 SEI 信息。如果不加这一步,即便视频有了高动态,所有的播放引擎依旧只会把它当成普通的 SDR 画面来强制暗化映射(继续灰蒙蒙)。 + +--- + +### 推荐落实步骤 +如果你要着手改善工具,**强烈建议从方案 A 开始做起**,几行 PyAV 黑魔法就能搞定入城口的格式化转换;不仅开发极快,还能保证分析算法不会出现断崖式的兼容错误。当你真的有了全链路 C++ 级别重构的耐心,再考虑开启方案 B。 diff --git a/Makefile b/Makefile index 67a2ea37..f411eb2c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: lint test check testvideos clean-testvideos benchmark examples clean-examples clean +.PHONY: lint test test-gui check testvideos clean-testvideos benchmark examples clean-examples clean lint: uv run isort --check-only src tests @@ -6,7 +6,10 @@ lint: uv run basedpyright src tests test: - uv run pytest tests/ --verbose --ignore=tests/benchmarks/ + uv run pytest tests/ --verbose -m "not gui" --ignore=tests/benchmarks/ + +test-gui: + uv run pytest tests/ --verbose -m "gui" test-all: uv run pytest tests/ --verbose diff --git a/README.md b/README.md index 826a44f8..36bd731a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Video framerate, frametime, and screen tearing analysis tool for capture card footage. +Supports up to 4 videos side-by-side comparison. Automatically detects HDR (PQ/HLG) content and tonemaps to SDR for correct output. + ## Setup ```bash @@ -9,17 +11,95 @@ uv sync # runtime only uv sync --all-extras # with dev tools ``` -## Lint & Test +## Run ```bash -make check # run all -make lint # isort, flake8, basedpyright -make test # pytest +uv run trdrop ``` -## Run +## Usage + +### Quick Start + +1. Launch the app, you will see an empty window with a control bar at the top +2. Click **"+"** or drag video files into the window (up to 4 videos) +3. *(Optional)* Click **"Video..."** to set the output video path, click **"CSV..."** to set the metrics export path +4. Click **"Start"** to begin processing +5. Wait for the progress bar to reach 100%, or click **"Pause"** to inspect individual frames + +### Control Bar + +From left to right: + +| Control | Description | +|---------|-------------| +| State Indicator | Colored circle showing engine state (gray=idle, blue=ready, green=processing, orange=paused, purple=complete, red=error) | +| **"+"** | Add video files (MP4, MKV, AVI, MOV, WebM). Supports multiple selection. Max 4 videos | +| **"-"** | Remove the last loaded video | +| Video Count | Shows number of loaded videos (0-4) | +| **"CSV..."** | Set CSV export path. Exports frame-by-frame metrics (fps, frametime, duplicate detection per frame) | +| **"Video..."** | Set video export path. Exports composited video with overlays as MP4 | +| **"Config..."** | Load or save a YAML preset file for overlay configuration (see [Preset Configuration](#preset-configuration)) | +| **"Profile"** | Enable performance profiling. Generates `trdrop_profile.csv` and summary on completion | +| **"Start"** | Begin processing (enabled when videos are loaded) | +| **"Pause"** / **"Resume"** | Pause or resume processing | +| **"Reset"** | Clear everything and return to idle state | +| Seek Spinbox + **"Seek"** | Jump to a specific frame (enabled when paused or completed) | + +### Menu Bar + +- **File > Add Video...** (Ctrl+O / Cmd+O) — Same as "+" button +- **File > Quit** (Ctrl+Q / Cmd+Q) — Exit the application +- **Help > About** — Version info + +### Drag and Drop + +Drag video files directly into the window to load them. Supported formats: MP4, MKV, AVI, MOV, WebM. + +### Workflow + +1. **Load videos** — Use "+" button, menu, or drag-and-drop +2. **Configure exports** — Set CSV and/or video output paths (optional, but at least one is needed for output) +3. **Load preset** — Click "Config..." to load a YAML preset for overlay style customization (optional) +4. **Start processing** — The preview area shows composited frames in real-time with FPS overlay +5. **Inspect frames** — Pause processing, enter a frame number, and click "Seek" to view that frame's metrics +6. **View results** — After completion, check the exported video and/or CSV file + +### Outputs + +- **Video (.mp4)** — Composited video with all loaded videos arranged in a grid, FPS/frametime text overlays, and optional FPS plot +- **CSV (.csv)** — One row per frame per video, columns include: frame_index, is_duplicate, diff_ratio, windowed_fps, smoothed_fps, frametime_ms, etc. + +## Preset Configuration + +TRDrop uses YAML preset files to control overlay appearance. Click **"Config..."** in the control bar: + +- **"Yes"** — Load an existing YAML preset +- **"No"** — Save the default preset as a template to edit + +See `trdrop_preset.example.yaml` for a fully commented example with all available parameters. + +### Key Concepts + +- **Coordinates** are normalized 0.0–1.0 (0.5 = center of the frame) +- **Reference** can be `global` (relative to entire output frame) or `video:N` (relative to video N's region) +- **Colors** are RGBA arrays `[R, G, B, A]` with values 0–255 + +## HDR Support + +TRDrop automatically detects HDR video content (PQ/HLG transfer curves, BT.2020 color primaries) and applies tonemapping to SDR for correct display and export. This includes: + +- PQ EOTF (Perceptual Quantizer) decoding +- BT.2020 to BT.709 color gamut mapping +- Reinhard tonemapping +- BT.709 gamma encoding + +No configuration needed — HDR detection and conversion happen automatically. + +## Lint & Test ```bash -uv run trdrop -uv run python examples/pyav/01_read_video_info.py