fix(caps,ui): Encoder werden gemessen statt behauptet
Der Modulkopf von caps.py verspricht "ehrlich erkannt, nicht behauptet" -
erkenne_encoder() tat aber drei Mal das Gegenteil:
1. cpu-x264 und cpu-x265 standen fest verdrahtet in der Liste ("immer
dabei"). Ein Rip-Worker OHNE HandBrake behauptete damit, komprimieren zu
koennen; jeder Transcode dort endete sofort mit "nicht installiert".
2. 'vaapi' wurde allein wegen /dev/dri gemeldet, ohne zu pruefen, ob
HandBrake das ueberhaupt kann. Auf der VM gemessen: das Worker-Image
kennt svt_av1/x264/x265/mpeg4/mpeg2/VP8/VP9/theora und KEINEN einzigen
Hardware-Encoder. Rippy haette VAAPI versprochen und dann versagt.
3. Ueber die Rechenleistung sagte es gar nichts. Genau deshalb war nicht zu
sehen, dass ein 4K-Encode auf dieser Maschine Tage braucht: CPU-Modell
ist das generische "QEMU Virtual CPU version 2.5+", grep -c avx2
/proc/cpuinfo = 0, nur bis sse4_2. x265 lebt von AVX2. Gemessen an der
Leseposition des Quellstroms: 28-55 h fuer einen Film, 3,84 von 4 Kernen
gesaettigt. Abhilfe waere der CPU-Typ 'host' in Proxmox.
Jetzt wird die Encoder-Liste aus `HandBrakeCLI --help` geparst (Abschnitt
-e/--encoder, Formatstrings im Worker-Image gemessen - AGENTS Regel D), und
Hardware zaehlt nur, wenn Geraet UND HandBrake-Unterstuetzung da sind. Neu
gemeldet: cpu-av1 (svt_av1 ist wirklich verfuegbar) sowie CPU-Modell,
Kernzahl und Vektorbefehlsstufe je Worker.
UI: Kerne/Vektorbefehle stehen bei jedem Worker (Worker-Tab und
Einstellungen -> System), dazu die ungefilterte HandBrake-Auskunft und eine
sichtbare Warnung, wenn AVX2 fehlt - inklusive des Hinweises auf den
VM-CPU-Typ. Der Warntext liegt in lib/encoder.ts, damit er nicht doppelt
im Code steht.
Mit im gleichen Zug (dieselbe Datei): der Schalter "Alle Tracks rippen" ist
entfallen. Er war ein Placebo - abcde bekommt keine Track-Auswahl und es gab
auch keine Oberflaeche, um eine Teilmenge zu waehlen. Eine Audio-CD wurde
also immer vollstaendig gerippt, egal wie der Schalter stand. An seiner
Stelle steht jetzt die Wahrheit.
7 Tests, Testdaten sind echte HandBrake-Ausgaben von der VM. Der Parser ist
zusaetzlich gegen die vollen 697 Zeilen der echten --help gegengeprueft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ interface WorkerInfo {
|
||||
const ENCODER_LABELS: Record<string, string> = {
|
||||
'cpu-x264': 'CPU · H.264 (x264)',
|
||||
'cpu-x265': 'CPU · H.265 (x265)',
|
||||
'cpu-av1': 'CPU · AV1 (SVT-AV1)',
|
||||
'vaapi': 'Hardware · VAAPI (AMD/Intel)',
|
||||
'nvenc': 'Hardware · NVENC (NVIDIA)',
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ interface WorkerWahl {
|
||||
}
|
||||
|
||||
const ENCODER_KURZ: Record<string, string> = {
|
||||
'cpu-x264': 'H.264', 'cpu-x265': 'H.265', 'vaapi': 'VAAPI⚡', 'nvenc': 'NVENC⚡',
|
||||
'cpu-x264': 'H.264', 'cpu-x265': 'H.265', 'cpu-av1': 'AV1',
|
||||
'vaapi': 'VAAPI⚡', 'nvenc': 'NVENC⚡',
|
||||
}
|
||||
|
||||
interface TitelInfo {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Server, RefreshCw, Copy, CheckCircle, Trash2, Download } from 'lucide-react'
|
||||
import { api } from '../lib/api'
|
||||
import { simdWarnung } from '../lib/encoder'
|
||||
import { useToast } from '../context/ToastContext'
|
||||
import { Card, CardHeader, CardTitle, CardContent } from './ui/Card'
|
||||
import { Button } from './ui/Button'
|
||||
@@ -16,16 +17,22 @@ interface WorkerInfo {
|
||||
ip?: string
|
||||
makemkv?: string
|
||||
handbrake?: string
|
||||
cpu_modell?: string
|
||||
cpu_kerne?: string
|
||||
cpu_simd?: string
|
||||
handbrake_encoder?: string
|
||||
}
|
||||
}
|
||||
|
||||
const ENCODER_LABELS: Record<string, string> = {
|
||||
'cpu-x264': 'CPU · H.264',
|
||||
'cpu-x265': 'CPU · H.265',
|
||||
'cpu-av1': 'CPU · AV1 (SVT)',
|
||||
'vaapi': 'GPU · VAAPI (AMD/Intel)',
|
||||
'nvenc': 'GPU · NVENC (NVIDIA)',
|
||||
}
|
||||
|
||||
|
||||
function relativeZeit(iso?: string): string {
|
||||
if (!iso) return 'nie'
|
||||
const sekunden = Math.floor((Date.now() - new Date(iso).getTime()) / 1000)
|
||||
@@ -134,6 +141,18 @@ export default function WorkerVerwaltung() {
|
||||
{w.info?.ip ? ` · IP ${w.info.ip}` : ''}
|
||||
{w.info?.hostname && w.info.hostname !== w.name ? ` · ID ${w.info.hostname}` : ''}
|
||||
</p>
|
||||
{(w.info?.cpu_kerne || w.info?.cpu_simd) && (
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400" title={w.info?.cpu_modell || ''}>
|
||||
{w.info?.cpu_kerne ? `${w.info.cpu_kerne} Kerne` : ''}
|
||||
{w.info?.cpu_kerne && w.info?.cpu_simd ? ' · ' : ''}
|
||||
{w.info?.cpu_simd ? `Vektorbefehle ${w.info.cpu_simd}` : ''}
|
||||
</p>
|
||||
)}
|
||||
{simdWarnung(w.info?.cpu_simd) && (
|
||||
<p className="mt-1 text-xs text-amber-600 dark:text-amber-400 max-w-xl">
|
||||
{simdWarnung(w.info?.cpu_simd)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -80,6 +80,7 @@ export const LOG_LEVEL_STYLES = {
|
||||
export const ENCODER_BADGES: Record<string, { label: string; highlight: boolean }> = {
|
||||
'cpu-x264': { label: 'H.264 (CPU)', highlight: false },
|
||||
'cpu-x265': { label: 'H.265 (CPU)', highlight: false },
|
||||
'cpu-av1': { label: 'AV1 (CPU)', highlight: false },
|
||||
'vaapi': { label: 'VAAPI ⚡', highlight: true },
|
||||
'nvenc': { label: 'NVENC ⚡', highlight: true },
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Encoder-Fähigkeiten eines Workers in Klartext übersetzen.
|
||||
//
|
||||
// Befund 25.07.2026: Die Rippy-VM lief auf dem generischen QEMU-CPU-Modell
|
||||
// („QEMU Virtual CPU version 2.5+") und hatte deshalb kein AVX2, nur sse4_2.
|
||||
// x265 lebt von diesen Vektorbefehlen — ein 4K-Encode brauchte dort gemessene
|
||||
// 28-55 Stunden, und nirgends im UI war das zu sehen. Der Worker meldet die
|
||||
// Angaben seit dieser Runde selbst (worker/caps.py), hier werden sie gedeutet.
|
||||
|
||||
// Stufen, mit denen Software-Encoding brauchbar schnell ist.
|
||||
export const SIMD_SCHNELL = ['avx512f', 'avx2']
|
||||
|
||||
/**
|
||||
* Warnt, wenn die CPU dieses Workers zu schwach für Software-Encoding ist.
|
||||
* Gibt null zurück, wenn alles in Ordnung ist ODER die Stufe unbekannt ist —
|
||||
* lieber nichts sagen als etwas Falsches behaupten.
|
||||
*/
|
||||
export function simdWarnung(simd?: string): string | null {
|
||||
if (!simd || simd === 'unbekannt') return null
|
||||
if (SIMD_SCHNELL.includes(simd)) return null
|
||||
return `Diese CPU kann nur ${simd} (kein AVX2) — Software-Encoding ist hier `
|
||||
+ 'sehr langsam. Bei einer virtuellen Maschine hilft es meist, den '
|
||||
+ 'CPU-Typ auf "host" zu stellen; sonst besser einen Worker mit '
|
||||
+ 'Hardware-Encoder wählen.'
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { Button } from '../components/ui/Button'
|
||||
import { Input, Select, Toggle } from '../components/ui/Input'
|
||||
import { MEDIA_SERVER_OPTIONEN } from '../lib/mediaServer'
|
||||
import { ENCODER_BADGES } from '../lib/design'
|
||||
import { simdWarnung } from '../lib/encoder'
|
||||
|
||||
interface SettingsState {
|
||||
tmdbApiKey: string
|
||||
@@ -19,7 +20,6 @@ interface SettingsState {
|
||||
movieDir: string
|
||||
seriesDir: string
|
||||
musicDir: string
|
||||
ripAllTracks: boolean
|
||||
mainFeatureOnly: boolean
|
||||
autoEject: boolean
|
||||
autoRipStart: boolean
|
||||
@@ -48,7 +48,6 @@ const defaultSettings: SettingsState = {
|
||||
movieDir: 'movies',
|
||||
seriesDir: 'series',
|
||||
musicDir: 'music',
|
||||
ripAllTracks: true,
|
||||
mainFeatureOnly: false,
|
||||
autoEject: true,
|
||||
autoRipStart: false,
|
||||
@@ -74,7 +73,15 @@ interface WorkerInfo {
|
||||
// keydb: "ja" | "nein" | "unbekannt" — sagt, ob DIESER Worker eine KEYDB.cfg
|
||||
// in seinem MakeMKV-Datenverzeichnis sieht. Nur der Worker, der wirklich
|
||||
// rippt, zaehlt, deshalb steht die Angabe pro Worker und nicht global.
|
||||
info?: { makemkv?: string, handbrake?: string, makemkv_key?: string, keydb?: string }
|
||||
// cpu_kerne/cpu_simd: Rechenleistung fuers Software-Encodieren. Ohne diese
|
||||
// Angaben war am 25.07.2026 nicht zu sehen, dass ein 4K-Encode auf der
|
||||
// Rippy-VM Tage statt Stunden braucht (kein AVX2, 4 Kerne).
|
||||
// handbrake_encoder: die ungefilterte Encoder-Auskunft von HandBrake selbst.
|
||||
info?: {
|
||||
makemkv?: string, handbrake?: string, makemkv_key?: string, keydb?: string,
|
||||
cpu_modell?: string, cpu_kerne?: string, cpu_simd?: string,
|
||||
handbrake_encoder?: string,
|
||||
}
|
||||
}
|
||||
|
||||
// Antwort von GET/POST/DELETE /system/keydb (Feldnamen exakt wie die API sie liefert).
|
||||
@@ -427,14 +434,16 @@ export default function SettingsPage() {
|
||||
🎵 Audio-CD
|
||||
</p>
|
||||
|
||||
<div className="p-4 rounded-xl border border-slate-200/80 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-950/80">
|
||||
<Toggle
|
||||
label="Alle Tracks rippen"
|
||||
description="Rippe alle Tracks einer CD (FLAC + MusicBrainz-Tags)."
|
||||
checked={settings.ripAllTracks}
|
||||
onChange={(v) => handleChange('ripAllTracks', v)}
|
||||
/>
|
||||
</div>
|
||||
{/* Der Schalter „Alle Tracks rippen" ist am 25.07.2026 entfallen.
|
||||
Er war ein Placebo: abcde bekommt keine Track-Auswahl (siehe
|
||||
build_abcde_cmd) und es gibt auch keine Oberflaeche, um eine
|
||||
Teilmenge zu waehlen — eine Audio-CD wurde also immer
|
||||
vollstaendig gerippt, egal wie der Schalter stand. Statt einer
|
||||
Wahl, die nichts bewirkt, steht hier jetzt die Wahrheit. */}
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 px-1">
|
||||
💿 Eine Audio-CD wird immer vollständig gerippt — alle Tracks als
|
||||
FLAC, mit MusicBrainz-Tags.
|
||||
</p>
|
||||
|
||||
<p className="text-xs font-bold uppercase tracking-wider pt-2 text-slate-400 dark:text-slate-500">
|
||||
Allgemein
|
||||
@@ -555,6 +564,24 @@ export default function SettingsPage() {
|
||||
{ENCODER_BADGES[e]?.label || e}
|
||||
</span>
|
||||
))}
|
||||
{(w.info?.cpu_kerne || w.info?.cpu_simd) && (
|
||||
<p className="mt-1 text-xs text-slate-500 dark:text-slate-400" title={w.info?.cpu_modell || ''}>
|
||||
{w.info?.cpu_modell ? `${w.info.cpu_modell} · ` : ''}
|
||||
{w.info?.cpu_kerne ? `${w.info.cpu_kerne} Kerne` : ''}
|
||||
{w.info?.cpu_kerne && w.info?.cpu_simd ? ' · ' : ''}
|
||||
{w.info?.cpu_simd ? `Vektorbefehle ${w.info.cpu_simd}` : ''}
|
||||
</p>
|
||||
)}
|
||||
{simdWarnung(w.info?.cpu_simd) && (
|
||||
<p className="mt-1 text-xs text-amber-600 dark:text-amber-400">
|
||||
{simdWarnung(w.info?.cpu_simd)}
|
||||
</p>
|
||||
)}
|
||||
{w.info?.handbrake_encoder && (
|
||||
<p className="mt-1 text-xs text-slate-400 dark:text-slate-500 font-mono break-all">
|
||||
HandBrake meldet: {w.info.handbrake_encoder}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
+128
-9
@@ -6,26 +6,133 @@ Remote-GPU-Worker meldet sich hier genauso wie der eingebaute CPU-Worker.
|
||||
"""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
def erkenne_encoder() -> list:
|
||||
"""Liste der verfügbaren Encoder-Backends auf dieser Maschine."""
|
||||
gefunden = ["cpu-x264", "cpu-x265"] # HandBrake-Software-Encoder, immer dabei
|
||||
HB_ENCODER_KOPF = re.compile(r"^-e,\s*--encoder\b")
|
||||
|
||||
# VAAPI: AMD (VCN) und Intel (QuickSync) melden sich über /dev/dri
|
||||
if os.path.exists("/dev/dri/renderD128"):
|
||||
# Reihenfolge = Anzeige-Reihenfolge im UI.
|
||||
SIMD_STUFEN = ("avx512f", "avx2", "avx", "sse4_2")
|
||||
|
||||
|
||||
def parse_encoder_liste(hilfe_text: str) -> list:
|
||||
"""Video-Encoder, die DIESES HandBrake wirklich anbietet.
|
||||
|
||||
Quelle: `HandBrakeCLI --help`, Abschnitt `-e, --encoder <string>`
|
||||
(AGENTS Regel D — im Worker-Image am 25.07.2026 gemessen, nicht geraten):
|
||||
|
||||
-e, --encoder <string> Select video encoder:
|
||||
svt_av1
|
||||
x264
|
||||
x265
|
||||
...
|
||||
--encoder-preset <string>
|
||||
|
||||
Der Block endet an der nächsten Option — also an der ersten Zeile, deren
|
||||
erstes nicht-leeres Zeichen ein Bindestrich ist.
|
||||
"""
|
||||
encoder = []
|
||||
im_block = False
|
||||
for zeile in (hilfe_text or "").splitlines():
|
||||
inhalt = zeile.strip()
|
||||
if not im_block:
|
||||
if HB_ENCODER_KOPF.match(inhalt):
|
||||
im_block = True
|
||||
continue
|
||||
if not inhalt:
|
||||
continue
|
||||
if inhalt.startswith("-"):
|
||||
break
|
||||
encoder.append(inhalt.split()[0])
|
||||
return encoder
|
||||
|
||||
|
||||
def leite_backends_ab(hb_encoder, dri_da: bool, nvidia_da: bool) -> list:
|
||||
"""Übersetzt die HandBrake-Encoder-Namen in die Backends, die das UI zeigt.
|
||||
|
||||
Zwei Ehrlichkeits-Regeln, die vorher fehlten (Befund 25.07.2026):
|
||||
* Ohne HandBrake gibt es KEINE Encoder. Vorher standen `cpu-x264` und
|
||||
`cpu-x265` fest verdrahtet in der Liste („immer dabei") — ein reiner
|
||||
Rip-Worker ohne HandBrake behauptete damit, komprimieren zu können.
|
||||
* Hardware zählt nur, wenn das Gerät da ist UND HandBrake den Encoder
|
||||
überhaupt kennt. Vorher genügte `/dev/dri` — das Worker-Image kann
|
||||
laut eigener Auskunft aber gar keinen Hardware-Encoder.
|
||||
"""
|
||||
namen = {e.lower() for e in (hb_encoder or [])}
|
||||
gefunden = []
|
||||
if any(n.startswith("x264") for n in namen):
|
||||
gefunden.append("cpu-x264")
|
||||
if any(n.startswith("x265") for n in namen):
|
||||
gefunden.append("cpu-x265")
|
||||
if any(n.startswith("svt_av1") for n in namen):
|
||||
gefunden.append("cpu-av1")
|
||||
if dri_da and any(("qsv" in n) or ("vce" in n) or ("vaapi" in n) for n in namen):
|
||||
gefunden.append("vaapi")
|
||||
|
||||
# NVENC: NVIDIA-Treiber im Container sichtbar
|
||||
if shutil.which("nvidia-smi") or os.path.exists("/usr/lib/x86_64-linux-gnu/libnvidia-encode.so.1"):
|
||||
if nvidia_da and any("nvenc" in n for n in namen):
|
||||
gefunden.append("nvenc")
|
||||
|
||||
return gefunden
|
||||
|
||||
|
||||
def beste_simd(flags_text: str) -> str:
|
||||
"""Höchste Vektor-Befehlsstufe der CPU — entscheidend fürs Software-Encodieren.
|
||||
|
||||
Befund 25.07.2026: Die Rippy-VM läuft auf dem generischen QEMU-CPU-Modell
|
||||
(„QEMU Virtual CPU version 2.5+") und hat deshalb KEIN AVX2, nur sse4_2.
|
||||
x265 lebt von diesen Befehlen; ein 4K-Encode brauchte dort gemessene
|
||||
28-55 Stunden. Sichtbar war das nirgends — genau diese Lücke schließt die
|
||||
Anzeige. Abhilfe bei Proxmox/KVM: CPU-Typ der VM auf `host` stellen.
|
||||
"""
|
||||
vorhanden = set((flags_text or "").split())
|
||||
for stufe in SIMD_STUFEN:
|
||||
if stufe in vorhanden:
|
||||
return stufe
|
||||
return "unbekannt"
|
||||
|
||||
|
||||
def _lies_cpuinfo() -> tuple:
|
||||
"""(Modellname, Flags-Text, Kernzahl) aus /proc/cpuinfo — leer auf Windows."""
|
||||
modell, flags, kerne = "", "", 0
|
||||
try:
|
||||
with open("/proc/cpuinfo", encoding="utf-8", errors="replace") as f:
|
||||
for zeile in f:
|
||||
if zeile.startswith("processor"):
|
||||
kerne += 1
|
||||
elif not modell and zeile.startswith("model name"):
|
||||
modell = zeile.split(":", 1)[-1].strip()
|
||||
elif not flags and zeile.startswith("flags"):
|
||||
flags = zeile.split(":", 1)[-1].strip()
|
||||
except OSError:
|
||||
pass
|
||||
return modell, flags, kerne
|
||||
|
||||
|
||||
def hole_handbrake_hilfe() -> str:
|
||||
"""`HandBrakeCLI --help` einmal abrufen (leer, wenn nicht installiert)."""
|
||||
if not shutil.which("HandBrakeCLI"):
|
||||
return ""
|
||||
try:
|
||||
aus = subprocess.run(
|
||||
["HandBrakeCLI", "--help"], capture_output=True, text=True, timeout=30
|
||||
)
|
||||
return (aus.stdout or "") + (aus.stderr or "")
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return ""
|
||||
|
||||
|
||||
def erkenne_encoder() -> list:
|
||||
"""Encoder-Backends, die auf DIESER Maschine wirklich nutzbar sind."""
|
||||
hb_encoder = parse_encoder_liste(hole_handbrake_hilfe())
|
||||
dri_da = os.path.exists("/dev/dri/renderD128")
|
||||
nvidia_da = bool(
|
||||
shutil.which("nvidia-smi")
|
||||
or os.path.exists("/usr/lib/x86_64-linux-gnu/libnvidia-encode.so.1")
|
||||
)
|
||||
return leite_backends_ab(hb_encoder, dri_da, nvidia_da)
|
||||
|
||||
|
||||
def erkenne_ip() -> str:
|
||||
"""Beste erratbare eigene IP (UDP-Route-Trick, KEIN echter Traffic).
|
||||
|
||||
@@ -56,11 +163,23 @@ def werkzeug_versionen() -> dict:
|
||||
"""
|
||||
import socket
|
||||
|
||||
modell, flags, kerne = _lies_cpuinfo()
|
||||
info = {
|
||||
# Zuordnung im UI: hostname (für den Celery-Online-Abgleich) + IP
|
||||
"hostname": socket.gethostname(),
|
||||
"ip": erkenne_ip(),
|
||||
# Rechenleistung fürs Software-Encodieren. Ohne diese drei Angaben war
|
||||
# am 25.07.2026 nicht zu sehen, dass ein 4K-Encode auf dieser Maschine
|
||||
# Tage statt Stunden braucht (kein AVX2, 4 Kerne).
|
||||
"cpu_modell": modell or (platform.processor() or "unbekannt"),
|
||||
"cpu_kerne": str(kerne or os.cpu_count() or 0),
|
||||
"cpu_simd": beste_simd(flags),
|
||||
}
|
||||
hb_encoder = parse_encoder_liste(hole_handbrake_hilfe())
|
||||
if hb_encoder:
|
||||
# Die ungefilterte Auskunft von HandBrake — damit im UI nachprüfbar ist,
|
||||
# woher die Backend-Liste kommt.
|
||||
info["handbrake_encoder"] = ",".join(hb_encoder)
|
||||
if shutil.which("makemkvcon"):
|
||||
info["makemkv"] = os.getenv("MAKEMKV_VERSION") or "installiert"
|
||||
if shutil.which("HandBrakeCLI"):
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""Tests der Fähigkeiten-Erkennung — reine Funktionen, keine Infrastruktur.
|
||||
|
||||
Die Testdaten sind ECHTE Ausgaben, am 25.07.2026 im Worker-Image der Rippy-VM
|
||||
abgenommen (AGENTS Regel D). Erfundene Formate hätten hier keinen Wert: genau
|
||||
daran ist die Erkennung vorher gescheitert.
|
||||
"""
|
||||
|
||||
import caps
|
||||
|
||||
# Wörtlich aus `docker exec rippy-worker-1 HandBrakeCLI --help` (HandBrake im
|
||||
# Worker-Image, 25.07.2026). Bemerkenswert: KEIN Hardware-Encoder dabei.
|
||||
HB_HILFE_ECHT = """
|
||||
-Z, --preset <string> Select preset by name
|
||||
-e, --encoder <string> Select video encoder:
|
||||
svt_av1
|
||||
svt_av1_10bit
|
||||
x264
|
||||
x264_10bit
|
||||
x265
|
||||
x265_10bit
|
||||
x265_12bit
|
||||
mpeg4
|
||||
mpeg2
|
||||
VP8
|
||||
VP9
|
||||
VP9_10bit
|
||||
theora
|
||||
--encoder-preset <string>
|
||||
Adjust video encoding settings
|
||||
"""
|
||||
|
||||
# Wie dieselbe Stelle auf einer Maschine MIT Hardware-Encodern aussieht
|
||||
# (HandBrake-Windows-Build; Struktur identisch, nur mehr Namen).
|
||||
HB_HILFE_MIT_HARDWARE = """
|
||||
-e, --encoder <string> Select video encoder:
|
||||
x264
|
||||
x265
|
||||
nvenc_h264
|
||||
nvenc_h265
|
||||
qsv_h264
|
||||
qsv_h265
|
||||
--encoder-preset <string>
|
||||
"""
|
||||
|
||||
|
||||
def test_encoder_liste_wird_aus_echter_hilfe_gelesen():
|
||||
encoder = caps.parse_encoder_liste(HB_HILFE_ECHT)
|
||||
assert encoder == [
|
||||
"svt_av1", "svt_av1_10bit",
|
||||
"x264", "x264_10bit",
|
||||
"x265", "x265_10bit", "x265_12bit",
|
||||
"mpeg4", "mpeg2", "VP8", "VP9", "VP9_10bit", "theora",
|
||||
]
|
||||
# Der Block endet an der nächsten Option — sie darf nicht mitgelesen werden
|
||||
assert not any("encoder-preset" in e for e in encoder)
|
||||
assert not any(e.startswith("-") for e in encoder)
|
||||
|
||||
|
||||
def test_encoder_liste_ohne_handbrake_ist_leer():
|
||||
assert caps.parse_encoder_liste("") == []
|
||||
assert caps.parse_encoder_liste(None) == []
|
||||
assert caps.parse_encoder_liste("irgendein Text ohne Encoder-Abschnitt") == []
|
||||
|
||||
|
||||
def test_ohne_handbrake_keine_encoder():
|
||||
"""Der wichtigste Fall: vorher standen cpu-x264/cpu-x265 fest verdrahtet in
|
||||
der Liste. Ein Rip-Worker ohne HandBrake behauptete damit, komprimieren zu
|
||||
können — und jeder Transcode dort endete sofort mit 'nicht installiert'."""
|
||||
assert caps.leite_backends_ab([], dri_da=True, nvidia_da=True) == []
|
||||
|
||||
|
||||
def test_backends_aus_echter_liste():
|
||||
encoder = caps.parse_encoder_liste(HB_HILFE_ECHT)
|
||||
# Kein /dev/dri, kein NVIDIA — und HandBrake kann hier ohnehin keine Hardware
|
||||
assert caps.leite_backends_ab(encoder, dri_da=False, nvidia_da=False) == [
|
||||
"cpu-x264", "cpu-x265", "cpu-av1",
|
||||
]
|
||||
|
||||
|
||||
def test_hardware_nur_wenn_geraet_UND_handbrake_es_kann():
|
||||
"""Befund 25.07.2026: Vorher genügte /dev/dri, um 'vaapi' zu melden. Das
|
||||
Worker-Image kann laut eigener Auskunft aber gar keinen Hardware-Encoder —
|
||||
Rippy hätte VAAPI versprochen und wäre beim Encodieren gescheitert."""
|
||||
ohne_hw = caps.parse_encoder_liste(HB_HILFE_ECHT)
|
||||
assert "vaapi" not in caps.leite_backends_ab(ohne_hw, dri_da=True, nvidia_da=True)
|
||||
assert "nvenc" not in caps.leite_backends_ab(ohne_hw, dri_da=True, nvidia_da=True)
|
||||
|
||||
mit_hw = caps.parse_encoder_liste(HB_HILFE_MIT_HARDWARE)
|
||||
# Gerät fehlt → trotz Können kein Hardware-Backend
|
||||
assert caps.leite_backends_ab(mit_hw, dri_da=False, nvidia_da=False) == [
|
||||
"cpu-x264", "cpu-x265",
|
||||
]
|
||||
# Beides da → beides gemeldet
|
||||
beides = caps.leite_backends_ab(mit_hw, dri_da=True, nvidia_da=True)
|
||||
assert "vaapi" in beides # qsv_* vorhanden
|
||||
assert "nvenc" in beides
|
||||
|
||||
|
||||
def test_simd_stufe_erkennt_die_hoechste():
|
||||
assert caps.beste_simd("fpu vme de sse4_2 avx avx2") == "avx2"
|
||||
assert caps.beste_simd("sse4_2 avx avx2 avx512f") == "avx512f"
|
||||
assert caps.beste_simd("fpu vme de pse tsc sse4_2") == "sse4_2"
|
||||
assert caps.beste_simd("") == "unbekannt"
|
||||
assert caps.beste_simd(None) == "unbekannt"
|
||||
|
||||
|
||||
def test_simd_stufe_der_rippy_vm_ist_sse4_2():
|
||||
"""Echt gemessen auf der Rippy-VM (25.07.2026): generisches QEMU-CPU-Modell,
|
||||
`grep -c avx2 /proc/cpuinfo` = 0. Genau das macht Software-4K dort so lahm."""
|
||||
flags_vm = (
|
||||
"fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 "
|
||||
"clflush mmx fxsr sse sse2 syscall nx lm rep_good nopl cpuid "
|
||||
"pni cx16 sse4_1 sse4_2 x2apic popcnt aes hypervisor lahf_lm"
|
||||
)
|
||||
assert caps.beste_simd(flags_vm) == "sse4_2"
|
||||
Reference in New Issue
Block a user