diff --git a/rippy-windows/src/fenster/einrichtung/ErstEinrichtung.tsx b/rippy-windows/src/fenster/einrichtung/ErstEinrichtung.tsx
index 6b387f2..7a4bbd9 100644
--- a/rippy-windows/src/fenster/einrichtung/ErstEinrichtung.tsx
+++ b/rippy-windows/src/fenster/einrichtung/ErstEinrichtung.tsx
@@ -7,7 +7,7 @@
// Werkzeuge mit Durchsuchen, Kompression mit Sprach-Auswahl, TMDb,
// Benachrichtigung, Automatik und Roh-Regel. Dazu der Poster-Hintergrund.
import { useEffect, useState } from 'react'
-import { DEFAULT_HB_PRESET, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
+import { DEFAULT_HB_PRESET, empfehlungText, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
import { Badge, FELD_KLASSE, Feld, KNOPF_GOLD, KNOPF_LEISE, PfadFeld, Schalter, gbKurz } from '../bausteine'
import { SprachAuswahl } from '../einstellungen/SprachAuswahl'
import { benachrichtigungTesten, einstellungSetzen, posterLaden, speicherLaden, type Verbindung } from '../kernverbindung'
@@ -167,7 +167,7 @@ export function ErstEinrichtung({ verbindung }: { verbindung: Verbindung }) {
{EMPFEHLUNGEN.map(([name, typ]) => (
{name}
-
{presetEmpfehlung(typ, backends, presets) || DEFAULT_HB_PRESET}
+
{empfehlungText(typ, presetEmpfehlung(typ, backends, presets) || DEFAULT_HB_PRESET)}
))}
diff --git a/rippy-windows/src/fenster/einstellungen/Karten.tsx b/rippy-windows/src/fenster/einstellungen/Karten.tsx
index f11e67a..5d3fe09 100644
--- a/rippy-windows/src/fenster/einstellungen/Karten.tsx
+++ b/rippy-windows/src/fenster/einstellungen/Karten.tsx
@@ -4,7 +4,7 @@
// einstellungSetzen in die EINE Quelle (§ 6.7).
import { useState } from 'react'
import type { GeraeteInfo, HauptStatus, VorgangEintrag } from '../../gemeinsam/nachrichten'
-import { presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
+import { empfehlungText, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
import {
Badge,
FELD_KLASSE,
@@ -127,7 +127,7 @@ export function KompressionKarte({ verbindung }: { verbindung: Verbindung }) {
const werte = verbindung.einstellungen
const presets = verbindung.werkzeuge?.presets ?? []
const backends = verbindung.werkzeuge?.backends ?? []
- const empfehlungFuer = (typ: string): string => presetEmpfehlung(typ, backends, presets)
+ const empfehlungFuer = (typ: string): string => empfehlungText(typ, presetEmpfehlung(typ, backends, presets))
return (
„automatisch" nimmt die Empfehlung aus der gemessenen Liste deines HandBrake — auf dieser Maschine die Grafikkarte, wo es sie gibt. Eine eigene Wahl
- schlägt die Empfehlung immer.
+ schlägt die Empfehlung immer. 4K-UHD wird nur mit Grafikkarten-Encoder komprimiert (8 Bit); ohne bleibt es verlustfrei. Wer HDR in 10 Bit will, wählt
+ „AV1 VCN 2160p 4K" — die Abspielgeräte müssen dann AV1 können.
0) return eigene
const empfehlung = presetEmpfehlung(typ, verbindung.werkzeuge?.backends ?? [], verbindung.werkzeuge?.presets ?? [])
- const gewaehlt = eigene.length > 0 ? eigene : empfehlung.length > 0 ? empfehlung : DEFAULT_HB_PRESET
- return gewaehlt === 'keine' ? 'ohne Kompression' : gewaehlt
+ if (empfehlung === PRESET_KEINE) return typ === 'uhd' ? 'verlustfrei (nur CPU-Encoder)' : 'ohne Kompression'
+ return empfehlungText(typ, empfehlung.length > 0 ? empfehlung : DEFAULT_HB_PRESET)
}
export function LaufwerkKachel({
diff --git a/rippy-windows/src/gemeinsam/preset-empfehlung.ts b/rippy-windows/src/gemeinsam/preset-empfehlung.ts
index 90ba9a3..041c0c4 100644
--- a/rippy-windows/src/gemeinsam/preset-empfehlung.ts
+++ b/rippy-windows/src/gemeinsam/preset-empfehlung.ts
@@ -18,6 +18,11 @@
export const DEFAULT_HB_PRESET = 'H.265 MKV 1080p30'
+// Reservierter Wert: „diesen Typ NICHT komprimieren". Kein echtes
+// HandBrake-Preset heißt so (gegen alle 107 aus --preset-list geprüft).
+// Lebt seit 5.7.0 hier, weil die Empfehlung ihn liefern kann.
+export const PRESET_KEINE = 'keine'
+
const FAMILIEN: ReadonlyArray = [
['vce', 'VCN'],
['nvenc', 'NVENC'],
@@ -25,7 +30,19 @@ const FAMILIEN: ReadonlyArray
]
/** Das empfohlene Preset für diesen Disc-Typ — aus den GEMESSENEN
- * Backends und Preset-Namen. Fällt auf den CPU-Default zurück. */
+ * Backends und Preset-Namen. DVD und Blu-ray fallen auf den CPU-Default
+ * zurück.
+ *
+ * 4K-UHD seit 5.7.0 nach der Leistung (Commander-Entscheid 12.09.2026):
+ * Gibt es einen Grafikkarten-Encoder UND dessen 4K-Preset, wird damit
+ * komprimiert; gibt es nur CPU-Encoder, bleibt UHD verlustfrei
+ * (PRESET_KEINE) — ein 4K-Encode auf der CPU dauert Tage, und der alte
+ * CPU-Default „1080p30" rechnete 4K klein. Gemessen am 12.09.2026: Die
+ * 4K-Hardware-Presets („H.265 VCN 2160p 4K" usw.) sind 8-Bit; der
+ * 10-Bit-HEVC-Encoder der AMD-Karte (vce_h265_10bit) stürzt in HandBrake
+ * 1.11.2 beim Start ab, 10-Bit-AV1 („AV1 VCN 2160p 4K") läuft — wer HDR
+ * in 10 Bit will, wählt das AV1-Preset selbst (Abspielgeräte müssen AV1
+ * können). */
export function presetEmpfehlung(
discTyp: string,
backends: readonly string[],
@@ -36,9 +53,19 @@ export function presetEmpfehlung(
if (!backends.includes(backend)) continue
const treffer = presets.find((p) => p.startsWith('H.265 ') && p.includes(kennung) && p.includes(gewuenscht))
if (treffer !== undefined) return treffer
- // 4K-Familie ohne 4K-Preset: das 1080p-Pendant ist besser als CPU.
+ // Eine 4K-Disc ohne 4K-Preset der Familie: nicht klein rechnen —
+ // verlustfrei lassen (unten). Bei DVD/Blu-ray ist das 1080p-Pendant
+ // besser als CPU.
+ if (discTyp === 'uhd') continue
const kleiner = presets.find((p) => p.startsWith('H.265 ') && p.includes(kennung))
if (kleiner !== undefined) return kleiner
}
- return DEFAULT_HB_PRESET
+ return discTyp === 'uhd' ? PRESET_KEINE : DEFAULT_HB_PRESET
+}
+
+/** Klartext zur Empfehlung fürs Fenster (5.7.0). */
+export function empfehlungText(discTyp: string, empfehlung: string): string {
+ if (empfehlung === PRESET_KEINE) return discTyp === 'uhd' ? 'verlustfrei lassen — nur CPU-Encoder gemessen' : 'verlustfrei lassen'
+ if (discTyp === 'uhd' && /2160p/.test(empfehlung)) return `${empfehlung} (8 Bit)`
+ return empfehlung
}
diff --git a/rippy-windows/src/kern/ablauf/ablegen.ts b/rippy-windows/src/kern/ablauf/ablegen.ts
index 46f7098..fd828c7 100644
--- a/rippy-windows/src/kern/ablauf/ablegen.ts
+++ b/rippy-windows/src/kern/ablauf/ablegen.ts
@@ -121,7 +121,7 @@ export async function komprimierenUndAblegen(
): Promise {
const einstellungen = umgebung.einstellungen()
const handbrake = umgebung.handbrake()
- const soll = komprimierenFuer(auftrag.discTyp, einstellungen)
+ const soll = komprimierenFuer(auftrag.discTyp, einstellungen, umgebung.presetEmpfehlung?.(auftrag.discTyp) ?? '')
const komprimiert = soll && handbrake.length > 0
const preset =
auftrag.preset.length > 0
diff --git a/rippy-windows/src/kern/komprimieren/presets.ts b/rippy-windows/src/kern/komprimieren/presets.ts
index 9de0e06..ae3f829 100644
--- a/rippy-windows/src/kern/komprimieren/presets.ts
+++ b/rippy-windows/src/kern/komprimieren/presets.ts
@@ -2,9 +2,9 @@
// mit den Befunden vom 25.07.2026 begründet: EIN Preset für alles rechnete
// 4K auf 1080p herunter und DVDs hinauf.
-import { DEFAULT_HB_PRESET, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
+import { DEFAULT_HB_PRESET, PRESET_KEINE, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
-export { DEFAULT_HB_PRESET, presetEmpfehlung }
+export { DEFAULT_HB_PRESET, PRESET_KEINE, presetEmpfehlung }
/** Einstellungs-Schlüssel je Disc-Typ. */
export const PRESET_SCHLUESSEL: Record = {
@@ -13,15 +13,12 @@ export const PRESET_SCHLUESSEL: Record = {
uhd: 'transcodePresetUhd',
}
-// Reservierter Wert: „diesen Typ NICHT komprimieren". Kein echtes
-// HandBrake-Preset heißt so (gegen alle 90 aus --preset-list geprüft).
-// Grund: 4K verlustfrei durchreichen, DVDs trotzdem schrumpfen.
-export const PRESET_KEINE = 'keine'
-
export type Einstellungen = Record
-/** Soll dieser Disc-Typ nach dem Rip komprimiert werden? (pur) */
-export function komprimierenFuer(discTyp: string, einstellungen: Einstellungen): boolean {
+/** Soll dieser Disc-Typ nach dem Rip komprimiert werden? (pur)
+ * 5.7.0: Steht der Typ auf „automatisch" und die gemessene Empfehlung
+ * sagt „keine" (4K ohne Grafikkarten-Encoder), bleibt es verlustfrei. */
+export function komprimierenFuer(discTyp: string, einstellungen: Einstellungen, empfehlung = ''): boolean {
if (einstellungen['transcodeEnabled'] === false || einstellungen['transcodeEnabled'] === 'false') {
return false
}
@@ -29,6 +26,7 @@ export function komprimierenFuer(discTyp: string, einstellungen: Einstellungen):
if (schluessel.length > 0) {
const wert = String(einstellungen[schluessel] ?? '').trim()
if (wert === PRESET_KEINE) return false
+ if (wert.length === 0 && empfehlung === PRESET_KEINE) return false
}
return true
}
diff --git a/rippy-windows/test/vierk-regel.test.ts b/rippy-windows/test/vierk-regel.test.ts
new file mode 100644
index 0000000..1653acf
--- /dev/null
+++ b/rippy-windows/test/vierk-regel.test.ts
@@ -0,0 +1,47 @@
+// 5.7.0 — 4K nach der Leistung (Commander-Entscheid 12.09.2026): Grafik-
+// karten-Encoder plus 4K-Preset → komprimieren; nur CPU → verlustfrei.
+// Preset-Namen und Encoder am mitgelieferten HandBrake 1.11.2 gemessen
+// (--preset-list, --help; vce_h265_10bit stürzt ab, AV1 10-Bit läuft).
+import { describe, expect, it } from 'vitest'
+import { DEFAULT_HB_PRESET, PRESET_KEINE, empfehlungText, presetEmpfehlung } from '../src/gemeinsam/preset-empfehlung'
+import { komprimierenFuer, presetFuer } from '../src/kern/komprimieren/presets'
+
+const PRESETS = ['H.265 MKV 1080p30', 'H.265 MKV 2160p60 4K', 'AV1 VCN 2160p 4K', 'H.265 VCN 2160p 4K', 'H.265 VCN 1080p', 'H.265 NVENC 1080p']
+const NUR_CPU = ['cpu-x264', 'cpu-x265', 'cpu-av1']
+const AMD = [...NUR_CPU, 'vce', 'vce-av1']
+const NVIDIA_OHNE_4K = [...NUR_CPU, 'nvenc']
+
+describe('presetEmpfehlung für 4K-UHD', () => {
+ it('Grafikkarte mit 4K-Preset → das 4K-Preset (8 Bit im Klartext)', () => {
+ expect(presetEmpfehlung('uhd', AMD, PRESETS)).toBe('H.265 VCN 2160p 4K')
+ expect(empfehlungText('uhd', 'H.265 VCN 2160p 4K')).toBe('H.265 VCN 2160p 4K (8 Bit)')
+ })
+ it('nur CPU → verlustfrei lassen, nie das 1080p-CPU-Preset', () => {
+ expect(presetEmpfehlung('uhd', NUR_CPU, PRESETS)).toBe(PRESET_KEINE)
+ expect(empfehlungText('uhd', PRESET_KEINE)).toContain('verlustfrei')
+ })
+ it('Grafikkarte ohne 4K-Preset → verlustfrei, nicht klein rechnen', () => {
+ expect(presetEmpfehlung('uhd', NVIDIA_OHNE_4K, PRESETS)).toBe(PRESET_KEINE)
+ })
+ it('DVD und Blu-ray wie bisher: Grafikkarte, sonst CPU-Default', () => {
+ expect(presetEmpfehlung('bluray', AMD, PRESETS)).toBe('H.265 VCN 1080p')
+ expect(presetEmpfehlung('dvd', NUR_CPU, PRESETS)).toBe(DEFAULT_HB_PRESET)
+ expect(presetEmpfehlung('bluray', NVIDIA_OHNE_4K, PRESETS)).toBe('H.265 NVENC 1080p')
+ })
+})
+
+describe('komprimierenFuer mit Empfehlung', () => {
+ it('automatisch + Empfehlung „keine" → nicht komprimieren', () => {
+ expect(komprimierenFuer('uhd', {}, PRESET_KEINE)).toBe(false)
+ expect(komprimierenFuer('uhd', { transcodePresetUhd: '' }, PRESET_KEINE)).toBe(false)
+ })
+ it('eigene Wahl schlägt die Empfehlung — in beide Richtungen', () => {
+ expect(komprimierenFuer('uhd', { transcodePresetUhd: 'AV1 VCN 2160p 4K' }, PRESET_KEINE)).toBe(true)
+ expect(komprimierenFuer('uhd', { transcodePresetUhd: PRESET_KEINE }, 'H.265 VCN 2160p 4K')).toBe(false)
+ expect(presetFuer('uhd', { transcodePresetUhd: 'AV1 VCN 2160p 4K' }, PRESET_KEINE)).toBe('AV1 VCN 2160p 4K')
+ })
+ it('Blu-ray bleibt unberührt', () => {
+ expect(komprimierenFuer('bluray', {}, 'H.265 VCN 1080p')).toBe(true)
+ expect(komprimierenFuer('bluray', {}, '')).toBe(true)
+ })
+})