mirror of
https://github.com/chrisbenincasa/tunarr.git
synced 2026-04-18 09:03:35 -04:00
chore: add vanilla settings and basic commands for claude
This commit is contained in:
20
.claude/commands/check.md
Normal file
20
.claude/commands/check.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
allowed-tools: Bash(pnpm turbo:*), Bash(pnpm lint-changed:*)
|
||||
description: Run typechecking, lint, and tests across all packages
|
||||
---
|
||||
|
||||
## Your task
|
||||
|
||||
Run the full static analysis and test suite for the monorepo:
|
||||
|
||||
1. **Typecheck all packages** — run `pnpm turbo typecheck`
|
||||
2. **Lint changed files** — run `pnpm lint-changed`
|
||||
3. **Run all tests** — run `pnpm turbo test`
|
||||
|
||||
Run these in sequence. After all three complete, summarize:
|
||||
- Any type errors (package, file, line)
|
||||
- Any lint errors or warnings
|
||||
- Any test failures (suite, test name, error)
|
||||
- Overall pass/fail status
|
||||
|
||||
If everything is clean, say so clearly. Do not attempt to fix errors unless the user asks.
|
||||
21
.claude/commands/new-migration.md
Normal file
21
.claude/commands/new-migration.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
allowed-tools: Bash(cd server && pnpm drizzle-kit:*), Bash(git diff:*), Bash(git status:*)
|
||||
description: Generate a new Drizzle ORM migration after schema changes
|
||||
argument-hint: Brief migration description (e.g. "add-channel-tags")
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
- Current schema changes: !`git diff --name-only HEAD | grep -E "^server/src/db/schema/"`
|
||||
- Existing migrations: !`ls server/src/migration/`
|
||||
|
||||
## Your task
|
||||
|
||||
A schema change has been made and needs a migration generated.
|
||||
|
||||
1. Confirm there are uncommitted schema changes under `server/src/db/schema/`. If there are none, tell the user and stop.
|
||||
2. Run `cd server && pnpm drizzle-kit generate` to generate the migration.
|
||||
3. Show the contents of the newly created migration file.
|
||||
4. Remind the user to review the migration before committing — Drizzle sometimes generates destructive statements (DROP COLUMN, DROP TABLE) that need manual adjustment.
|
||||
|
||||
Migration name hint from arguments: $ARGUMENTS
|
||||
213
.claude/commands/pipeline-feature.md
Normal file
213
.claude/commands/pipeline-feature.md
Normal file
@@ -0,0 +1,213 @@
|
||||
---
|
||||
description: Add a feature to the FFmpeg transcode pipeline following Tunarr's pipeline builder pattern
|
||||
argument-hint: Brief description of the feature (e.g. "crop filter", "AV1 encoder", "noise reduction filter")
|
||||
---
|
||||
|
||||
# Pipeline Feature Development
|
||||
|
||||
You are helping a developer add a feature to Tunarr's FFmpeg transcode pipeline.
|
||||
|
||||
Feature request: $ARGUMENTS
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Understand the request
|
||||
|
||||
Determine which pipeline primitive this feature maps to. Ask the user if unclear:
|
||||
|
||||
- **Filter** — a video or audio processing step (scale, deinterlace, denoise, crop, overlay, etc.)
|
||||
- **Encoder** — a new output codec (video or audio)
|
||||
- **Decoder** — a new input codec handler
|
||||
- **Input option** — a per-input CLI flag (e.g. `-readrate`, `-reconnect`)
|
||||
- **Global option** — a top-level ffmpeg flag (e.g. `-thread_queue_size`)
|
||||
- **Hardware acceleration variant** — a new hwaccel backend for an existing feature
|
||||
|
||||
Also confirm: does this apply to **software only**, or does it need **hardware variants** (VAAPI, NVIDIA/CUDA, QSV, VideoToolbox)?
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Explore relevant existing code
|
||||
|
||||
Before writing anything, read the existing implementation of the nearest equivalent feature to understand conventions. Use these as canonical references:
|
||||
|
||||
| What you're adding | Reference implementation to read first |
|
||||
|---|---|
|
||||
| Software video filter | `server/src/ffmpeg/builder/filter/ScaleFilter.ts` |
|
||||
| Software audio filter | `server/src/ffmpeg/builder/filter/LoudnormFilter.ts` |
|
||||
| VAAPI hardware filter | `server/src/ffmpeg/builder/filter/vaapi/ScaleVaapiFilter.ts` |
|
||||
| NVIDIA hardware filter | `server/src/ffmpeg/builder/filter/nvidia/ScaleCudaFilter.ts` |
|
||||
| Software video encoder | `server/src/ffmpeg/builder/encoder/Libx264Encoder.ts` |
|
||||
| Hardware encoder (VAAPI) | `server/src/ffmpeg/builder/encoder/vaapi/H264VaapiEncoder.ts` |
|
||||
| Decoder | `server/src/ffmpeg/builder/decoder/H264Decoder.ts` |
|
||||
| Input option | `server/src/ffmpeg/builder/options/input/HttpReconnectInputOption.ts` |
|
||||
| Global option | `server/src/ffmpeg/builder/options/GlobalOption.ts` |
|
||||
|
||||
Also read the integration point where the equivalent feature is wired in:
|
||||
- Software filters: `server/src/ffmpeg/builder/pipeline/software/SoftwarePipelineBuilder.ts`
|
||||
- Hardware filters: the relevant builder in `server/src/ffmpeg/builder/pipeline/hardware/`
|
||||
- Encoders/decoders: `server/src/ffmpeg/builder/decoder/DecoderFactory.ts` and the relevant pipeline builder's `setupEncoder()`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Clarify before implementing
|
||||
|
||||
Before writing code, present any open questions:
|
||||
|
||||
- Does this filter modify the frame format? (affects `FrameState` — resolution, pixel format, color space, codec)
|
||||
- Should it be conditional (e.g. only when HDR is detected, only when deinterlacing is enabled)?
|
||||
- What pipeline position does it belong in? (before/after scale, before/after encoder, etc.)
|
||||
- Is it gated by a user-facing config option, or always applied?
|
||||
- For hardware features: should it fall back to software if the hwaccel device doesn't support it?
|
||||
|
||||
Wait for answers before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Implement
|
||||
|
||||
### Adding a filter
|
||||
|
||||
1. Create the filter class in the correct directory:
|
||||
- Software: `server/src/ffmpeg/builder/filter/MyFilter.ts`
|
||||
- VAAPI: `server/src/ffmpeg/builder/filter/vaapi/MyVaapiFilter.ts`
|
||||
- NVIDIA: `server/src/ffmpeg/builder/filter/nvidia/MyCudaFilter.ts`
|
||||
- QSV: `server/src/ffmpeg/builder/filter/qsv/MyQsvFilter.ts`
|
||||
|
||||
2. Extend the correct base:
|
||||
- `FilterOption` for software filters
|
||||
- `HardwareFilterOption` for hardware filters (manage pre/post processing filters)
|
||||
|
||||
3. Implement the required contract:
|
||||
- `get filter(): string` — the ffmpeg filter expression
|
||||
- `affectsFrameState: boolean` — set `true` if this changes resolution, pixel format, or color format
|
||||
- `nextState(currentState: FrameState): FrameState` — if `affectsFrameState`, return the updated state using `currentState.update({ ... })`
|
||||
|
||||
4. Integrate into the pipeline builder. **Critical: FrameState must be threaded correctly** — each filter receives the state from the previous filter and returns the next:
|
||||
```typescript
|
||||
// In SoftwarePipelineBuilder.setupVideoFilters():
|
||||
if (shouldApplyMyFilter) {
|
||||
const f = new MyFilter(params);
|
||||
filterChain.videoFilterSteps.push(f);
|
||||
if (f.affectsFrameState) {
|
||||
currentFrameState = f.nextState(currentFrameState);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. If the filter needs hardware variants, implement those separately and integrate them in the respective hardware pipeline builder (`VaapiPipelineBuilder`, `NvidiaPipelineBuilder`, `QsvPipelineBuilder`). Each hardware builder inherits from `SoftwarePipelineBuilder` and overrides `setupVideoFilters()`.
|
||||
|
||||
### Adding an encoder
|
||||
|
||||
1. Create the encoder in `server/src/ffmpeg/builder/encoder/` (or the hardware subdirectory).
|
||||
2. Extend `VideoEncoder` (or `AudioEncoder`).
|
||||
3. Implement:
|
||||
- `readonly name: string` — the ffmpeg codec name (e.g. `'libsvtav1'`)
|
||||
- `readonly videoFormat: VideoFormat` — output format (e.g. `'av1'`)
|
||||
- `options(): string[]` — codec flags and options
|
||||
4. Wire into the pipeline builder's `setupEncoder()` method, selecting this encoder when the target format matches.
|
||||
|
||||
### Adding a decoder
|
||||
|
||||
1. Create in `server/src/ffmpeg/builder/decoder/`.
|
||||
2. Extend `Decoder` (which extends `InputOption`).
|
||||
3. Implement `readonly name: string`.
|
||||
4. Register in `DecoderFactory` for the relevant codec and hardware mode.
|
||||
|
||||
### Adding an input or global option
|
||||
|
||||
1. Create in `server/src/ffmpeg/builder/options/input/` or `server/src/ffmpeg/builder/options/`.
|
||||
2. Extend `InputOption` or `ConstantGlobalOption`.
|
||||
3. Implement `options()` returning the CLI arg array.
|
||||
4. Add to the pipeline in `BasePipelineBuilder.fromContext()` or the relevant setup method.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Write tests
|
||||
|
||||
Every new filter, encoder, or pipeline integration needs two layers of tests.
|
||||
|
||||
### Unit tests
|
||||
|
||||
Place alongside the implementation file (e.g. `MyFilter.test.ts` next to `MyFilter.ts`). These run without real FFmpeg or hardware.
|
||||
|
||||
**What to test for a filter:**
|
||||
- The `filter` string is correct for each relevant input scenario (different resolutions, pixel formats, frame locations)
|
||||
- If `affectsFrameState` is `true`, assert that `nextState()` returns the expected `FrameState` fields
|
||||
- If the filter handles hardware frame location (CPU vs GPU), test both paths — the filter should emit a `hwdownload` prefix when frames are on the hardware device
|
||||
|
||||
Use `PadFilter.test.ts` as the reference — it covers software, VAAPI 8-bit, VAAPI 10-bit, and CUDA variants with explicit `FrameState` construction.
|
||||
|
||||
**What to test for a pipeline builder integration:**
|
||||
- Build a pipeline with mocked/stub capabilities and assert that the generated command args (`pipeline.getCommandArgs()`) contain the expected filter or option in the expected position
|
||||
- Test the conditional branches: feature enabled, feature disabled, HDR vs SDR, hardware available vs unavailable
|
||||
|
||||
Use `VaapiPipelineBuilder.test.ts` as the reference — it constructs `FfmpegState` and `FrameState` directly and asserts on specific filter types appearing in the pipeline.
|
||||
|
||||
**Test setup conventions:**
|
||||
- Use `FrameState` and `FfmpegState` constructors directly — no DI required
|
||||
- Create fake `FfmpegCapabilities` via `EmptyFfmpegCapabilities` or stub the relevant `canHandle*` methods
|
||||
- Use `VideoStream.create(...)` with explicit fields rather than probing real files
|
||||
|
||||
### Live integration tests
|
||||
|
||||
Place in the same directory as the pipeline builder test, with a `.local.test.ts` suffix (e.g. `SoftwarePipeline.local.test.ts`). These run actual FFmpeg against real fixture files and verify the output is valid media.
|
||||
|
||||
**Setup:**
|
||||
- Import `binaries`, `Fixtures`, `vaapiTest`/`nvidiaTest`/`qsvTest` from `server/src/testing/ffmpeg/FfmpegTestFixtures.ts`
|
||||
- Use `createTempWorkdir()` from `server/src/testing/ffmpeg/FfmpegIntegrationHelper.ts` for output paths — always clean up in `afterEach`
|
||||
- Cap test duration to 1 second using `dayjs.duration(1, 'second')` to keep the suite fast
|
||||
- Use `deriveVideoStreamForFixture(Fixtures.video1080p)` to get a real `VideoInputSource` with correct stream metadata
|
||||
|
||||
**Hardware test skipping:**
|
||||
- Hardware tests must use the extended test fixtures (`vaapiTest`, `nvidiaTest`, `qsvTest`) which auto-skip if the hardware device is not available
|
||||
- Never use a plain `test()` for hardware paths — it will fail in CI
|
||||
|
||||
**What to assert:**
|
||||
- `runFfmpegWithPipeline()` exits with code `0`
|
||||
- `probeFile()` on the output file confirms the expected codec, resolution, or pixel format was applied
|
||||
- If the feature is conditional (e.g. only fires on HDR input), test with `Fixtures.videoHevc1080p` (HDR) and a non-HDR fixture and assert the difference
|
||||
|
||||
**Available fixtures** (`server/src/testing/ffmpeg/fixtures/`):
|
||||
- `Fixtures.video720p` — 720p H.264
|
||||
- `Fixtures.video1080p` — 1080p H.264
|
||||
- `Fixtures.video480p43` — 480p H.264 4:3
|
||||
- `Fixtures.videoHevc720p` / `Fixtures.videoHevc1080p` — HDR10 HEVC
|
||||
- `Fixtures.watermark` / `Fixtures.blackWatermark` — watermark PNGs
|
||||
|
||||
**Environment variables for local hardware testing:**
|
||||
- `TUNARR_TEST_FFMPEG` / `TUNARR_TEST_FFPROBE` — override binary paths
|
||||
- `TUNARR_TEST_VAAPI_DEVICE` — override VAAPI device (default `/dev/dri/renderD128`)
|
||||
|
||||
### Running the tests
|
||||
|
||||
```bash
|
||||
# Unit tests only (fast, no hardware required)
|
||||
cd server && pnpm test src/ffmpeg/
|
||||
|
||||
# Single test file
|
||||
cd server && pnpm test src/ffmpeg/builder/filter/MyFilter.test.ts
|
||||
|
||||
# Integration tests (requires real FFmpeg and optionally hardware)
|
||||
cd server && pnpm test src/ffmpeg/builder/pipeline/software/SoftwarePipeline.local.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Verify
|
||||
|
||||
After implementing and writing tests:
|
||||
|
||||
1. Run `cd server && pnpm typecheck` — the pipeline step type system is strict and will catch interface mismatches.
|
||||
2. Run `cd server && pnpm test src/ffmpeg/` to confirm all unit tests pass.
|
||||
3. If adding a hardware variant, confirm the software path is still exercised correctly when hardware is unavailable — hardware builders fall back through `SoftwarePipelineBuilder`.
|
||||
4. Confirm `FrameState` threading is correct: trace through `setupVideoFilters()` and verify state is updated after every filter that sets `affectsFrameState = true`.
|
||||
|
||||
---
|
||||
|
||||
## Key architecture reminders
|
||||
|
||||
- **Never mutate `FrameState` directly** — always use `currentState.update({ ... })` which returns a new instance.
|
||||
- **`FilterChain` has four slots**: `videoFilterSteps`, `subtitleOverlayFilterSteps`, `watermarkOverlayFilterSteps`, `pixelFormatFilterSteps` — put filters in the right one.
|
||||
- **Hardware filters manage frame location** — frames must be uploaded to device memory before hardware filters and downloaded after if software encoding follows. Use the existing upload/download filter patterns.
|
||||
- **`HardwareFilterOption` pre/post filters** — use `preprocessFilters` and `postProcessFilters` arrays, not ad-hoc insertions.
|
||||
- **`PipelineBuilderFactory`** — if adding a new hwaccel mode end-to-end, register the new builder here.
|
||||
34
.claude/commands/pr.md
Normal file
34
.claude/commands/pr.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
allowed-tools: Bash(git log:*), Bash(git diff:*), Bash(git status:*), Bash(git branch:*), Bash(git push:*), Bash(gh pr create:*), Bash(gh pr view:*)
|
||||
description: Create a pull request targeting the correct branch per project conventions
|
||||
argument-hint: Optional PR title or description hint
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
- Current branch: !`git branch --show-current`
|
||||
- Commits ahead of main: !`git log --oneline origin/main..HEAD`
|
||||
- Commits ahead of dev: !`git log --oneline origin/dev..HEAD`
|
||||
- Changed files: !`git diff --name-only origin/main..HEAD`
|
||||
- Git status: !`git status --short`
|
||||
|
||||
## Your task
|
||||
|
||||
Create a pull request following Tunarr's branching conventions:
|
||||
|
||||
**Branch targeting rules:**
|
||||
- `fix` commits → target `main`
|
||||
- `feat` commits and large changes → target `dev`
|
||||
- If the commits are mixed or ambiguous, ask the user which branch to target before proceeding
|
||||
|
||||
**Steps:**
|
||||
1. Determine the correct target branch from the commit types above
|
||||
2. If there are uncommitted changes, stop and tell the user to commit first
|
||||
3. Push the current branch to origin if not already pushed
|
||||
4. Draft a PR title and body based on the commits and diff:
|
||||
- Title: concise, follows conventional commit style
|
||||
- Body: summary of what changed and why, plus a test plan checklist
|
||||
5. Create the PR with `gh pr create` targeting the correct branch
|
||||
6. Return the PR URL
|
||||
|
||||
Hint from arguments: $ARGUMENTS
|
||||
19
.claude/commands/regen-api.md
Normal file
19
.claude/commands/regen-api.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
allowed-tools: Bash(cd server && pnpm generate-openapi:*), Bash(cd web && pnpm generate-client:*), Bash(cd web && pnpm regen-routes:*), Bash(git diff:*)
|
||||
description: Regenerate the OpenAPI spec and web API client after server API changes
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
- Changed API/types files: !`git diff --name-only HEAD | grep -E "^(server/src/api/|types/src/)"`
|
||||
|
||||
## Your task
|
||||
|
||||
Regenerate the full API contract chain after server-side changes:
|
||||
|
||||
1. **Generate OpenAPI spec** — run `cd server && pnpm generate-openapi`
|
||||
2. **Regenerate web client** — run `cd web && pnpm generate-client`
|
||||
3. **Regenerate TanStack Router routes** — run `cd web && pnpm regen-routes`
|
||||
4. Show a summary of what changed: `git diff --stat -- web/src/generated/`
|
||||
|
||||
Do these steps in order (each depends on the previous). Report any errors immediately and stop.
|
||||
32
.claude/settings.json
Normal file
32
.claude/settings.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(pnpm turbo:*)",
|
||||
"Bash(pnpm fmt:*)",
|
||||
"Bash(pnpm lint-changed:*)",
|
||||
"Bash(pnpm build:*)",
|
||||
"Bash(pnpm test:*)",
|
||||
"Bash(pnpm i:*)",
|
||||
"Bash(pnpm typecheck:*)",
|
||||
"Bash(pnpm drizzle-kit:*)",
|
||||
"Bash(cd server && pnpm test:*)",
|
||||
"Bash(cd server && pnpm generate-openapi:*)",
|
||||
"Bash(cd server && pnpm drizzle-kit:*)",
|
||||
"Bash(cd server && pnpm typecheck:*)",
|
||||
"Bash(cd web && pnpm generate-client:*)",
|
||||
"Bash(cd web && pnpm regen-routes:*)",
|
||||
"Bash(cd web && pnpm typecheck:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git status:*)",
|
||||
"Bash(git show:*)",
|
||||
"Bash(git blame:*)",
|
||||
"Bash(git branch:*)",
|
||||
"Bash(gh issue view:*)",
|
||||
"Bash(gh issue list:*)",
|
||||
"Bash(gh pr view:*)",
|
||||
"Bash(gh pr list:*)",
|
||||
"Bash(gh pr checks:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user