WAS: `npm run klick` startet das ECHTE Rippy (Electron, Haupt, Kern, Fenster, Datenbank) mit eigenem Profil (--profil, --klick-smoke: ohne Tray, Updater, Toasts), einem nachgebauten Laufwerk (RIPPY_FAKE_LAUFWERK) und nachgebauten Werkzeugen (bau/klick/fake-*.cjs) — und klickt sich durch: Rippen → Dialog → Sprachen je Titel (jpn statt deu) → Rollen auf Folge → Staffel 1, ab Folge 3 → Vorschau → Rip → Warteschlange → Kompression → Ablage als S01E03/S01E04 unter Serien\…\Season 01 → Einstellungen/Roh-Dateien → Roh loeschen → Ereignisse. Bilder liegen in beweise/klick/. Die Naht dafuer (kern/werkzeuge/aufruf.ts): Zeigt ein Werkzeug-Pfad auf ein Node-Skript, laeuft es ueber die eigene Laufzeit (ELECTRON_RUN_AS_NODE) — fuer EXE-Pfade aendert sich nichts. WARUM: Beide Fehler vom 01.09.2026 waren Verkabelung; 261 Unit-Tests sahen keinen. Der Klick-Beweis haette beide gefunden. NEBENBEFUND: Die CSP blockte eingebettete Schrift-Teile (data:-Fonts der fontsource-Pakete) — Konsolenfehler im gebauten Fenster, jetzt font-src. Dazu test/messung.spuren.test.ts: HandBrake-Scan einer echten Roh-Datei auf Verlangen (RIPPY_MESSUNG_MKV). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
102 lines
3.8 KiB
TypeScript
102 lines
3.8 KiB
TypeScript
// Encoder-Fähigkeiten — GEMESSEN, nicht behauptet (KONZEPT § 4.2,
|
|
// Regeln aus caps.py, dort am echten HandBrake abgenommen):
|
|
//
|
|
// - `--help`, Abschnitt `-e, --encoder`: NUR was DIESE Maschine wirklich
|
|
// benutzen kann (HandBrake probiert Hardware beim Start selbst an).
|
|
// - `--preset-list`: ALLE mitgelieferten Preset-NAMEN (Struktur ist die
|
|
// Einrückung — Namen stehen mit genau vier Leerzeichen).
|
|
// - Hardware nach FAMILIE unterscheiden (nvenc/qsv/vce/vaapi), sonst
|
|
// läuft AMD unter einem Intel-Namen (Commander-PC: RX 9070 XT).
|
|
import { werkzeugSpawn } from '../werkzeuge/aufruf'
|
|
import { textVon } from '../rip/parser'
|
|
|
|
const HB_ENCODER_KOPF = /^-e,\s*--encoder\b/
|
|
|
|
/** Video-Encoder aus `--help` — der Block endet an der nächsten Option. */
|
|
export function parseEncoderListe(hilfeText: string): string[] {
|
|
const encoder: string[] = []
|
|
let imBlock = false
|
|
for (const zeile of (hilfeText ?? '').split(/\r?\n/)) {
|
|
const inhalt = zeile.trim()
|
|
if (!imBlock) {
|
|
if (HB_ENCODER_KOPF.test(inhalt)) imBlock = true
|
|
continue
|
|
}
|
|
if (inhalt.length === 0) continue
|
|
if (inhalt.startsWith('-')) break
|
|
encoder.push(inhalt.split(/\s+/)[0])
|
|
}
|
|
return encoder
|
|
}
|
|
|
|
// Reihenfolge = Anzeige-Reihenfolge; AV1 in Hardware sichtbar machen.
|
|
const HW_FAMILIEN: ReadonlyArray<readonly [string, string]> = [
|
|
['nvenc', 'nvenc'],
|
|
['qsv', 'qsv'],
|
|
['vce', 'vce'],
|
|
['vaapi', 'vaapi'],
|
|
]
|
|
|
|
/** HandBrake-Encoder-Namen → Backend-Kennungen fürs UI. */
|
|
export function leiteBackendsAb(hbEncoder: readonly string[]): string[] {
|
|
const namen = new Set(hbEncoder.map((e) => e.toLowerCase()))
|
|
const gefunden: string[] = []
|
|
if ([...namen].some((n) => n.startsWith('x264'))) gefunden.push('cpu-x264')
|
|
if ([...namen].some((n) => n.startsWith('x265'))) gefunden.push('cpu-x265')
|
|
if ([...namen].some((n) => n.startsWith('svt_av1'))) gefunden.push('cpu-av1')
|
|
for (const [kennzeichen, backend] of HW_FAMILIEN) {
|
|
const treffer = [...namen].filter((n) => n.includes(kennzeichen))
|
|
if (treffer.length === 0) continue
|
|
gefunden.push(backend)
|
|
if (treffer.some((n) => n.includes('av1'))) gefunden.push(`${backend}-av1`)
|
|
}
|
|
return gefunden
|
|
}
|
|
|
|
/** Preset-Namen aus `--preset-list`: genau vier Leerzeichen Einrückung.
|
|
* Kategorien (0), Beschreibungen (8) und Logzeilen fallen so weg. */
|
|
export function parsePresetListe(text: string): string[] {
|
|
const presets: string[] = []
|
|
for (const zeile of (text ?? '').split(/\r?\n/)) {
|
|
if (zeile.trim().length === 0) continue
|
|
const einrueckung = zeile.length - zeile.replace(/^ +/, '').length
|
|
if (einrueckung === 4) presets.push(zeile.trim())
|
|
}
|
|
return presets
|
|
}
|
|
|
|
function lauf(programm: string, args: string[], timeoutMs: number): Promise<string> {
|
|
return new Promise((fertig) => {
|
|
const kind = werkzeugSpawn(programm, args, { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
const stuecke: Buffer[] = []
|
|
kind.stdout.on('data', (s: Buffer) => stuecke.push(s))
|
|
kind.stderr.on('data', (s: Buffer) => stuecke.push(s))
|
|
const zeiger = setTimeout(() => kind.kill(), timeoutMs)
|
|
kind.on('error', () => {
|
|
clearTimeout(zeiger)
|
|
fertig('')
|
|
})
|
|
kind.on('close', () => {
|
|
clearTimeout(zeiger)
|
|
fertig(textVon(Buffer.concat(stuecke)))
|
|
})
|
|
})
|
|
}
|
|
|
|
export interface EncoderAuskunft {
|
|
encoder: string[]
|
|
backends: string[]
|
|
presets: string[]
|
|
}
|
|
|
|
/** Fragt HandBrake selbst — leer, wenn es nicht da ist (kein Raten). */
|
|
export async function encoderMessen(programm: string): Promise<EncoderAuskunft> {
|
|
if (programm.length === 0) return { encoder: [], backends: [], presets: [] }
|
|
const [hilfe, presetListe] = await Promise.all([
|
|
lauf(programm, ['--help'], 30_000),
|
|
lauf(programm, ['--preset-list'], 30_000),
|
|
])
|
|
const encoder = parseEncoderListe(hilfe)
|
|
return { encoder, backends: leiteBackendsAb(encoder), presets: parsePresetListe(presetListe) }
|
|
}
|