feat(v5): 4K-UHD nach der Leistung — Grafikkarte mit 4K-Preset komprimiert, nur CPU bleibt verlustfrei

WAS: presetEmpfehlung liefert für UHD das 4K-Preset der gemessenen
Grafikkarten-Familie (VCN/NVENC/QSV) — sonst PRESET_KEINE (verlustfrei);
komprimierenFuer folgt der Empfehlung, wenn der Typ auf „automatisch"
steht. Eigene Wahl schlägt beides. Fenster: Kachel, Kompressions-Karte und
Erst-Einrichtung sagen „(8 Bit)" bzw. „verlustfrei — nur CPU-Encoder".
Gemessen am 12.09.2026 (HandBrake 1.11.2, RX 9070 XT, Akira-UHD-Abbild):
die 4K-Hardware-Presets sind 8-Bit (vce_h265, Profil main); der 10-Bit-
HEVC-Encoder vce_h265_10bit stürzt beim Start ab (Segfault, mit Profil
auto und main10); AV1 10-Bit („AV1 VCN 2160p 4K", vce_av1_10bit) läuft
— steht als eigene Wahl in der Liste, mit dem Hinweis auf AV1-fähige
Abspielgeräte.
WARUM: Durchsicht 12.09.2026, A4 — Commander-Entscheid: „Rippy analysiert
doch die Performance des PCs, könnten wir das nicht davon abhängig
machen?" Vorher rechnete der CPU-Default 4K auf 1080p klein, tagelang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-12 21:48:38 +02:00
co-authored by Claude Opus 5
parent 6b6e5c3436
commit 0fc87aa022
7 changed files with 96 additions and 21 deletions
@@ -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]) => (
<div key={typ} className="rounded-lg border border-tinte/8 bg-buehne/60 p-2">
<p className="font-mono text-[10px] tracking-wider text-nebel">{name}</p>
<p className="mt-0.5 font-mono text-xs text-gold-hell">{presetEmpfehlung(typ, backends, presets) || DEFAULT_HB_PRESET}</p>
<p className="mt-0.5 font-mono text-xs text-gold-hell">{empfehlungText(typ, presetEmpfehlung(typ, backends, presets) || DEFAULT_HB_PRESET)}</p>
</div>
))}
</div>
@@ -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 (
<SektionsKarte
id="kompression"
@@ -153,7 +153,8 @@ export function KompressionKarte({ verbindung }: { verbindung: Verbindung }) {
</div>
<p className="text-[11px] leading-4 text-nebel">
„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.
</p>
<div className="grid gap-3 sm:grid-cols-2">
<SprachAuswahl
@@ -13,7 +13,7 @@ import type {
RipStatus,
VorgangEintrag,
} from '../../gemeinsam/nachrichten'
import { DEFAULT_HB_PRESET, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
import { DEFAULT_HB_PRESET, PRESET_KEINE, empfehlungText, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
import {
Badge,
IconDisc,
@@ -58,9 +58,11 @@ export function presetAnzeige(typ: GeraeteInfo['typ'], verbindung: Verbindung):
if (werte['transcodeEnabled'] === 'false') return 'ohne Kompression'
const schluessel = typ === 'dvd' ? 'transcodePresetDvd' : typ === 'uhd' ? 'transcodePresetUhd' : 'transcodePresetBluray'
const eigene = werte[schluessel] ?? ''
if (eigene === PRESET_KEINE) return 'ohne Kompression'
if (eigene.length > 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({
@@ -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<readonly [backend: string, presetKennung: string]> = [
['vce', 'VCN'],
['nvenc', 'NVENC'],
@@ -25,7 +30,19 @@ const FAMILIEN: ReadonlyArray<readonly [backend: string, presetKennung: string]>
]
/** 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
}
+1 -1
View File
@@ -121,7 +121,7 @@ export async function komprimierenUndAblegen(
): Promise<AblageErgebnis> {
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
@@ -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<string, string> = {
@@ -13,15 +13,12 @@ export const PRESET_SCHLUESSEL: Record<string, string> = {
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<string, string | boolean | undefined>
/** 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
}
+47
View File
@@ -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)
})
})