feat(v5): jede Spur einzeln anhaken — Codec und Kanäle je Tonspur, Untertitel genauso
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fc2d16ca38
commit
e7e33e9021
@@ -124,16 +124,26 @@ await schritt('Titel-Lauf liefert drei Titel', async () => {
|
||||
await dialog().getByText('Titel 2', { exact: true }).waitFor()
|
||||
})
|
||||
|
||||
await schritt('Sprachen je Titel: jpn/deu Ton, deu/eng Untertitel als Chips', async () => {
|
||||
await dialog().getByRole('button', { name: /jpn/ }).first().waitFor()
|
||||
await dialog().getByRole('button', { name: /✓ deu/ }).first().waitFor()
|
||||
await schritt('5.7.0: Spuren je Titel einzeln — Codec und Kanäle, der DTS-Kern bleibt unsichtbar', async () => {
|
||||
await dialog().getByLabel('Japanese · DTS-HD MA · Surround 5.1').first().waitFor()
|
||||
await dialog().getByLabel('German · DD · Surround 5.1').first().waitFor()
|
||||
await dialog().getByLabel('German · DD · Stereo (Kommentar)').first().waitFor()
|
||||
await dialog().getByLabel('German · PGS (nur erzwungene)').first().waitFor()
|
||||
if ((await dialog().getByLabel('Japanese · DTS · Surround 5.1').count()) !== 0) throw new Error('der abgeleitete DTS-Kern wird angeboten')
|
||||
// Vorbelegt: je Wunschsprache (deu,eng) die erste Tonspur → nur German DD 5.1;
|
||||
// Untertitel: alle deutschen (auch „nur erzwungene"), englisch nicht.
|
||||
if (!(await dialog().getByLabel('German · DD · Surround 5.1').first().isChecked())) throw new Error('German 5.1 nicht vorbelegt')
|
||||
if (await dialog().getByLabel('German · DD · Stereo (Kommentar)').first().isChecked()) throw new Error('Kommentar-Spur vorbelegt')
|
||||
if (await dialog().getByLabel('Japanese · DTS-HD MA · Surround 5.1').first().isChecked()) throw new Error('Japanisch vorbelegt, obwohl Deutsch da ist')
|
||||
if (!(await dialog().getByLabel('German · PGS (nur erzwungene)').first().isChecked())) throw new Error('erzwungene Untertitel nicht vorbelegt')
|
||||
if (await dialog().getByLabel('English · PGS').first().isChecked()) throw new Error('englische Untertitel vorbelegt')
|
||||
})
|
||||
|
||||
await schritt('Titel 0 bekommt japanischen Ton (deu abwählen, jpn anwählen)', async () => {
|
||||
const chips = dialog().locator('button', { hasText: /^✓ deu$|^deu$/ })
|
||||
await chips.first().click()
|
||||
await dialog().getByRole('button', { name: /^jpn$/ }).first().click()
|
||||
await dialog().getByRole('button', { name: /^✓ jpn$/ }).first().waitFor()
|
||||
await schritt('Titel 0 bekommt japanischen Ton (German 5.1 abhaken, Japanese anhaken)', async () => {
|
||||
await dialog().getByLabel('German · DD · Surround 5.1').first().uncheck()
|
||||
await dialog().getByText('keine Tonspur gewählt — HandBrake nimmt die erste Spur').waitFor()
|
||||
await dialog().getByLabel('Japanese · DTS-HD MA · Surround 5.1').first().check()
|
||||
if ((await dialog().getByText('keine Tonspur gewählt').count()) !== 0) throw new Error('Warnung bleibt trotz japanischer Spur')
|
||||
})
|
||||
|
||||
await schritt('„Hauptinhalt + Extras" wählt beide langen Titel, der Schnipsel bleibt draußen', async () => {
|
||||
@@ -153,7 +163,9 @@ await schritt('5.6.0: Untertitel-Vorwahl erscheint NUR bei Extras, „anderer Fi
|
||||
await rollen.nth(1).selectOption('extra')
|
||||
await dialog().getByText('BEIM ABSPIELEN AN').waitFor()
|
||||
const vorwahl = dialog().locator('select').nth(2)
|
||||
await vorwahl.selectOption('deu')
|
||||
// 5.7.0: Rippys Regel nennt die Spur (japanischer Ton → deutsche
|
||||
// Untertitel), die Optionen sind Spuren — gewählt wird per Nummer (5).
|
||||
await vorwahl.selectOption('5')
|
||||
const knoepfe = await dialog().getByRole('button', { name: 'anderer Film …' }).count()
|
||||
if (knoepfe < 2) throw new Error(`nur ${knoepfe} „anderer Film"-Knöpfe`)
|
||||
await dialog().getByRole('button', { name: 'anderer Film …' }).first().click()
|
||||
@@ -279,22 +291,32 @@ await schritt('Werkzeuge haben Durchsuchen, Allgemein und Update sind eigene Kar
|
||||
})
|
||||
await bild('einstellungen')
|
||||
|
||||
await schritt('Roh-Dateien löschen → „gelöscht"', async () => {
|
||||
await seite.getByRole('button', { name: 'Roh-Dateien löschen' }).first().click()
|
||||
await schritt('Roh-Dateien löschen → Rückfrage → „Ja, löschen" → „gelöscht"', async () => {
|
||||
await seite.getByRole('button', { name: 'Roh-Dateien löschen …' }).first().click()
|
||||
await seite.getByRole('button', { name: 'Ja, löschen' }).first().click()
|
||||
await seite.getByText(/Roh: gelöscht/).first().waitFor()
|
||||
})
|
||||
|
||||
await schritt('5.6.0: Hell-Modus über Einstellungen → Allgemein, data-design wechselt', async () => {
|
||||
await seite.locator('#allgemein').locator('select').first().selectOption('hell')
|
||||
await seite.locator('#allgemein').getByLabel('Erscheinungsbild').selectOption('hell')
|
||||
await seite.locator('html[data-design="hell"]').waitFor()
|
||||
})
|
||||
await bild('hell-modus')
|
||||
|
||||
await schritt('zurück in den Kinosaal (dunkel)', async () => {
|
||||
await seite.locator('#allgemein').locator('select').first().selectOption('dunkel')
|
||||
await seite.locator('#allgemein').getByLabel('Erscheinungsbild').selectOption('dunkel')
|
||||
await seite.locator('html[data-design="dunkel"]').waitFor()
|
||||
})
|
||||
|
||||
await schritt('5.7.0: „Titel lesen — höchstens" steht auf 15 Minuten, 30 lässt sich wählen', async () => {
|
||||
const regler = seite.locator('#allgemein').getByLabel('Titel lesen — höchstens')
|
||||
if ((await regler.inputValue()) !== '15') throw new Error(`Vorgabe ist ${await regler.inputValue()}, nicht 15`)
|
||||
await regler.selectOption('30')
|
||||
// Der Wert geht zum Kern und kommt als Einstellung zurück — erst dann
|
||||
// zeigt das (kontrollierte) Feld 30.
|
||||
await regler.locator('option[value="30"]:checked').waitFor({ state: 'attached' })
|
||||
})
|
||||
|
||||
await schritt('Ereignisse nennen die Kompression', async () => {
|
||||
await seite.getByRole('button', { name: 'Übersicht' }).click()
|
||||
await seite.getByText(/Kompression „/).first().waitFor()
|
||||
@@ -357,6 +379,6 @@ await schritt('Fertig: der Knopf Los geht’s', async () => {
|
||||
})
|
||||
await bild('einrichtung-fertig')
|
||||
|
||||
console.log('KLICK: OK — Speicher, Rippen, Dialog, Sprachen, Folgen, Platz, Rip, Warteschlange, Kompression, Arbeitsordner, Ablage, Verlauf, Bilanz, Roh-Dateien, Einstellungen, Vorwahl, anderer Film, Vorgangs-Leiste, Restzeit, Hell-Modus, Erst-Einrichtung: alles geklickt.')
|
||||
console.log('KLICK: OK — Speicher, Rippen, Dialog, Spuren einzeln, Folgen, Platz, Rip, Warteschlange, Kompression, Arbeitsordner, Ablage, Verlauf, Bilanz, Roh-Dateien, Einstellungen, Vorwahl, anderer Film, Vorgangs-Leiste, Restzeit, Hell-Modus, Erst-Einrichtung: alles geklickt.')
|
||||
await app.close().catch(() => {})
|
||||
process.exit(0)
|
||||
|
||||
@@ -47,16 +47,21 @@ if (args.includes('--scan')) {
|
||||
out('"MainFeature": 0,')
|
||||
out('"TitleList": [')
|
||||
out('{')
|
||||
// 5.7.0: CodecName und ChannelCount wie das echte 1.11.2 (gemessen
|
||||
// 12.09.2026) — dieselben Spuren, die fake-makemkvcon meldet, ohne den
|
||||
// DTS-Kern (den nimmt MakeMKVs Regel nicht mit).
|
||||
out('"AudioList": [')
|
||||
out('{ "Description": "Japanese (AAC LC, 2.0 ch)", "Language": "Japanese", "LanguageCode": "jpn", "TrackNumber": 1 },')
|
||||
out('{ "Description": "Deutsch (AAC LC, 2.0 ch)", "Language": "Deutsch", "LanguageCode": "deu", "TrackNumber": 2 }')
|
||||
out('{ "ChannelCount": 6, "ChannelLayout": 1551, "CodecName": "dtshd", "Description": "Japanese (DTS-HD MA, 5.1 ch)", "Language": "Japanese", "LanguageCode": "jpn", "TrackNumber": 1 },')
|
||||
out('{ "ChannelCount": 6, "ChannelLayout": 1551, "CodecName": "ac3", "Description": "Deutsch (AC3, 5.1 ch)", "Language": "Deutsch", "LanguageCode": "deu", "TrackNumber": 2 },')
|
||||
out('{ "ChannelCount": 2, "ChannelLayout": 3, "CodecName": "ac3", "Description": "Deutsch (AC3, 2.0 ch)", "Language": "Deutsch", "LanguageCode": "deu", "TrackNumber": 3 }')
|
||||
out('],')
|
||||
out('"Duration": { "Hours": 0, "Minutes": 52, "Seconds": 47, "Ticks": 542070 },')
|
||||
out('"Index": 1,')
|
||||
out(`"Name": ${JSON.stringify(eingabe)},`)
|
||||
out('"SubtitleList": [')
|
||||
out('{ "Format": "bitmap", "Language": "Deutsch", "LanguageCode": "deu", "SourceName": "PGS", "TrackNumber": 1 },')
|
||||
out('{ "Format": "bitmap", "Language": "English", "LanguageCode": "eng", "SourceName": "PGS", "TrackNumber": 2 }')
|
||||
out('{ "Format": "bitmap", "Language": "Deutsch", "LanguageCode": "deu", "Source": 5, "SourceName": "PGS", "TrackNumber": 1 },')
|
||||
out('{ "Format": "bitmap", "Language": "Deutsch", "LanguageCode": "deu", "Source": 5, "SourceName": "PGS", "TrackNumber": 2 },')
|
||||
out('{ "Format": "bitmap", "Language": "English", "LanguageCode": "eng", "Source": 5, "SourceName": "PGS", "TrackNumber": 3 }')
|
||||
out(']')
|
||||
out('}')
|
||||
out(']')
|
||||
|
||||
@@ -31,18 +31,27 @@ if (befehl === 'info') {
|
||||
out(`TINFO:${t.nr},11,0,"${t.bytes}"`)
|
||||
out(`SINFO:${t.nr},0,1,6201,"Video"`)
|
||||
if (t.nr < 2) {
|
||||
out(`SINFO:${t.nr},1,1,6202,"Audio"`)
|
||||
out(`SINFO:${t.nr},1,3,0,"jpn"`)
|
||||
out(`SINFO:${t.nr},1,4,0,"Japanese"`)
|
||||
out(`SINFO:${t.nr},2,1,6202,"Audio"`)
|
||||
out(`SINFO:${t.nr},2,3,0,"deu"`)
|
||||
out(`SINFO:${t.nr},2,4,0,"German"`)
|
||||
out(`SINFO:${t.nr},3,1,6203,"Subtitles"`)
|
||||
out(`SINFO:${t.nr},3,3,0,"deu"`)
|
||||
out(`SINFO:${t.nr},3,4,0,"German"`)
|
||||
out(`SINFO:${t.nr},4,1,6203,"Subtitles"`)
|
||||
out(`SINFO:${t.nr},4,3,0,"eng"`)
|
||||
out(`SINFO:${t.nr},4,4,0,"English"`)
|
||||
// 5.7.0: jede Spur mit Codec (6), Kanälen (14), Anordnung (2), Flags
|
||||
// (22) und Beschreibung (30), wie an der Kizuna-Disc gemessen — samt
|
||||
// dem abgeleiteten DTS-Kern (Flag 0x800), den der Dialog NICHT zeigt,
|
||||
// und der „nur erzwungene"-Fassung eines Untertitels, die er zeigt.
|
||||
const spur = (nr, code, typ, lang, name, codec, kanaele, layout, flags, text) => {
|
||||
out(`SINFO:${t.nr},${nr},1,${code},"${typ}"`)
|
||||
if (layout) out(`SINFO:${t.nr},${nr},2,0,"${layout}"`)
|
||||
out(`SINFO:${t.nr},${nr},3,0,"${lang}"`)
|
||||
out(`SINFO:${t.nr},${nr},4,0,"${name}"`)
|
||||
out(`SINFO:${t.nr},${nr},6,0,"${codec}"`)
|
||||
if (kanaele) out(`SINFO:${t.nr},${nr},14,0,"${kanaele}"`)
|
||||
out(`SINFO:${t.nr},${nr},22,0,"${flags}"`)
|
||||
out(`SINFO:${t.nr},${nr},30,0,"${text}"`)
|
||||
}
|
||||
spur(1, 6202, 'Audio', 'jpn', 'Japanese', 'DTS-HD MA', '6', 'Surround 5.1', 1024, 'DTS-HD MA Surround 5.1 Japanese')
|
||||
spur(2, 6202, 'Audio', 'jpn', 'Japanese', 'DTS', '6', 'Surround 5.1', 2304, 'DTS Surround 5.1 Japanese')
|
||||
spur(3, 6202, 'Audio', 'deu', 'German', 'DD', '6', 'Surround 5.1', 0, 'DD Surround 5.1 German')
|
||||
spur(4, 6202, 'Audio', 'deu', 'German', 'DD', '2', 'Stereo', 0, 'DD Stereo German (Kommentar)')
|
||||
spur(5, 6203, 'Untertitel', 'deu', 'German', 'PGS', '', '', 0, 'PGS German')
|
||||
spur(6, 6203, 'Untertitel', 'deu', 'German', 'PGS', '', '', 6144, 'PGS German (nur erzwungene)')
|
||||
spur(7, 6203, 'Untertitel', 'eng', 'English', 'PGS', '', '', 0, 'PGS English')
|
||||
}
|
||||
}
|
||||
process.exit(0)
|
||||
|
||||
Reference in New Issue
Block a user