WAS: Der Auswahl-Dialog zeigt je Titel jede Ton- und Untertitelspur, die der Info-Lauf gemessen hat („German · DTS-HD MA · Surround 5.1", „German · DD · Stereo (Kommentar)", „German · PGS (nur erzwungene)"), vorbelegt wie bisher (je Wunschsprache die erste Tonspur, alle Untertitel der Wunsch- sprachen) — angehakt wird einzeln. Die Untertitel-Vorwahl je Extra ist jetzt eine Spur. Der Parser liest dazu SINFO-Attribute 2 (Anordnung), 22 (Flags) und markiert abgeleitete Spuren (Bit 0x800, an der Kizuna- Disc gemessen): DTS-Kerne werden nicht angeboten, „nur erzwungene"- Untertitel schon. Die Ablage scannt die Roh-Datei und findet die gewählten MakeMKV-Spuren gestuft wieder (Sprache + Codec-Familie + Kanäle → … → Sprache), HandBrake bekommt `--audio 1,3 --subtitle 2,1 --subtitle-default=1`; Fehlendes wird gemeldet, scheitert der Scan, gelten die Sprachen der gewählten Spuren als Liste. MakeMKVs Auswahl- regel lässt Stereo-Doppel jetzt stehen (nur -sel:havecore); Rippys alte Regel wird in der Registry ersetzt, eine fremde bleibt. Dazu F31 (Durchsicht): `--aencoder copy` ohne `--audio-copy-mask` verengt HandBrakeCLI 1.11.2 auf „copy:aac" — AC3/DTS/TrueHD wurden seit 5.0 in AAC umgerechnet. Beide Wege setzen die Maske jetzt ausdrücklich (gemessen 12.09.2026: AC3 bleibt AC3, Passthru-Zeile nennt alle Codecs). WARUM: Commander-Entscheid 12.09.2026 („Jede Spur einzeln anhaken") — bisher ließ sich je Sprache nur „alles oder nichts" wählen, und der Kommentar-Ton fehlte in der Roh-Datei (havemulti). Gemessen am mitgelieferten HandBrakeCLI mit ffmpeg-gebauter Quelle (test/messung.spuren.test.ts, RIPPY_MESSUNG=1): --audio 1,3 → jpn und deu-Kommentar, --subtitle 2,1 --subtitle-default=1 → eng (default) vor deu, --subtitle none → keine Spur. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
486 lines
21 KiB
TypeScript
486 lines
21 KiB
TypeScript
// Tests von Kompression + Ablage (kern/ablauf/ablegen.ts, 5.4.0) — mit
|
|
// nachgebildetem HandBrake, das die Ausgabedatei wirklich SCHREIBT (die
|
|
// Serien-Umbenennung braucht sie). Die Regeln: Kompression nur wenn
|
|
// gewollt UND HandBrake da, sonst VERSCHIEBEN; ein Kompressions-Fehler
|
|
// lässt den verlustfreien Rip STEHEN und sagt wo er liegt; Sprachen je
|
|
// Datei erreichen HandBrake; Folgen von Hand schlagen die Laufzeit-Raterei.
|
|
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import type { KompressionsAuftrag, RohDatei } from '../src/kern/ablauf/auftrag'
|
|
import type { Zuordnung } from '../src/kern/metadaten/zuordnung'
|
|
import { LEERER_TREFFER } from '../src/kern/metadaten/zuordnung'
|
|
|
|
const hb = vi.hoisted(() => ({
|
|
aufrufe: [] as Array<Record<string, unknown>>,
|
|
ergebnis: 'success' as 'success' | 'error' | 'cancelled',
|
|
}))
|
|
|
|
vi.mock('../src/kern/komprimieren/handbrake', async () => {
|
|
const fs = await import('node:fs')
|
|
return {
|
|
komprimieren: vi.fn(async (o: { ausgabe: string; aufFortschritt?: (p: number) => void }) => {
|
|
hb.aufrufe.push({ ...o })
|
|
o.aufFortschritt?.(50)
|
|
if (hb.ergebnis === 'cancelled') return { status: 'cancelled' }
|
|
if (hb.ergebnis === 'error') return { status: 'error', fehler: 'HandBrake endete mit Code 3' }
|
|
fs.writeFileSync(o.ausgabe, 'encoded')
|
|
return { status: 'success', ausgabe: o.ausgabe }
|
|
}),
|
|
}
|
|
})
|
|
|
|
import { komprimierenUndAblegen, type AblageUmgebung } from '../src/kern/ablauf/ablegen'
|
|
|
|
let wegwerf: string[] = []
|
|
afterEach(() => {
|
|
hb.aufrufe = []
|
|
hb.ergebnis = 'success'
|
|
for (const ordner of wegwerf.splice(0)) rmSync(ordner, { recursive: true, force: true })
|
|
})
|
|
|
|
function zuordnung(typ: 'movie' | 'tv', titel: string, jahr: number | null): Zuordnung {
|
|
return {
|
|
titel,
|
|
jahr,
|
|
confidence: 0.95,
|
|
meta: { ...LEERER_TREFFER, typ, id: 149, titel, jahr, quelle: 'tmdb', laufzeitMinuten: 124 },
|
|
}
|
|
}
|
|
|
|
function aufbau(dateinamen: string[], rollen: Array<RohDatei['rolle']> = []): {
|
|
ablage: string
|
|
roh: string
|
|
dateien: RohDatei[]
|
|
} {
|
|
const ablage = mkdtempSync(join(tmpdir(), 'rippy-ablegen-'))
|
|
wegwerf.push(ablage)
|
|
const roh = join(ablage, 'roh', 'bluray-2026-08-30T12-00-00')
|
|
mkdirSync(roh, { recursive: true })
|
|
const dateien = dateinamen.map((name, i) => {
|
|
const pfad = join(roh, name)
|
|
writeFileSync(pfad, 'roh'.repeat(10))
|
|
return {
|
|
pfad,
|
|
titelNr: i,
|
|
rolle: rollen[i] ?? 'hauptfilm',
|
|
dauerS: 3167,
|
|
groesseBytes: 30,
|
|
audioSprachen: [],
|
|
untertitelSprachen: [],
|
|
}
|
|
})
|
|
return { ablage, roh, dateien }
|
|
}
|
|
|
|
function auftrag(teil: Partial<KompressionsAuftrag> & { rohOrdner: string; dateien: RohDatei[] }): KompressionsAuftrag {
|
|
return {
|
|
vorgangId: 'bluray-2026-08-30T12-00-00',
|
|
laufwerk: 'G',
|
|
discTyp: 'bluray',
|
|
zuordnung: null,
|
|
staffel: null,
|
|
ersteFolge: null,
|
|
discNr: null,
|
|
fingerprint: 'LABEL|33',
|
|
lesefehler: false,
|
|
hinweise: [],
|
|
begonnen: '2026-08-30T12:00:00Z',
|
|
preset: '',
|
|
zielOrdner: '',
|
|
...teil,
|
|
}
|
|
}
|
|
|
|
function umgebung(ablage: string, überschreiben: Partial<AblageUmgebung> = {}): AblageUmgebung {
|
|
return {
|
|
handbrake: () => 'C:\\hb\\HandBrakeCLI.exe',
|
|
ablage: () => ablage,
|
|
einstellungen: () => ({ audioSprachen: 'deu,eng', untertitelSprachen: 'deu' }),
|
|
jetzt: () => new Date('2026-08-30T13:00:00Z'),
|
|
spurenScannen: async () => null,
|
|
...überschreiben,
|
|
}
|
|
}
|
|
|
|
const KEIN_ABBRUCH = new AbortController().signal
|
|
|
|
describe('komprimierenUndAblegen', () => {
|
|
it('komprimiert in Filme/<Titel (Jahr)> mit NFO und meldet die Phasen', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const bibliothek: string[] = []
|
|
const phasen: string[] = []
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('movie', 'Akira', 1988) }),
|
|
umgebung(ablage, { bibliothekEintragen: (e) => bibliothek.push(e.titel) }),
|
|
(s) => phasen.push(s.phase),
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(ergebnis.komprimiert).toBe(true)
|
|
expect(ergebnis.zielOrdner).toBe(join(ablage, 'Filme', 'Akira (1988)'))
|
|
expect(existsSync(join(ergebnis.zielOrdner, 'movie.nfo'))).toBe(true)
|
|
expect(ergebnis.dateien).toEqual([join(ergebnis.zielOrdner, 'Akira (1988).mkv')])
|
|
expect(phasen).toContain('komprimiert')
|
|
expect(phasen.at(-1)).toBe('legt-ab')
|
|
expect(bibliothek).toEqual(['Akira'])
|
|
expect(ergebnis.text).toContain('komprimiert nach')
|
|
})
|
|
|
|
it('Kompression abgewählt: Roh-Dateien werden VERSCHOBEN, mit NFO — der Roh-Ordner bleibt leer zurück', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, discTyp: 'uhd', zuordnung: zuordnung('movie', 'Akira', 1988) }),
|
|
umgebung(ablage, { einstellungen: () => ({ transcodePresetUhd: 'keine' }) }),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(ergebnis.komprimiert).toBe(false)
|
|
expect(ergebnis.text).toContain('ohne Kompression')
|
|
expect(existsSync(join(ergebnis.zielOrdner, 'Akira (1988).mkv'))).toBe(true)
|
|
expect(existsSync(join(ergebnis.zielOrdner, 'movie.nfo'))).toBe(true)
|
|
expect(readdirSync(roh)).toEqual([])
|
|
expect(hb.aufrufe).toHaveLength(0)
|
|
})
|
|
|
|
it('ohne HandBrake: ebenfalls verschieben, nicht liegen lassen', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien }),
|
|
umgebung(ablage, { handbrake: () => '' }),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.komprimiert).toBe(false)
|
|
expect(readdirSync(roh)).toEqual([])
|
|
expect(ergebnis.zielOrdner).toBe(join(ablage, 'Filme', 'bluray-2026-08-30T12-00-00'))
|
|
})
|
|
|
|
it('Kompressions-Fehler: Roh bleibt stehen und wird GENANNT', async () => {
|
|
hb.ergebnis = 'error'
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const ergebnis = await komprimierenUndAblegen(auftrag({ rohOrdner: roh, dateien }), umgebung(ablage), () => {}, KEIN_ABBRUCH)
|
|
expect(ergebnis.status).toBe('fehler')
|
|
expect(ergebnis.text).toContain('bleibt stehen')
|
|
expect(ergebnis.text).toContain(roh)
|
|
expect(existsSync(dateien[0].pfad)).toBe(true)
|
|
})
|
|
|
|
it('Abbruch mittendrin: abgebrochen, Roh bleibt', async () => {
|
|
hb.ergebnis = 'cancelled'
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const ergebnis = await komprimierenUndAblegen(auftrag({ rohOrdner: roh, dateien }), umgebung(ablage), () => {}, KEIN_ABBRUCH)
|
|
expect(ergebnis.status).toBe('abgebrochen')
|
|
expect(existsSync(dateien[0].pfad)).toBe(true)
|
|
})
|
|
|
|
it('Sprachen je Datei erreichen HandBrake — fremder Ton schaltet die deutschen Untertitel vor', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'])
|
|
dateien[0].audioSprachen = ['jpn']
|
|
dateien[0].untertitelSprachen = ['eng', 'deu']
|
|
const ergebnis = await komprimierenUndAblegen(auftrag({ rohOrdner: roh, dateien }), umgebung(ablage), () => {}, KEIN_ABBRUCH)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(hb.aufrufe).toHaveLength(2)
|
|
// Datei 0: eigene Wahl — deu nach vorn, Vorwahl an.
|
|
expect(hb.aufrufe[0].audioSprachen).toEqual(['jpn'])
|
|
expect(hb.aufrufe[0].untertitelSprachen).toEqual(['deu', 'eng'])
|
|
expect(hb.aufrufe[0].untertitelStandard).toBe(true)
|
|
// Datei 1: nichts gewählt — die globale Einstellung, keine Vorwahl.
|
|
expect(hb.aufrufe[1].audioSprachen).toEqual(['deu', 'eng'])
|
|
expect(hb.aufrufe[1].untertitelSprachen).toEqual(['deu'])
|
|
expect(hb.aufrufe[1].untertitelStandard).toBe(false)
|
|
})
|
|
|
|
it('der Scan der Roh-Datei meldet fehlende Sprachen als Hinweis', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
dateien[0].audioSprachen = ['jpn']
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien }),
|
|
umgebung(ablage, {
|
|
spurenScannen: async () => ({ audio: [{ nr: 1, lang: 'deu', beschreibung: 'Deutsch', codec: 'dtshd', kanaele: 6 }], untertitel: [], dauerS: 3167 }),
|
|
}),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.hinweise.join(' ')).toContain('Tonspur jpn ist NICHT in der Roh-Datei')
|
|
})
|
|
|
|
it('Serie von Hand: Staffel 1, ab Folge 3 → S01E03 und S01E04, Warnung bei TMDb-Abweichung', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'], ['folge', 'folge'])
|
|
dateien[1].dauerS = 3249
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({
|
|
rohOrdner: roh,
|
|
dateien,
|
|
zuordnung: zuordnung('tv', 'Spartacus', 2010),
|
|
staffel: 1,
|
|
ersteFolge: 3,
|
|
}),
|
|
umgebung(ablage, {
|
|
// Folge 4 ist bei TMDb 20 min kürzer — das gibt eine Warnung, aber
|
|
// die Hand des Nutzers gilt.
|
|
episodenLaufzeiten: async () => [
|
|
[3, 3200],
|
|
[4, 2000],
|
|
],
|
|
}),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(ergebnis.zielOrdner).toBe(join(ablage, 'Serien', 'Spartacus', 'Season 01'))
|
|
expect(readdirSync(ergebnis.zielOrdner).filter((n) => n.endsWith('.mkv')).sort()).toEqual([
|
|
'Spartacus S01E03.mkv',
|
|
'Spartacus S01E04.mkv',
|
|
])
|
|
expect(ergebnis.dateien.map((d) => d.split(/[\\/]/).pop())).toEqual(['Spartacus S01E03.mkv', 'Spartacus S01E04.mkv'])
|
|
expect(ergebnis.hinweise.join(' ')).toContain('Folge 4: Datei 54 min, TMDb 33 min')
|
|
expect(existsSync(join(ergebnis.zielOrdner, 'tvshow.nfo'))).toBe(true)
|
|
})
|
|
|
|
it('Serie ohne Vorgabe: Laufzeit-Zuordnung wie bisher — nur bei eindeutigem Treffer', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'], ['folge', 'folge'])
|
|
dateien[0].dauerS = 3167
|
|
dateien[1].dauerS = 3249
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('tv', 'Spartacus', 2010), staffel: 2 }),
|
|
umgebung(ablage, {
|
|
episodenLaufzeiten: async () => [
|
|
[1, 3180],
|
|
[2, 3240],
|
|
],
|
|
}),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(readdirSync(ergebnis.zielOrdner).filter((n) => n.endsWith('.mkv')).sort()).toEqual([
|
|
'Spartacus S02E01.mkv',
|
|
'Spartacus S02E02.mkv',
|
|
])
|
|
})
|
|
|
|
it('Extras liegen im Unterordner extras\\ neben dem Hauptfilm', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t05.mkv'], ['hauptfilm', 'extra'])
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('movie', 'Akira', 1988) }),
|
|
umgebung(ablage),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(existsSync(join(ergebnis.zielOrdner, 'Akira (1988).mkv'))).toBe(true)
|
|
expect(existsSync(join(ergebnis.zielOrdner, 'extras', 'Akira (1988) - Extra 01.mkv'))).toBe(true)
|
|
})
|
|
|
|
it('Wiederholen: der vorgegebene Zielordner wird benutzt, kein zweiter angelegt', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const ziel = join(ablage, 'Filme', 'Akira (1988)')
|
|
mkdirSync(ziel, { recursive: true })
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('movie', 'Akira', 1988), zielOrdner: ziel, preset: 'Fast 1080p30' }),
|
|
umgebung(ablage),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.zielOrdner).toBe(ziel)
|
|
expect(ergebnis.preset).toBe('Fast 1080p30')
|
|
expect(readdirSync(join(ablage, 'Filme'))).toEqual(['Akira (1988)'])
|
|
})
|
|
|
|
it('der Fortschritt ist über beide Dateien gewichtet', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'])
|
|
dateien[0].groesseBytes = 3
|
|
dateien[1].groesseBytes = 1
|
|
const prozente: number[] = []
|
|
await komprimierenUndAblegen(auftrag({ rohOrdner: roh, dateien }), umgebung(ablage), (s) => prozente.push(s.prozent), KEIN_ABBRUCH)
|
|
// 50 % der ersten (3/4) = 37, 50 % der zweiten = 87.
|
|
expect(prozente).toContain(37)
|
|
expect(prozente).toContain(87)
|
|
})
|
|
})
|
|
|
|
describe('Wiederholen ersetzt die alte Fassung', () => {
|
|
it('Serie: die alten S01E03/E04 fallen erst nach dem Encode, dann greift die Umbenennung', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'], ['folge', 'folge'])
|
|
const ziel = join(ablage, 'Serien', 'Spartacus', 'Season 01')
|
|
mkdirSync(ziel, { recursive: true })
|
|
const alt3 = join(ziel, 'Spartacus S01E03.mkv')
|
|
const alt4 = join(ziel, 'Spartacus S01E04.mkv')
|
|
writeFileSync(alt3, 'alt')
|
|
writeFileSync(alt4, 'alt')
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({
|
|
rohOrdner: roh,
|
|
dateien,
|
|
zuordnung: zuordnung('tv', 'Spartacus', 2010),
|
|
staffel: 1,
|
|
ersteFolge: 3,
|
|
zielOrdner: ziel,
|
|
ersetzt: [alt3, alt4],
|
|
}),
|
|
umgebung(ablage),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(readdirSync(ziel).filter((n) => n.endsWith('.mkv')).sort()).toEqual(['Spartacus S01E03.mkv', 'Spartacus S01E04.mkv'])
|
|
expect(readFileSync(alt3, 'utf8')).toBe('encoded')
|
|
expect(ergebnis.hinweise.join(' ')).toContain('Alte Fassung ersetzt')
|
|
expect(ergebnis.hinweise.join(' ')).not.toContain('existiert schon')
|
|
})
|
|
|
|
it('scheitert der zweite Lauf, bleibt die alte Fassung stehen', async () => {
|
|
hb.ergebnis = 'error'
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const ziel = join(ablage, 'Filme', 'Akira (1988)')
|
|
mkdirSync(ziel, { recursive: true })
|
|
const alt = join(ziel, 'title_t00.mkv')
|
|
writeFileSync(alt, 'alt')
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('movie', 'Akira', 1988), zielOrdner: ziel, ersetzt: [alt] }),
|
|
umgebung(ablage),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fehler')
|
|
expect(readFileSync(alt, 'utf8')).toBe('alt')
|
|
})
|
|
})
|
|
|
|
// ── 5.7.0: Funde der Durchsicht vom 12.09.2026 ─────────────────────────
|
|
describe('komprimierenUndAblegen — Durchsicht 5.7.0', () => {
|
|
// F3: Disc 2 derselben Staffel landet im selben Season-Ordner wie Disc 1.
|
|
// Vorher zählte die Umbenennung ALLE MKVs des Ordners („4 Dateien, 2
|
|
// Zuordnungen") und tat nichts — der Serien-Merker lief ins Leere.
|
|
it('F3: zweite Disc in denselben Season-Ordner — nur ihre Folgen werden benannt', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'], ['folge', 'folge'])
|
|
const season = join(ablage, 'Serien', 'Spartacus', 'Season 01')
|
|
mkdirSync(season, { recursive: true })
|
|
writeFileSync(join(season, 'Spartacus S01E01.mkv'), 'disc1')
|
|
writeFileSync(join(season, 'Spartacus S01E02.mkv'), 'disc1')
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('tv', 'Spartacus', 2010), staffel: 1, ersteFolge: 3 }),
|
|
umgebung(ablage),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(readdirSync(season).filter((n) => n.endsWith('.mkv')).sort()).toEqual([
|
|
'Spartacus S01E01.mkv',
|
|
'Spartacus S01E02.mkv',
|
|
'Spartacus S01E03.mkv',
|
|
'Spartacus S01E04.mkv',
|
|
])
|
|
expect(readFileSync(join(season, 'Spartacus S01E01.mkv'), 'utf8')).toBe('disc1')
|
|
expect(ergebnis.folgenVergeben).toEqual([3, 4])
|
|
})
|
|
|
|
// F3: Liegt im Season-Ordner noch ein title_t00.mkv der ersten Disc
|
|
// (deren Umbenennung ausfiel), darf die zweite es NICHT überschreiben.
|
|
it('F3: gleichnamige Datei im Ziel wird nie überschrieben — „(2)" statt Verlust', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'], ['folge'])
|
|
const season = join(ablage, 'Serien', 'Spartacus', 'Season 01')
|
|
mkdirSync(season, { recursive: true })
|
|
writeFileSync(join(season, 'title_t00.mkv'), 'disc1-unbenannt')
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('tv', 'Spartacus', 2010), staffel: 1 }),
|
|
umgebung(ablage),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(readFileSync(join(season, 'title_t00.mkv'), 'utf8')).toBe('disc1-unbenannt')
|
|
expect(existsSync(join(season, 'title_t00 (2).mkv'))).toBe(true)
|
|
expect(ergebnis.dateien.map((d) => d.split(/[\\/]/).pop())).toEqual(['title_t00 (2).mkv'])
|
|
})
|
|
})
|
|
|
|
// ── 5.7.0: Spuren einzeln ──────────────────────────────────────────────────
|
|
describe('Spuren einzeln (5.7.0)', () => {
|
|
const jpnHd = { nr: 1, lang: 'jpn', sprache: 'Japanese', codec: 'DTS-HD MA', kanaele: '6', layout: 'Surround 5.1', beschreibung: '', abgeleitet: false }
|
|
const deuKommentar = { nr: 4, lang: 'deu', sprache: 'German', codec: 'DD', kanaele: '2', layout: 'Stereo', beschreibung: 'DD Stereo German (Kommentar)', abgeleitet: false }
|
|
const deuPgs = { nr: 5, lang: 'deu', sprache: 'German', codec: 'PGS', kanaele: '', layout: '', beschreibung: 'PGS German', abgeleitet: false }
|
|
const engPgs = { nr: 7, lang: 'eng', sprache: 'English', codec: 'PGS', kanaele: '', layout: '', beschreibung: 'PGS English', abgeleitet: false }
|
|
const scan = {
|
|
audio: [
|
|
{ nr: 1, lang: 'jpn', beschreibung: '', codec: 'dtshd', kanaele: 6 },
|
|
{ nr: 2, lang: 'deu', beschreibung: '', codec: 'ac3', kanaele: 6 },
|
|
{ nr: 3, lang: 'deu', beschreibung: '', codec: 'ac3', kanaele: 2 },
|
|
],
|
|
untertitel: [
|
|
{ nr: 1, lang: 'deu', beschreibung: '', codec: 'PGS', kanaele: 0 },
|
|
{ nr: 2, lang: 'eng', beschreibung: '', codec: 'PGS', kanaele: 0 },
|
|
],
|
|
dauerS: 3167,
|
|
}
|
|
|
|
it('gewählte Spuren erreichen HandBrake als Nummern — japanischer Ton stellt die deutschen Untertitel vor und wählt sie vor', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
dateien[0].audioSprachen = ['jpn', 'deu']
|
|
dateien[0].untertitelSprachen = ['eng', 'deu']
|
|
dateien[0].audioSpuren = [jpnHd, deuKommentar]
|
|
dateien[0].untertitelSpuren = [engPgs, deuPgs]
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien }),
|
|
umgebung(ablage, { spurenScannen: async () => scan }),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(ergebnis.status).toBe('fertig')
|
|
expect(hb.aufrufe[0].audioNr).toEqual([1, 3])
|
|
expect(hb.aufrufe[0].untertitelNr).toEqual([1, 2])
|
|
expect(hb.aufrufe[0].untertitelStandardNr).toBe(1)
|
|
expect(ergebnis.hinweise.join(' ')).not.toContain('NICHT in der Roh-Datei')
|
|
})
|
|
|
|
it('eine gewählte Spur fehlt in der Roh-Datei → Hinweis, der Rest läuft', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const fra = { ...jpnHd, nr: 9, lang: 'fra', sprache: 'French' }
|
|
dateien[0].audioSpuren = [fra, deuKommentar]
|
|
dateien[0].untertitelSpuren = []
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien }),
|
|
umgebung(ablage, { spurenScannen: async () => scan }),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(hb.aufrufe[0].audioNr).toEqual([3])
|
|
expect(hb.aufrufe[0].untertitelNr).toEqual([])
|
|
expect(ergebnis.hinweise.join(' ')).toContain('Tonspur French DTS-HD MA ist NICHT in der Roh-Datei')
|
|
})
|
|
|
|
it('Scan der Roh-Datei scheitert → Sprachen der gewählten Spuren als Liste (wie bisher)', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
dateien[0].audioSpuren = [jpnHd]
|
|
dateien[0].untertitelSpuren = [deuPgs]
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien }),
|
|
umgebung(ablage, { spurenScannen: async () => null }),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(hb.aufrufe[0].audioNr).toBeUndefined()
|
|
expect(hb.aufrufe[0].audioSprachen).toEqual(['jpn'])
|
|
expect(hb.aufrufe[0].untertitelSprachen).toEqual(['deu'])
|
|
expect(hb.aufrufe[0].untertitelStandard).toBe(true)
|
|
expect(ergebnis.hinweise.join(' ')).toContain('ließen sich nicht messen')
|
|
})
|
|
|
|
it('keine gewählte Tonspur findbar → Sprachlisten statt stummer Datei', async () => {
|
|
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
|
|
const fra = { ...jpnHd, nr: 9, lang: 'fra', sprache: 'French' }
|
|
dateien[0].audioSpuren = [fra]
|
|
dateien[0].untertitelSpuren = [deuPgs]
|
|
const ergebnis = await komprimierenUndAblegen(
|
|
auftrag({ rohOrdner: roh, dateien }),
|
|
umgebung(ablage, { spurenScannen: async () => scan }),
|
|
() => {},
|
|
KEIN_ABBRUCH,
|
|
)
|
|
expect(hb.aufrufe[0].audioNr).toBeUndefined()
|
|
expect(hb.aufrufe[0].audioSprachen).toEqual(['fra'])
|
|
expect(ergebnis.hinweise.join(' ')).toContain('keine der gewählten Tonspuren gefunden')
|
|
})
|
|
})
|