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.
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.
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)
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements #143 (SPA parity: transcoding editors) + API gap #159.
Backend (#159)
FFmpegFullProfileResponseModelnow round-trip complete (NormalizeAudio/NormalizeVideo/PadMode/TargetLoudness/NormalizeColors/ResolutionId,DeinterlaceVideobool) — GET→PUT no longer loses state.GET /api/ffmpeg/hardware-acceleration-kindslookup.GET/{id},POST→ 201+Location,PUT,DELETE) and watermarks (same), full-field DTOs;CreateFillerPresetnow returns the created id (Blazor call site updated).NotFoundErrorlost through LanguageExtValidationaggregation) — handlers restructured to theOption.Matchprecedent.WatermarkLocation/WatermarkSizeregistered as OpenAPI string enums (were documented as ints, mismatching runtime).Frontend (#143)
PresetsForFFmpegProfileported to TS), Filler Presets (kind/mode/collection-type interlocks; pickers vialibrary/browse), Watermarks (mode-driven fields, image upload via/api/artwork/uploads, preview).v1.json/v1.d.tsregenerated authoritatively.Verification:
dotnet buildclean; 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