feat(web+api): FFmpeg profile, filler preset & watermark editors for SPA parity (#143) #166

Merged
timothy merged 5 commits from feat/143-transcoding-editors into main 2026-07-07 15:06:39 +02:00
Owner

Implements #143 (SPA parity: transcoding editors) + API gap #159.

Backend (#159)

  • FFmpegFullProfileResponseModel now round-trip complete (NormalizeAudio/NormalizeVideo/PadMode/TargetLoudness/NormalizeColors/ResolutionId, DeinterlaceVideo bool) — GET→PUT no longer loses state.
  • New GET /api/ffmpeg/hardware-acceleration-kinds lookup.
  • Full CRUD for filler presets (GET/{id}, POST → 201+Location, PUT, DELETE) and watermarks (same), full-field DTOs; CreateFillerPreset now returns the created id (Blazor call site updated).
  • Fix found by live E2E: PUT/DELETE on missing filler/watermark returned 422 instead of 404 (NotFoundError lost through LanguageExt Validation aggregation) — handlers restructured to the Option.Match precedent.
  • WatermarkLocation/WatermarkSize registered as OpenAPI string enums (were documented as ints, mismatching runtime).
  • 20 new OpenAPI error-contract test cases; controller tests rewritten to full CRUD.

Frontend (#143)

  • New screens: FFmpeg Profiles (full ~30-field editor with the Blazor validator's per-hwaccel format + bit-depth/profile rules and PresetsForFFmpegProfile ported to TS), Filler Presets (kind/mode/collection-type interlocks; pickers via library/browse), Watermarks (mode-driven fields, image upload via /api/artwork/uploads, preview).
  • Nav: Filler Presets under Media; FFmpeg Profiles + Watermarks under System. Settings Classic-UI text updated; FFmpeg card links to the new screen.
  • Includes merge of main (post-#165) with unified System nav group; v1.json/v1.d.ts regenerated authoritatively.

Verification: dotnet build clean; ErsatzTV.Tests 569 green; Core.Tests 493 green (+1 skipped); web typecheck/vitest 193 (16 files)/build/eslint clean. Live E2E on localhost:8409: filler + watermark POST 201 → GET → PUT → DELETE 204 round-trips (incl. custom image + opacity expression), missing-id 404s, empty-name 422, ffmpeg GET→PUT round-trip verified, hwaccel lookup works, all three SPA routes serve.

Refs #143, #159.

🤖 Generated with Claude Code

Implements #143 (SPA parity: transcoding editors) + API gap #159. **Backend (#159)** - `FFmpegFullProfileResponseModel` now round-trip complete (`NormalizeAudio/NormalizeVideo/PadMode/TargetLoudness/NormalizeColors/ResolutionId`, `DeinterlaceVideo` bool) — GET→PUT no longer loses state. - New `GET /api/ffmpeg/hardware-acceleration-kinds` lookup. - Full CRUD for filler presets (`GET/{id}`, `POST` → 201+Location, `PUT`, `DELETE`) and watermarks (same), full-field DTOs; `CreateFillerPreset` now returns the created id (Blazor call site updated). - Fix found by live E2E: PUT/DELETE on missing filler/watermark returned 422 instead of 404 (`NotFoundError` lost through LanguageExt `Validation` aggregation) — handlers restructured to the `Option.Match` precedent. - `WatermarkLocation`/`WatermarkSize` registered as OpenAPI string enums (were documented as ints, mismatching runtime). - 20 new OpenAPI error-contract test cases; controller tests rewritten to full CRUD. **Frontend (#143)** - New screens: FFmpeg Profiles (full ~30-field editor with the Blazor validator's per-hwaccel format + bit-depth/profile rules and `PresetsForFFmpegProfile` ported to TS), Filler Presets (kind/mode/collection-type interlocks; pickers via `library/browse`), Watermarks (mode-driven fields, image upload via `/api/artwork/uploads`, preview). - Nav: Filler Presets under Media; FFmpeg Profiles + Watermarks under System. Settings Classic-UI text updated; FFmpeg card links to the new screen. - Includes merge of main (post-#165) with unified System nav group; `v1.json`/`v1.d.ts` regenerated authoritatively. **Verification**: `dotnet build` clean; ErsatzTV.Tests 569 green; Core.Tests 493 green (+1 skipped); web typecheck/vitest 193 (16 files)/build/eslint clean. Live E2E on localhost:8409: filler + watermark POST 201 → GET → PUT → DELETE 204 round-trips (incl. custom image + opacity expression), missing-id 404s, empty-name 422, ffmpeg GET→PUT round-trip verified, hwaccel lookup works, all three SPA routes serve. Refs #143, #159. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 4 commits 2026-07-07 14:57:00 +02:00
Backend plumbing for the SPA transcoding editors (#143 blocker):

- FFmpeg profile response DTO round-trip: add NormalizeAudio, NormalizeVideo,
  PadMode, TargetLoudness, NormalizeColors, ResolutionId (keep Resolution name
  for compat); DeinterlaceVideo now bool (was bool?). #nullable enable on the DTO.
- New GET /api/ffmpeg/hardware-acceleration-kinds wrapping
  GetSupportedHardwareAccelerationKinds (returns enum names as strings).
- Filler preset CRUD: GET-by-id/POST/PUT/DELETE on FillerPresetController with
  full request/response DTOs; new FillerPresetFullResponseModel +
  GetFillerPresetByIdForApi. CreateFillerPreset now returns the new id
  (CreateFillerPresetResult) to match sibling Create commands and enable a
  201+Location; updated the one Blazor call site.
- Watermark CRUD: GET-by-id/POST/PUT/DELETE on WatermarkController with full
  DTOs (path+contentType, all 15 fields); new WatermarkFullResponseModel +
  GetWatermarkByIdForApi.
- Startup: register WatermarkLocation/WatermarkSize as OpenAPI string enums
  (they live in ErsatzTV.FFmpeg.State and were documented as ints, mismatching
  the Newtonsoft StringEnumConverter runtime serialization).
- Tests: full CRUD controller tests for filler + watermark; contract-test
  entries (404/401/422) for the new mutating endpoints; regenerated v1.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three new SPA list+editor surfaces, closing the Blazor-only gap found in the
#91 phase-(b) recon:

- FFmpeg Profiles (System group): list with resolution/video/audio summary +
  add/copy/delete; full General/Video/Audio editor. Per-hwaccel video-format
  and profile rules ported from FFmpegProfileEditViewModelValidator; video
  preset list ported from FFmpegLibraryHelper.PresetsForFFmpegProfile /
  AvailablePresets.ForAccelAndFormat (cited in code). Resolutions from
  /api/settings/resolutions, hwaccel kinds from the new lookup endpoint.
  Copy = client-side clone into the create form (no server copy endpoint).
- Filler Presets (Media group): list + editor with the Blazor VM interlocks
  (Fallback/Tail force mode None; non-MidRoll clears expression; Fallback
  disables chapters; Playlist collection type forces Count mode; duration/
  count/pad fields gated by mode). Collection pickers use the existing
  /api/library/browse client for all seven collection types incl. Playlist.
- Watermarks (System group): list + editor with mode-driven field enabling,
  ChannelLogo vs Custom image source, upload via /api/artwork/uploads
  (target=watermark) and preview via /artwork/watermarks/{path}.
- App.tsx: ScreenIds/routes (sub-paths /add + /{id}) and nav registration;
  api modules ffmpegProfiles/fillerPresets/watermarks with URL-assert tests;
  SettingsScreen Classic-UI help text updated (these are no longer
  Blazor-only) and the FFmpeg-profiles card now links to the new screen.
- Regenerated web/src/api/generated/v1.d.ts from the #159 OpenAPI changes.

web verification: typecheck clean; vitest 188/188 (14 files); vite build OK;
eslint clean on touched files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live E2E found PUT/DELETE on a missing filler preset or watermark returned 422
instead of 404. Root cause: the must-exist checks fed a NotFoundError through
LanguageExt Validation, whose aggregation to Either flattens the BaseError
subtype, so ApiResults.ToErrorResult never saw a NotFoundError. The precedent
handlers (DeleteProgramSchedule, DeleteFFmpegProfile, UpdateProgramSchedule)
avoid this by resolving must-exist as an Option and returning the NotFoundError
directly as an Either Left via Option.Match — restructured the four
filler/watermark Update/Delete handlers to that pattern (remaining name
validation still 422s). No Blazor behavior change (it only reads error.Value).

Verified live: PUT/DELETE missing → 404, GET missing → 404, empty-name POST →
422; happy-path CRUD unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into feat/143-transcoding-editors
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m10s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m30s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
7454411dee
# Conflicts:
#	web/src/App.tsx
#	web/src/screens/SettingsScreen.tsx
timothy added 1 commit 2026-07-07 15:01:13 +02:00
test: cover FillerPresetController + WatermarkController in API security test (#166 review)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m15s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
66abb24402
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timothy merged commit e0f36a12fb into main 2026-07-07 15:06:39 +02:00
timothy deleted branch feat/143-transcoding-editors 2026-07-07 15:06:39 +02:00
Sign in to join this conversation.