Files
kniip/.cursor/plans/screencasts_mp4_gif_68933a82.plan.md
T
2026-11-07 12:19:45 +01:00

73 lines
6.0 KiB
Markdown

---
name: Screencasts MP4 GIF
overview: "Knip+ krijgt een eigen schermopname: gebied kiezen via tray of sneltoets, opnemen met geluid, muisaanwijzer en klikmarkering, daarna begin en einde inkorten en opslaan als MP4 of GIF."
todos:
- id: spike
content: ScreenRecorderLib toevoegen, TFM naar windows10.0.19041.0, en controleren dat single-file publish met opname werkt
status: completed
- id: hotkey-tray
content: GlobalHotkey + tray-item + StartRecording/StopRecording in App
status: completed
- id: selector
content: RegionSelectorWindow (slepen, venster, heel scherm) en RegionFrameWindow
status: completed
- id: toolbar-session
content: RecordingToolbar en RecordingSession (audio, cursor, klikken, pauze, aftellen)
status: completed
- id: screencast-window
content: ScreencastWindow met preview en bereikschuif voor inkorten
status: completed
- id: export
content: VideoExporter (MP4 trim) en GifEncoder (frames via GetThumbnailsAsync, quantisatie, delta-frames)
status: completed
- id: settings
content: AppSettings-velden en sectie Screencasts in SettingsWindow
status: completed
- id: build
content: build.ps1/installer bijwerken en eind-tot-eind testen
status: completed
isProject: false
---
# Screencasts (MP4 en GIF) in Knip+
## Aanpak
Er komt één opnamepad: Knip+ neemt altijd eerst op als MP4 in een tijdelijk bestand (met geluid als dat aan staat). Pas na het stoppen kies je in een nabewerkingsvenster het bereik en het formaat. Zo hoef je vooraf niets te beslissen, en GIF en MP4 delen dezelfde opname en dezelfde inkortfunctie.
```mermaid
flowchart LR
trigger["Tray of sneltoets"] --> selector[RegionSelectorWindow]
selector --> toolbar["RecordingToolbar: start, pauze, stop"]
toolbar --> session["RecordingSession: tijdelijke MP4"]
session --> editor["ScreencastWindow: voorbeeld en inkorten"]
editor --> mp4["VideoExporter: MP4"]
editor --> gif["GifEncoder: GIF"]
```
## Techniek
- **Opnemen**: NuGet-pakket `ScreenRecorderLib` (MIT, gebruikt Windows Media Foundation, H.264/AAC). Dat regelt in één keer het gekozen gebied, de muisaanwijzer, de klikmarkering en het mixen van systeemgeluid en microfoon. Er is dan geen ffmpeg nodig.
- **Inkorten naar MP4**: `Windows.Media.Editing.MediaComposition` met `TrimTimeFromStart` en `TrimTimeFromEnd`, en daarna `RenderToFileAsync`. Daarvoor gaat het target framework in [src/KnipPlus/KnipPlus.csproj](src/KnipPlus/KnipPlus.csproj) van `net8.0-windows` naar `net8.0-windows10.0.19041.0`.
- **GIF**: frames uit de MP4 halen met `MediaComposition.GetThumbnailsAsync`, met een instelbare fps (standaard 10) en maximale breedte. Daarna codeert een eigen `GifEncoder` ze: per frame een palet van 256 kleuren (octree-quantisatie), framevertraging, oneindig herhalen (NETSCAPE-extensie) en ongewijzigde pixels transparant maken, zodat het bestand klein blijft. De ingebouwde `GifBitmapEncoder` van WPF kent geen framevertraging of herhalen en is daarom niet bruikbaar.
- **Risico bij single-file publish**: `ScreenRecorderLib` is een C++/CLI-assembly en die kan niet vanuit de bundel geladen worden. Daarom komt `-p:IncludeAllContentForSelfExtract=true` in [build.ps1](build.ps1) (of als fallback publiceren als map via de installer). Dit test ik direct als eerste stap.
## Nieuwe bestanden (`src/KnipPlus/Recording/`)
- `RegionSelectorWindow.xaml(.cs)`: een transparante overlay over alle schermen. Je sleept een rechthoek, klikt op een venster om dat te kiezen, of drukt op Enter voor het hele scherm. De selectie houdt rekening met DPI per monitor.
- `RegionFrameWindow.cs`: een rand rond het opnamegebied die klikken doorlaat en buiten het gebied valt, zodat hij niet in de opname komt.
- `RecordingToolbar.xaml(.cs)`: een kleine knoppenbalk die altijd bovenop blijft, naast het gebied. Daarop staan schakelaars voor systeemgeluid en microfoon, en knoppen voor start (met 3 seconden aftellen), pauze en stop, plus de verstreken tijd.
- `RecordingSession.cs`: een wrapper om `ScreenRecorderLib.Recorder` (opties, status, fouten) die een tijdelijk MP4-pad oplevert in `%TEMP%\KnipPlus`.
- `ScreencastWindow.xaml(.cs)`: voorbeeld van de opname via `MediaElement`, een bereikschuif met begin- en eindgreep en afspelen binnen het bereik. Verder knoppen "Opslaan als MP4", "Opslaan als GIF" (met fps en breedte) en "Kopiëren" (het bestand als FileDropList op het klembord). Bij het sluiten wordt het tijdelijke bestand opgeruimd.
- `VideoExporter.cs`: MP4 inkorten en renderen.
- `GifEncoder.cs`: GIF-encoder met quantisatie en het overslaan van ongewijzigde pixels per frame.
## Wijzigingen in bestaande bestanden
- [src/KnipPlus/Infrastructure/TrayIcon.cs](src/KnipPlus/Infrastructure/TrayIcon.cs): menu-item "Screencast opnemen..." en een event `RecordRequested`. Tijdens een opname wordt dit "Opname stoppen".
- `src/KnipPlus/Infrastructure/GlobalHotkey.cs` (nieuw): `RegisterHotKey` via een verborgen `HwndSource`. Dezelfde sneltoets start of stopt de opname (standaard Ctrl + Shift + R). Als de sneltoets al bezet is, verschijnt er een ballonmelding.
- [src/KnipPlus/Interop/NativeMethods.cs](src/KnipPlus/Interop/NativeMethods.cs): `RegisterHotKey`, `UnregisterHotKey` en `WindowFromPoint` (voor het kiezen van een venster).
- [src/KnipPlus/App.xaml.cs](src/KnipPlus/App.xaml.cs): `StartRecording()` en `StopRecording()`. Er draait maximaal één opname tegelijk. Tijdens de opname staat `Capture.Paused` aan, zodat er geen knipsels opgepikt worden.
- [src/KnipPlus/Settings/AppSettings.cs](src/KnipPlus/Settings/AppSettings.cs): `RecordHotkey`, `RecordSystemAudio`, `RecordMicrophone`, `RecordShowCursor`, `RecordHighlightClicks`, `RecordFps` (30), `GifFps` (10), `GifMaxWidth` (800) en `LastVideoFolder` (standaard `Video's\Knip+`).
- [src/KnipPlus/Settings/SettingsWindow.xaml](src/KnipPlus/Settings/SettingsWindow.xaml) en de bijbehorende `.cs`: een sectie "Screencasts" met de sneltoets, standaardinstellingen voor geluid, muisaanwijzer en klikken, en GIF-instellingen.
- [installer/KnipPlus.iss](installer/KnipPlus.iss): alleen aanpassen als de fallback (publiceren als map) nodig blijkt.