fix(transcode): "Abbrechen" wirkt sofort statt erst beim naechsten Prozent
Ampel / ampel (push) Failing after 28s
Ampel / ampel (push) Failing after 28s
Fund des Commanders am laufenden Akira-Job (Nachtrag im vorigen Savepoint):
Job stand auf 'canceling', HandBrake lief weiter. Gemessen 3,4 Minuten
zwischen Anforderung (18:30:17) und Bestaetigung (18:33:38) - bei langsamerem
Fortschritt entsprechend mehr.
Ursache genau wie dort beschrieben: Der Abbruch wurde nur in
datei_fortschritt geprueft, und diese Closure stieg oben sofort wieder aus,
wenn sich die Prozentzahl nicht geaendert hatte ("if gesamt == letzter[0]:
return"). Bei einem Prozent je halber Stunde hing der Abbruch also an einem
Ereignis, das eine halbe Stunde lang nicht eintrat.
run_handbrake bekommt jetzt einen eigenen abbruch_cb neben progress_cb -
dasselbe Muster, das run_makemkv schon fuer log_cb benutzt, und aus
demselben Grund: der Fortschritts-Kanal verwirft Aufrufe. Er wird bei JEDER
Ausgabezeile aufgerufen und im Worker auf 5 s gedrosselt.
Nebeneffekt, der genauso wichtig ist: Er greift auch waehrend des
Scan-Durchlaufs, der ueberhaupt keine Encode-Prozente liefert. Dort war ein
Abbruch vorher grundsaetzlich unmoeglich - und seit die Prozent-Regex den
Scan korrekt ignoriert, waere das sonst sogar schlimmer geworden.
Die Leseschleife ist als _handbrake_schleife() herausgezogen, damit die
Reihenfolge (Abbruch VOR Fortschritt) ohne echtes HandBrake pruefbar ist.
Der Test belegt: zwei Scan-Zeilen genuegen fuer den Abbruch, es muss NICHT
auf eine Encode-Zeile gewartet werden.
Savepoint nachgezogen: Der Encode laeuft nicht mehr, ein Deploy ist
gefahrlos moeglich, und die alte "SOFORT ENTSCHEIDEN"-Passage (Platte
laeuft voll, wenn der Job fertig wird) ist damit gegenstandslos. Neu darin:
die drei Wege fuer UHD, und die Warnung, dass eine /proc-Suche nach
"HandBrake" die eigene Shell mittrifft - zwei Fehlalarme in dieser Sitzung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+56
-24
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
## Aktueller Stand: v3.14 — Durchsicht: vier Placebos und ein unwirksamer Schutz (25.07.2026, 18:40)
|
## Aktueller Stand: v3.14 — Durchsicht: vier Placebos und ein unwirksamer Schutz (25.07.2026, 18:40)
|
||||||
|
|
||||||
> Diese Sitzung hat nichts deployt (der Akira-Encode läuft — ein Rebuild
|
> Alles unten ist committet, die Ampel ist grün, **nichts ist deployt**.
|
||||||
> schießt ihn ab). Alles unten ist committet und die Ampel ist grün.
|
> Der Akira-Encode wurde parallel zu dieser Sitzung um 18:31 abgebrochen
|
||||||
> **Zuerst lesen: „SOFORT ENTSCHEIDEN" ganz unten.**
|
> (siehe Nachtrag im Block darunter) — **es läuft jetzt nichts mehr, ein
|
||||||
|
> Deploy ist gefahrlos möglich.** Zuerst lesen: „SOFORT ENTSCHEIDEN" unten.
|
||||||
|
|
||||||
### DER WICHTIGSTE FUND: der Platten-Schutz aus `c065967` greift nicht
|
### DER WICHTIGSTE FUND: der Platten-Schutz aus `c065967` greift nicht
|
||||||
|
|
||||||
@@ -35,11 +36,16 @@ Falls, der die Platte füllte.
|
|||||||
18:00:39 (Deploy von `8bb075c`), postgres/redis älter.
|
18:00:39 (Deploy von `8bb075c`), postgres/redis älter.
|
||||||
- **Platte:** 148 G, 105 G belegt, 37 G frei = 75 G Rohschnitt + 4,9 G Medien
|
- **Platte:** 148 G, 105 G belegt, 37 G frei = 75 G Rohschnitt + 4,9 G Medien
|
||||||
(Evangelion) + ~25 G System. **Keine Kopier-Reste**, kein `original`-Ordner.
|
(Evangelion) + ~25 G System. **Keine Kopier-Reste**, kein `original`-Ordner.
|
||||||
- **Akira-Encode läuft:** HandBrake PID 40 seit 18:01:37, Preset
|
- **Akira-Job:** `failed`, „Abgebrochen durch Nutzer" (Abbruch 18:30:17
|
||||||
`H.265 MKV 2160p60 4K`, **3,84 von 4 Kernen**. Leseposition im Quellstrom
|
angefordert, Worker bestätigt 18:33:38 → **3,4 Minuten Verzug**, siehe unten).
|
||||||
**1.145.940.149 von 79.604.951.639 Bytes = 1,44 %**.
|
Es läuft **kein** HandBrake mehr (per `/proc` geprüft, Stand 19:06).
|
||||||
- **Restlaufzeit ≈ 28–55 h.** Zwei Messfenster ergaben 395 und 784 KB/s — das
|
- **Der Encode war bei 1,44 %**, gemessen an der Leseposition im Quellstrom
|
||||||
Tempo schwankt mit der Bildkomplexität, deshalb eine Spanne und keine Zahl.
|
(`/proc/<pid>/fdinfo/3`: 1.145.940.149 von 79.604.951.639 Bytes) — exakter
|
||||||
|
als jede Fortschrittsanzeige. Zwei Tempo-Fenster ergaben 395 und 784 KB/s
|
||||||
|
→ **28–55 h** für den Film, bei 3,84 von 4 gesättigten Kernen.
|
||||||
|
- **Achtung bei Prozess-Suchen per `/proc`:** Ein `case "$c" in *HandBrake*)`
|
||||||
|
trifft die eigene Shell mit, weil deren Kommandozeile das Wort enthält. Zwei
|
||||||
|
Fehlalarme in dieser Sitzung. Immer die PID gegenprüfen.
|
||||||
- **Die Ursache dafür ist neu und behebbar:** Die VM läuft auf dem generischen
|
- **Die Ursache dafür ist neu und behebbar:** Die VM läuft auf dem generischen
|
||||||
QEMU-CPU-Modell (`QEMU Virtual CPU version 2.5+`), `grep -c avx2
|
QEMU-CPU-Modell (`QEMU Virtual CPU version 2.5+`), `grep -c avx2
|
||||||
/proc/cpuinfo` = **0**, nur bis `sse4_2`. x265 lebt von AVX2. Abhilfe:
|
/proc/cpuinfo` = **0**, nur bis `sse4_2`. x265 lebt von AVX2. Abhilfe:
|
||||||
@@ -138,26 +144,52 @@ entscheidet.**
|
|||||||
- **Zwei neue API-Tests laufen nur in der Ampel** — `test_api_smoke.py`
|
- **Zwei neue API-Tests laufen nur in der Ampel** — `test_api_smoke.py`
|
||||||
überspringt sich unter Windows selbst (`main.py` braucht `fcntl`).
|
überspringt sich unter Windows selbst (`main.py` braucht `fcntl`).
|
||||||
|
|
||||||
|
### GEBAUT — „Abbrechen" wirkt jetzt sofort
|
||||||
|
|
||||||
|
Der Fund aus dem Nachtrag (Commander, am laufenden Job beobachtet) ist behoben.
|
||||||
|
Ursache war genau wie dort beschrieben: Der Abbruch wurde nur in
|
||||||
|
`datei_fortschritt` geprüft, und diese Closure stieg oben sofort wieder aus,
|
||||||
|
wenn sich die Prozentzahl nicht geändert hatte. Bei einem Prozent je halber
|
||||||
|
Stunde sah „Abbrechen" entsprechend lange wirkungslos aus.
|
||||||
|
|
||||||
|
Jetzt gibt es in `run_handbrake` einen **eigenen Abbruch-Kanal** neben dem
|
||||||
|
Fortschritts-Callback — dasselbe Muster, das `run_makemkv` schon für `log_cb`
|
||||||
|
benutzt, und aus demselben Grund. Er wird bei JEDER Ausgabezeile aufgerufen
|
||||||
|
und ist im Worker auf 5 Sekunden gedrosselt (`ABBRUCH_INTERVALL_SEKUNDEN`).
|
||||||
|
Nebeneffekt: Er greift auch während des Scan-Durchlaufs, der überhaupt keine
|
||||||
|
Encode-Prozente liefert — dort war ein Abbruch vorher grundsätzlich unmöglich.
|
||||||
|
Die Leseschleife ist als `_handbrake_schleife()` herausgezogen, damit die
|
||||||
|
Reihenfolge (Abbruch VOR Fortschritt) ohne echtes HandBrake testbar ist.
|
||||||
|
|
||||||
### SOFORT ENTSCHEIDEN
|
### SOFORT ENTSCHEIDEN
|
||||||
|
|
||||||
**Der laufende Job wird beim Fertigwerden mit dem DEPLOYTEN (alten) Code
|
**1. Deployen — es läuft nichts, der Weg ist frei.** Nichts blockiert mehr, und
|
||||||
versuchen, 75 GB in 37 GB zu kopieren.** `keepOriginal` steht auf `true`, und
|
solange nicht deployt ist, gilt weiterhin der alte Code: unwirksamer
|
||||||
den Wert hat die Aufgabe beim Start um 18:01:37 gelesen — ihn jetzt umzustellen
|
Platten-Schutz, Anzeige auf 99 %, Auswurf ohne Wirkung, träges „Abbrechen".
|
||||||
ändert für diesen Lauf nichts mehr. Der Ablauf wäre: Platte läuft auf 100 %,
|
|
||||||
dann ENOSPC, dann räumt der `except`-Zweig die Teilkopie weg. Selbstheilend —
|
|
||||||
aber währenddessen ist die Systemplatte voll, und genau daran hat sich heute
|
|
||||||
Mittag der Worker-Container verschluckt.
|
|
||||||
|
|
||||||
Die Optionen, in der Reihenfolge, die ich empfehle:
|
```
|
||||||
|
ssh arcane@192.168.178.162 'cd ~/projects/rippy && git pull --ff-only && docker compose up -d --build'
|
||||||
|
```
|
||||||
|
|
||||||
1. **Encode abbrechen, deployen, Kompression neu anstoßen.** Kostet die
|
**2. Die UHD-Strategie ist noch offen** (aus dem Nachtrag, unverändert gültig).
|
||||||
bisherigen ~30 Minuten (der Lauf ist bei 1,44 % — praktisch nichts) und
|
Preset-je-Disc-Typ war richtig, reicht aber nicht: Software-HEVC in 4K ist auf
|
||||||
bringt sofort alle Fixes live, inklusive des Platten-Schutzes und der
|
dieser CPU keine Option. Drei Wege, keiner davon gebaut:
|
||||||
ehrlichen Fortschrittsanzeige. Wer ohnehin über den CPU-Typ nachdenkt,
|
- **UHD gar nicht komprimieren** — Roh-MKV behalten. Ehrlichste Variante,
|
||||||
erledigt das im selben Fenster.
|
kostet Platz (75–100 GB je Film, gehört dann auf die NAS).
|
||||||
2. **Laufen lassen und in 1–2 Tagen deployen**, vorher die 75 GB von Hand
|
- **Hardware-Encoder** — Remote-Worker mit GPU (`nvenc`/`vaapi`). Rippy kann
|
||||||
wegräumen oder auf die NAS schieben, damit die Kopie nicht anlaufen kann.
|
das schon routen (bewiesen v3.7); es fehlt die Maschine. Achtung: Das
|
||||||
3. **Laufen lassen und den vollen Plattenlauf in Kauf nehmen.**
|
Worker-Image kann selbst keinen Hardware-Encoder, ein GPU-Worker braucht ein
|
||||||
|
HandBrake-Build mit `nvenc_*`/`qsv_*` — die neue Anzeige sagt das jetzt.
|
||||||
|
- **CPU-Typ der VM auf `host`** — schaltet AVX2 frei, bringt bei x265 typisch
|
||||||
|
Faktor 2–4. Aus 28–55 h werden damit aber immer noch Stunden bis Tage; das
|
||||||
|
allein löst 4K nicht, hilft aber jedem 1080p-Encode.
|
||||||
|
|
||||||
|
**3. Vier tote Endpunkte** — entfernen oder behalten (Tabelle oben).
|
||||||
|
|
||||||
|
**4. Im Akira-Ordner liegt ein unbrauchbares 4K-Fragment** (die 1080p-Fassung
|
||||||
|
wurde beim Start auf 0 Bytes gekürzt). Der 75-GB-Rohschnitt ist unversehrt,
|
||||||
|
also ist nichts verloren — aber die Datei gehört weg, bevor jemand sie für ein
|
||||||
|
Ergebnis hält.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+46
-25
@@ -339,8 +339,19 @@ def get_progress_from_line(line: str) -> int:
|
|||||||
return int(((aktuell - 1) * 100 + prozent) / gesamt)
|
return int(((aktuell - 1) * 100 + prozent) / gesamt)
|
||||||
|
|
||||||
|
|
||||||
def run_handbrake(input_path: str, output_path: str, preset: str = DEFAULT_HB_PRESET, progress_cb=None) -> dict:
|
def run_handbrake(input_path: str, output_path: str, preset: str = DEFAULT_HB_PRESET,
|
||||||
"""Komprimiert eine MKV-Datei mit HandBrakeCLI; meldet Fortschritt."""
|
progress_cb=None, abbruch_cb=None) -> dict:
|
||||||
|
"""Komprimiert eine MKV-Datei mit HandBrakeCLI; meldet Fortschritt.
|
||||||
|
|
||||||
|
`abbruch_cb` wird bei JEDER Ausgabezeile aufgerufen und soll RipAbbruch
|
||||||
|
werfen, wenn der Nutzer abgebrochen hat. Bewusst ein eigener Kanal neben
|
||||||
|
`progress_cb` — aus demselben Grund wie `log_cb` bei run_makemkv: der
|
||||||
|
Fortschritts-Callback verwirft Aufrufe mit unveränderter Prozentzahl, und
|
||||||
|
genau daran hing der Abbruch früher. Bei einem 4K-Encode mit einem Prozent
|
||||||
|
je halber Stunde sah „Abbrechen" deshalb minutenlang wirkungslos aus
|
||||||
|
(Befund 25.07.2026). Zusätzlich greift es damit auch während des
|
||||||
|
Scan-Durchlaufs, der gar keine Encode-Prozente ausgibt.
|
||||||
|
"""
|
||||||
if not check_handbrake_installed():
|
if not check_handbrake_installed():
|
||||||
return {"status": "error", "error": "HandBrakeCLI ist nicht installiert"}
|
return {"status": "error", "error": "HandBrakeCLI ist nicht installiert"}
|
||||||
|
|
||||||
@@ -352,33 +363,43 @@ def run_handbrake(input_path: str, output_path: str, preset: str = DEFAULT_HB_PR
|
|||||||
text=True,
|
text=True,
|
||||||
bufsize=1
|
bufsize=1
|
||||||
)
|
)
|
||||||
|
return _handbrake_schleife(process, output_path, abbruch_cb, progress_cb)
|
||||||
try:
|
|
||||||
for line in process.stdout:
|
|
||||||
progress = get_progress_from_line(line)
|
|
||||||
# >= 0: ein echtes 0 % ist eine Angabe und muss durch. Der alte
|
|
||||||
# Filter `> 0` verwarf den gesamten ersten Prozentpunkt — bei
|
|
||||||
# einem 4K-Encode sind das Stunden ohne sichtbare Bewegung.
|
|
||||||
if progress >= 0 and progress_cb:
|
|
||||||
progress_cb(progress)
|
|
||||||
except RipAbbruch:
|
|
||||||
process.kill()
|
|
||||||
process.wait()
|
|
||||||
return {"status": "cancelled", "error": "Abgebrochen durch Nutzer"}
|
|
||||||
|
|
||||||
process.wait()
|
|
||||||
|
|
||||||
if process.returncode == 0 and os.path.exists(output_path):
|
|
||||||
return {"status": "success", "output_path": output_path}
|
|
||||||
return {
|
|
||||||
"status": "error",
|
|
||||||
"error": f"HandBrake endete mit Code {process.returncode}",
|
|
||||||
"return_code": process.returncode,
|
|
||||||
}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "error": str(e)}
|
return {"status": "error", "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def _handbrake_schleife(process, output_path: str, abbruch_cb=None, progress_cb=None) -> dict:
|
||||||
|
"""Liest HandBrakes Ausgabe und wertet sie aus. Eigene Funktion, damit die
|
||||||
|
Reihenfolge (Abbruch VOR Fortschritt) ohne echtes HandBrake testbar ist."""
|
||||||
|
try:
|
||||||
|
for line in process.stdout:
|
||||||
|
# Zuerst der Abbruch — unabhängig davon, ob die Zeile überhaupt
|
||||||
|
# einen Fortschritt enthält. Sonst haengt „Abbrechen" daran, dass
|
||||||
|
# sich die Prozentzahl bewegt (Befund 25.07.2026).
|
||||||
|
if abbruch_cb:
|
||||||
|
abbruch_cb()
|
||||||
|
progress = get_progress_from_line(line)
|
||||||
|
# >= 0: ein echtes 0 % ist eine Angabe und muss durch. Der alte
|
||||||
|
# Filter `> 0` verwarf den gesamten ersten Prozentpunkt — bei
|
||||||
|
# einem 4K-Encode sind das Stunden ohne sichtbare Bewegung.
|
||||||
|
if progress >= 0 and progress_cb:
|
||||||
|
progress_cb(progress)
|
||||||
|
except RipAbbruch:
|
||||||
|
process.kill()
|
||||||
|
process.wait()
|
||||||
|
return {"status": "cancelled", "error": "Abgebrochen durch Nutzer"}
|
||||||
|
|
||||||
|
process.wait()
|
||||||
|
|
||||||
|
if process.returncode == 0 and os.path.exists(output_path):
|
||||||
|
return {"status": "success", "output_path": output_path}
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": f"HandBrake endete mit Code {process.returncode}",
|
||||||
|
"return_code": process.returncode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_abcde_cmd(device_path: str, config_path: str, auswerfen: bool = True) -> list:
|
def build_abcde_cmd(device_path: str, config_path: str, auswerfen: bool = True) -> list:
|
||||||
"""Baut das abcde-Kommando (pure Funktion, testbar).
|
"""Baut das abcde-Kommando (pure Funktion, testbar).
|
||||||
|
|
||||||
|
|||||||
+35
-3
@@ -18,6 +18,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
import shutil
|
import shutil
|
||||||
|
import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -92,6 +93,11 @@ def pfad_lokal(pfad: str, mapping: str = None) -> str:
|
|||||||
RAW_DIR = os.getenv("RAW_DIR", "/app/temp/raw")
|
RAW_DIR = os.getenv("RAW_DIR", "/app/temp/raw")
|
||||||
MEDIA_ROOT = "/app/media"
|
MEDIA_ROOT = "/app/media"
|
||||||
|
|
||||||
|
# Wie oft während einer Kompression nachgesehen wird, ob der Nutzer abgebrochen
|
||||||
|
# hat. Eine DB-Abfrage alle paar Sekunden ist nichts gegen einen Encode, der
|
||||||
|
# Stunden läuft — und „Abbrechen" fühlt sich damit sofort an.
|
||||||
|
ABBRUCH_INTERVALL_SEKUNDEN = 5
|
||||||
|
|
||||||
|
|
||||||
def unter_wurzel(pfad: str, wurzel: str) -> bool:
|
def unter_wurzel(pfad: str, wurzel: str) -> bool:
|
||||||
"""Liegt `pfad` wirklich unterhalb von `wurzel` (oder IST es die Wurzel)?
|
"""Liegt `pfad` wirklich unterhalb von `wurzel` (oder IST es die Wurzel)?
|
||||||
@@ -728,6 +734,30 @@ def transcode_files(self, job_id: str, raw_dir: str, final_dir: str):
|
|||||||
|
|
||||||
anzahl = len(quellen)
|
anzahl = len(quellen)
|
||||||
letzter = [-1]
|
letzter = [-1]
|
||||||
|
letzte_abbruchpruefung = [0.0]
|
||||||
|
|
||||||
|
def abbruch_pruefen():
|
||||||
|
"""Zeitgesteuert prüfen, ob der Nutzer abgebrochen hat. Wirft RipAbbruch.
|
||||||
|
|
||||||
|
Befund 25.07.2026 (Commander, am laufenden Akira-Job beobachtet): Der
|
||||||
|
Abbruch wurde NUR in `datei_fortschritt` geprüft — und diese Closure
|
||||||
|
stieg oben sofort wieder aus, wenn sich die Prozentzahl nicht geändert
|
||||||
|
hatte. Bei einem 4K-Encode, der pro Prozent eine halbe Stunde braucht,
|
||||||
|
sah „Abbrechen" entsprechend lange wirkungslos aus (gemessen: 3,4 min
|
||||||
|
zwischen Anforderung 18:30:17 und Bestätigung 18:33:38 — bei noch
|
||||||
|
langsamerem Fortschritt entsprechend mehr).
|
||||||
|
|
||||||
|
Deshalb hängt die Prüfung jetzt an der Zeit statt am Fortschritt und
|
||||||
|
läuft bei JEDER Ausgabezeile von HandBrake — auch während des
|
||||||
|
Scan-Durchlaufs, der überhaupt keine Encode-Prozente liefert.
|
||||||
|
"""
|
||||||
|
jetzt = time.monotonic()
|
||||||
|
if jetzt - letzte_abbruchpruefung[0] < ABBRUCH_INTERVALL_SEKUNDEN:
|
||||||
|
return
|
||||||
|
letzte_abbruchpruefung[0] = jetzt
|
||||||
|
if _abbruch_angefordert(job_id):
|
||||||
|
raise RipAbbruch()
|
||||||
|
|
||||||
for index, quelle in enumerate(quellen):
|
for index, quelle in enumerate(quellen):
|
||||||
ziel = os.path.join(final_dir, os.path.basename(quelle))
|
ziel = os.path.join(final_dir, os.path.basename(quelle))
|
||||||
|
|
||||||
@@ -736,11 +766,13 @@ def transcode_files(self, job_id: str, raw_dir: str, final_dir: str):
|
|||||||
if gesamt == letzter[0]:
|
if gesamt == letzter[0]:
|
||||||
return
|
return
|
||||||
letzter[0] = gesamt
|
letzter[0] = gesamt
|
||||||
if _abbruch_angefordert(job_id):
|
|
||||||
raise RipAbbruch()
|
|
||||||
db.update_job(job_id, progress=min(99, gesamt))
|
db.update_job(job_id, progress=min(99, gesamt))
|
||||||
|
|
||||||
hb = run_handbrake(quelle, ziel, preset=preset, progress_cb=datei_fortschritt)
|
hb = run_handbrake(
|
||||||
|
quelle, ziel, preset=preset,
|
||||||
|
progress_cb=datei_fortschritt,
|
||||||
|
abbruch_cb=abbruch_pruefen,
|
||||||
|
)
|
||||||
if hb.get("status") == "cancelled":
|
if hb.get("status") == "cancelled":
|
||||||
_job_abschliessen(job_id, hb)
|
_job_abschliessen(job_id, hb)
|
||||||
return hb
|
return hb
|
||||||
|
|||||||
@@ -225,3 +225,50 @@ def test_preset_fuer_ohne_einstellungen_nimmt_den_eingebauten_standard():
|
|||||||
assert preset_fuer("", None) == DEFAULT_HB_PRESET
|
assert preset_fuer("", None) == DEFAULT_HB_PRESET
|
||||||
assert preset_fuer(None, {}) == DEFAULT_HB_PRESET
|
assert preset_fuer(None, {}) == DEFAULT_HB_PRESET
|
||||||
assert preset_fuer("cd", {"transcodePresetUhd": "egal"}) == DEFAULT_HB_PRESET
|
assert preset_fuer("cd", {"transcodePresetUhd": "egal"}) == DEFAULT_HB_PRESET
|
||||||
|
|
||||||
|
|
||||||
|
def test_handbrake_prueft_abbruch_bei_jeder_zeile_nicht_nur_bei_fortschritt():
|
||||||
|
"""Befund 25.07.2026 (am laufenden Akira-Job beobachtet): Der Abbruch hing
|
||||||
|
am Fortschritts-Callback, und der stieg bei unveraenderter Prozentzahl
|
||||||
|
sofort aus. Bei einem 4K-Encode mit einem Prozent je halber Stunde sah
|
||||||
|
„Abbrechen" minutenlang wirkungslos aus (gemessen: 3,4 min).
|
||||||
|
|
||||||
|
Der Abbruch-Kanal muss deshalb JEDE Ausgabezeile sehen — auch die des
|
||||||
|
Scan-Durchlaufs, der gar keine Encode-Prozente liefert.
|
||||||
|
"""
|
||||||
|
import ripping
|
||||||
|
|
||||||
|
zeilen = [
|
||||||
|
"Scanning title 1 of 1, preview 1, 10.00 %\n",
|
||||||
|
"Scanning title 1 of 1, preview 2, 20.00 %\n",
|
||||||
|
"Encoding: task 1 of 1, 0.00 %\n",
|
||||||
|
"Encoding: task 1 of 1, 0.00 %\n",
|
||||||
|
]
|
||||||
|
gesehen = []
|
||||||
|
|
||||||
|
class FakeProcess:
|
||||||
|
def __init__(self):
|
||||||
|
self.stdout = iter(zeilen)
|
||||||
|
self.returncode = 0
|
||||||
|
self.getoetet = False
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
self.getoetet = True
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
prozess = FakeProcess()
|
||||||
|
|
||||||
|
def abbruch_cb():
|
||||||
|
gesehen.append(1)
|
||||||
|
if len(gesehen) == 2: # beim zweiten Mal abbrechen
|
||||||
|
raise ripping.RipAbbruch()
|
||||||
|
|
||||||
|
ergebnis = ripping._handbrake_schleife(prozess, "/x.mkv", abbruch_cb, None)
|
||||||
|
|
||||||
|
assert ergebnis["status"] == "cancelled"
|
||||||
|
assert prozess.getoetet is True
|
||||||
|
# Zwei Scan-Zeilen genuegten — es musste NICHT auf eine Encode-Zeile gewartet
|
||||||
|
# werden. Genau das war der Fehler.
|
||||||
|
assert len(gesehen) == 2
|
||||||
|
|||||||
Reference in New Issue
Block a user