"""In welcher Phase ist ein Job gestorben — und was hilft danach wirklich? Commander-Befund 26.07.2026: „Hier gibt es den Button ‚neu' aber WAS wird dann gemacht? Komprimierung? Neu Gerippt? Das muss ja je nach fehlgeschlagenem Job eine Option anbieten." Die Frage war berechtigt und der Knopf war falsch: Er hieß nur „Neu" und rief immer `POST /jobs/{id}/retry-transcode` — also immer die KOMPRESSION. Für einen abgebrochenen Transcode ist das goldrichtig (die Roh-MKV liegt vollständig da, man spart eine Stunde Rippen). Für einen abgebrochenen RIP ist es Unsinn: Am 26.07.2026 lagen 5,1 GB von rund 40 GB da, und „Neu" hätte daraus brav einen Film komprimiert, der bei 12 % aufhört. ## Warum es dafür eine MARKE braucht Sobald `status = "failed"` steht, ist der vorherige Zustand fort — die Spalte hat nur einen Wert. `zombies.war_im_rip()` im Worker kann die Phase noch sehen, weil sie dort im Moment des Aufräumens vorliegt; die API sieht später nur noch „failed". Deshalb schreibt der Worker die Phase in die Job-Metadaten: * Rip-Start → `rip_fertig = false` * Rip fertig (Übergabe an die Kompression) → `rip_fertig = true` * Start eines Transcodes → `rip_fertig = true` (heilt Bestandsjobs mit) ## Drei Antworten, nicht zwei Die Marke FEHLT bei jedem Job, der vor dieser Änderung lief. Diese Jobs „rip" zu nennen wäre geraten — und würde dem Commander an einem Bestandsjob mit 79,6 GB intakter Rohdaten das „Neu komprimieren" wegnehmen, das dort genau richtig ist. Wer die Phase nicht kennt, sagt das: `unklar` lässt das UI beide Wege anbieten, jeden mit einem ehrlichen Satz. """ import json # Schlüssel in den Job-Metadaten. Muss identisch in worker/tasks.py stehen — # es gibt kein geteiltes Paket zwischen den Containern. RIP_FERTIG = "rip_fertig" # Die drei möglichen Antworten. NEU_KOMPRIMIEREN = "transcode" NEU_RIPPEN = "rip" UNKLAR = "unklar" NICHTS = "" def _meta(meta_json) -> dict: """Metadaten lesen, ohne an kaputtem JSON zu scheitern.""" if isinstance(meta_json, dict): return meta_json if not meta_json: return {} try: gelesen = json.loads(meta_json) except (ValueError, TypeError): return {} return gelesen if isinstance(gelesen, dict) else {} def retry_art(job) -> str: """Was soll der Knopf an DIESEM Job anbieten? * `NICHTS` — der Job ist nicht fehlgeschlagen, es gibt nichts zu wiederholen. * `NEU_KOMPRIMIEREN`— der Rip war fertig, nur die Kompression starb. * `NEU_RIPPEN` — der Rip selbst starb; alles Rohe ist ein Bruchstück. * `UNKLAR` — kein Vermerk (Bestandsjob), beide Wege anbieten. """ if (job.get("status") or "") != "failed": return NICHTS marke = _meta(job.get("meta")).get(RIP_FERTIG) if marke is True: return NEU_KOMPRIMIEREN if marke is False: return NEU_RIPPEN return UNKLAR