// Tests der HandBrake-Schicht — die Testfälle aus test_ripping/caps // wandern sinngemäß mit. Der wichtigste ist eine Anti-Regression: Der // alte Test FORDERTE den falschen Schalter ein (assert "--audio-codec" // in cmd) und schrieb den Fehler damit fest, statt ihn zu finden. import { describe, expect, it } from 'vitest' import { buildHandbrakeArgs, dateiDaneben, HbAuswertung, hbSchluss, HbZeilenLeser, progressAusZeile, etaAusZeile, unbekannterSchalter, unbekanntesPreset, } from '../src/kern/komprimieren/handbrake' import { komprimierenFuer, presetFuer, PRESET_KEINE, sprachliste, DEFAULT_HB_PRESET, } from '../src/kern/komprimieren/presets' describe('buildHandbrakeArgs', () => { const args = buildHandbrakeArgs({ eingabe: 'E:\\roh\\film_t00.mkv', ausgabe: 'E:\\fertig\\Film (2009).mkv', preset: 'H.265 MKV 1080p30', audioSprachen: ['deu', 'eng'], untertitelSprachen: ['deu'], }) it('nutzt --aencoder und NIEMALS --audio-codec (der 16,5-GB-Fund)', () => { // HandBrake meldet einen unbekannten Schalter mit Rückgabewert 0 — // dieser Wächter passt auf, dass der Fehler nie zurückkommt. expect(args).toContain('--aencoder') expect(args.join(' ')).not.toContain('--audio-codec') const stelle = args.indexOf('--aencoder') expect(args[stelle + 1]).toBe('copy') expect(args).toContain('--audio-fallback') }) it('erzwingt den Container aus der Endung (--format av_mkv)', () => { const stelle = args.indexOf('--format') expect(stelle).toBeGreaterThan(-1) expect(args[stelle + 1]).toBe('av_mkv') }) it('Sprachlisten und --all-subtitles/--first-audio', () => { expect(args[args.indexOf('--audio-lang-list') + 1]).toBe('deu,eng') expect(args[args.indexOf('--subtitle-lang-list') + 1]).toBe('deu') expect(args).toContain('--first-audio') expect(args).toContain('--all-subtitles') }) it('ohne Sprachwahl keine Listen-Schalter', () => { const schlicht = buildHandbrakeArgs({ eingabe: 'a.mkv', ausgabe: 'b.mkv', preset: 'X' }) expect(schlicht).not.toContain('--audio-lang-list') expect(schlicht).not.toContain('--subtitle-lang-list') }) }) describe('progressAusZeile — nur die Encoding-Zeile zählt', () => { it('liest den Encode-Fortschritt', () => { expect(progressAusZeile('Encoding: task 1 of 1, 42.57 % (89.31 fps, avg 90.05 fps, ETA 00h12m34s)')).toBe(42) }) it('der Scan-Durchlauf zählt NICHT (die 99-%-Falle)', () => { expect(progressAusZeile('Scanning title 1 of 1, preview 7, 70.00 %')).toBe(-1) expect(progressAusZeile('Encoding: task 1 of 1, Searching for start time, 12.00')).toBe(-1) }) it('zwei Durchläufe werden zusammengerechnet', () => { expect(progressAusZeile('Encoding: task 2 of 2, 50.00 %')).toBe(75) }) it('ein echtes 0 % ist eine Angabe', () => { expect(progressAusZeile('Encoding: task 1 of 1, 0.87 %')).toBe(0) }) }) describe('Fehler-Erkennung', () => { it('unbekanntes Preset (Wortlaut aus dem echten Lauf)', () => { expect(unbekanntesPreset('Invalid preset Gibt Es Nicht 999p')).toBe('Gibt Es Nicht 999p') expect(unbekanntesPreset('Valid presets are:')).toBe('') }) it('unbekannter Schalter (kommt mit Rückgabewert 0!)', () => { expect(unbekannterSchalter('unknown option (--audio-codec)')).toBe('--audio-codec') expect(unbekannterSchalter('Encoding: task 1 of 1, 1.00 %')).toBe('') }) it('hbSchluss hebt die letzten Worte auf', () => { expect(hbSchluss(['a', '', 'b', 'c', 'd', 'e'])).toBe(' — HandBrake sagte zuletzt: b | c | d | e') expect(hbSchluss([])).toBe('') }) }) describe('HbAuswertung.urteil — die bewiesene Reihenfolge', () => { it('falscher Schalter schlägt ALLES, auch Code 0', () => { const a = new HbAuswertung() a.verarbeite('unknown option (--audio-codec)') a.verarbeite('HandBrake has exited.') const urteil = a.urteil(0, 'x.mkv', () => true) expect(urteil.status).toBe('error') if (urteil.status === 'error') { expect(urteil.fehler).toContain('--audio-codec') expect(urteil.fehler).toContain('Fehler in Rippy') } }) it('Code 0 + Datei da = Erfolg', () => { const urteil = new HbAuswertung().urteil(0, 'x.mkv', (p) => p === 'x.mkv') expect(urteil).toEqual({ status: 'success', ausgabe: 'x.mkv' }) }) it('Code 0, Datei fehlt, liegt daneben → Erfolg MIT Hinweis (Container aus Preset)', () => { const urteil = new HbAuswertung().urteil(0, 'E:\\ziel\\film.mkv', (p) => p === 'E:\\ziel\\film.mp4') expect(urteil.status).toBe('success') if (urteil.status === 'success') { expect(urteil.ausgabe).toBe('E:\\ziel\\film.mp4') expect(urteil.hinweis).toContain('Container') } }) it('Code 0 ohne Datei: Ordner wird GENANNT, letzte Worte zitiert', () => { const a = new HbAuswertung() a.verarbeite('No title found.') const urteil = a.urteil(0, 'E:\\ziel\\film.mkv', () => false) expect(urteil.status).toBe('error') if (urteil.status === 'error') { expect(urteil.fehler).toContain('E:\\ziel') expect(urteil.fehler).toContain('No title found.') } }) it('unbekanntes Preset wird erklärt', () => { const a = new HbAuswertung() a.verarbeite('Invalid preset Uralt 480p') const urteil = a.urteil(3, 'x.mkv', () => false) expect(urteil.status).toBe('error') if (urteil.status === 'error') expect(urteil.fehler).toContain('Uralt 480p') }) }) describe('dateiDaneben', () => { it('findet die Datei mit anderer Endung im selben Ordner', () => { expect(dateiDaneben('E:\\z\\film.mkv', (p) => p === 'E:\\z\\film.mp4')).toBe('E:\\z\\film.mp4') expect(dateiDaneben('E:\\z\\film.mkv', () => false)).toBe('') }) }) describe('HbZeilenLeser — CR trennt den Fortschritt', () => { it('CR, LF und CRLF trennen; halbe Zeilen bleiben liegen', () => { const leser = new HbZeilenLeser() expect(leser.hinzu(Buffer.from('Encoding: task 1 of 1, 1.00 %\rEncoding: task 1 of 1, 2.00'))).toEqual([ 'Encoding: task 1 of 1, 1.00 %', ]) expect(leser.hinzu(Buffer.from(' %\r\nfertig\n'))).toEqual(['Encoding: task 1 of 1, 2.00 %', 'fertig']) }) it('das ü aus zwei Kodierungen tötet den Leser NICHT (0x81-Falle)', () => { const leser = new HbZeilenLeser() const zeilen = leser.hinzu( Buffer.concat([Buffer.from('pfad Gl'), Buffer.from([0x81]), Buffer.from('ck.mkv\n')]), ) expect(zeilen).toHaveLength(1) expect(zeilen[0]).toContain('pfad Gl') }) }) describe('presets', () => { it('komprimierenFuer: globaler Schalter schlägt alles; PRESET_KEINE je Typ', () => { expect(komprimierenFuer('uhd', { transcodeEnabled: false })).toBe(false) expect(komprimierenFuer('uhd', { transcodePresetUhd: PRESET_KEINE })).toBe(false) expect(komprimierenFuer('dvd', { transcodePresetUhd: PRESET_KEINE })).toBe(true) expect(komprimierenFuer('bluray', {})).toBe(true) }) it('presetFuer: Typ-Preset → allgemein → Default, nie „keine"', () => { expect(presetFuer('dvd', { transcodePresetDvd: 'Fast 480p' })).toBe('Fast 480p') expect(presetFuer('dvd', { transcodePreset: 'Allgemein' })).toBe('Allgemein') expect(presetFuer('dvd', {})).toBe(DEFAULT_HB_PRESET) expect(presetFuer('uhd', { transcodePresetUhd: PRESET_KEINE, transcodePreset: 'A' })).toBe('A') }) it('sprachliste säubert, dedupliziert, erzwingt Kleinschreibung', () => { expect(sprachliste('deu, eng , ,DEU')).toEqual(['deu', 'eng']) expect(sprachliste(['ENG', 'eng'])).toEqual(['eng']) expect(sprachliste(null)).toEqual([]) }) }) describe('buildHandbrakeArgs — Untertitel vorwählen (5.4.0, am HandBrake 1.11 gemessen)', () => { it('mit Sprachliste und untertitelStandard kommt --subtitle-default=1 dazu', () => { const args = buildHandbrakeArgs({ eingabe: 'a.mkv', ausgabe: 'b.mkv', preset: 'X', audioSprachen: ['jpn'], untertitelSprachen: ['deu', 'eng'], untertitelStandard: true, }) expect(args).toContain('--subtitle-default=1') expect(args[args.indexOf('--subtitle-lang-list') + 1]).toBe('deu,eng') }) it('ohne Sprachliste KEINE Vorwahl — die Reihenfolge wäre nicht bestimmt', () => { const args = buildHandbrakeArgs({ eingabe: 'a.mkv', ausgabe: 'b.mkv', preset: 'X', untertitelStandard: true }) expect(args).not.toContain('--subtitle-default=1') }) }) describe('etaAusZeile — Restzeit aus der Encoding-Zeile (5.6.0, Format aus dem Binary)', () => { it('liest Stunden, Minuten, Sekunden', () => { expect(etaAusZeile('Encoding: task 1 of 1, 42.57 % (89.31 fps, avg 90.05 fps, ETA 00h12m34s)')).toBe(12 * 60 + 34) expect(etaAusZeile('Encoding: task 2 of 2, 10.00 % (5.00 fps, avg 5.00 fps, ETA 01h00m05s)')).toBe(3605) }) it('ohne Klammer (erste Sekunden) oder außerhalb der Encoding-Zeile: -1', () => { expect(etaAusZeile('Encoding: task 1 of 1, 0.87 %')).toBe(-1) expect(etaAusZeile('Scanning title 1 of 1, preview 7, 70.00 % ETA 00h00m01s')).toBe(-1) }) it('HbAuswertung meldet die Restzeit mit, auch wenn der Prozentwert gleich bleibt', () => { const a = new HbAuswertung() expect(a.verarbeite('Encoding: task 1 of 1, 10.00 % (50.00 fps, avg 50.00 fps, ETA 00h10m00s)')).toEqual({ prozent: 10, restS: 600 }) expect(a.verarbeite('Encoding: task 1 of 1, 10.40 % (50.00 fps, avg 50.00 fps, ETA 00h09m59s)')).toEqual({ prozent: 10, restS: 599 }) expect(a.verarbeite('Encoding: task 1 of 1, 10.40 % (50.00 fps, avg 50.00 fps, ETA 00h09m59s)')).toEqual({}) expect(a.verarbeite('Encoding: task 1 of 1, 11.00 %')).toEqual({ prozent: 11 }) }) })