WAS (je Fund der Durchsicht vom 12.09.2026): - F15 Tray „Beenden" fragt nach, wenn ein Rip oder eine Kompression läuft (app.quit reißt makemkvcon/HandBrake über die Leine mit). - F17 „Roh-Dateien löschen" und „Ordner löschen" fragen im Fenster nach (Größe und Pfad stehen dabei) — ein Klick war vorher 30 GB. - F18 Eingabefelder speichern auch bei Enter und beim Verschwinden des Feldes (Bereichswechsel) — Tipparbeit ging vorher verloren. - F19 Dialoge: Escape schließt, role=dialog/aria-modal, Fokus im Dialog. - F20 Toast „Disc erkannt" einmal je Disc und Titel, nicht dreimal. - F21 Was mit dem Poster passiert, steht in den Hinweisen (R4). - F22 Werkzeug-Funde gelten 60 s; ein neuer Pfad und „werkzeuge-laden" suchen frisch — vorher bei fehlendem MakeMKV je Aufruf dreimal reg query /s (bis 45 s, synchron). - F23 movie.nfo/tvshow.nfo tragen <uniqueid type=tmdb|imdb>. - F24 Doppelter Zielordner heißt „Akira (1988) (2)" statt „[bluray-2]". - F25 Umlaute in drei Knopftexten. - F26 Phase „rettet" zählt im Haupt als laufender Vorgang (Update-Sperre, Taskleiste). - F27 „Neu komprimieren" nicht mehr für verschobene, unkomprimierte Vorgänge. - F28 Die Automatik sagt bei Audio-CD und unbekannter Disc, warum sie nichts tut. - F30 Audio-CD-Spuren werden asynchron gelesen (alle acht Blöcke Luft), „Abbrechen" wirkt mitten in der Spur. - A5 Der tote Schlüssel transcodePreset ist aus presetFuer raus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
245 lines
11 KiB
TypeScript
245 lines
11 KiB
TypeScript
// 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 → Empfehlung → Default, nie „keine" (5.7.0: kein allgemeines Preset mehr, A5)', () => {
|
|
expect(presetFuer('dvd', { transcodePresetDvd: 'Fast 480p' })).toBe('Fast 480p')
|
|
// Das alte „allgemeine Preset" ist seit 5.5.0 nicht mehr Teil der
|
|
// Pipeline-Einstellungen — es darf nichts mehr übersteuern.
|
|
expect(presetFuer('dvd', { transcodePreset: 'Allgemein' })).toBe(DEFAULT_HB_PRESET)
|
|
expect(presetFuer('dvd', {})).toBe(DEFAULT_HB_PRESET)
|
|
expect(presetFuer('uhd', { transcodePresetUhd: PRESET_KEINE }, 'H.265 VCN 2160p 4K')).toBe('H.265 VCN 2160p 4K')
|
|
expect(presetFuer('uhd', {}, PRESET_KEINE)).toBe(DEFAULT_HB_PRESET)
|
|
})
|
|
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 })
|
|
})
|
|
})
|
|
|
|
// 5.7.0 (Durchsicht F9): HandBrake schreibt auf einen Zwischennamen; eine
|
|
// halbe Datei trägt nie den echten Namen.
|
|
describe('Zwischenname (5.7.0)', () => {
|
|
it('zwischenname und endname gehören zusammen', async () => {
|
|
const { zwischenname, endname } = await import('../src/kern/komprimieren/handbrake')
|
|
expect(zwischenname('E:\\Filme\\Akira (1988)\\Akira (1988).mkv')).toBe('E:\\Filme\\Akira (1988)\\Akira (1988).teil.mkv')
|
|
expect(endname('E:\\Filme\\Akira (1988)\\Akira (1988).teil.mkv')).toBe('E:\\Filme\\Akira (1988)\\Akira (1988).mkv')
|
|
expect(endname('E:\\x\\title_t00.teil.mp4')).toBe('E:\\x\\title_t00.mp4')
|
|
expect(zwischenname('ohne-endung')).toBe('ohne-endung.teil')
|
|
})
|
|
it('zwischenReste räumt die Zwischen-Datei und ihre Datei daneben', async () => {
|
|
const { zwischenReste } = await import('../src/kern/komprimieren/handbrake')
|
|
const da = new Set(['E:\\x\\a.teil.mkv', 'E:\\x\\a.teil.mp4'])
|
|
const weg: string[] = []
|
|
expect(zwischenReste('E:\\x\\a.teil.mkv', (p) => weg.push(p), (p) => da.has(p))).toEqual(['E:\\x\\a.teil.mkv', 'E:\\x\\a.teil.mp4'])
|
|
expect(zwischenReste('E:\\x\\b.teil.mkv', (p) => weg.push(p), (p) => da.has(p))).toEqual([])
|
|
})
|
|
})
|