// Nachgebautes HandBrakeCLI für den Klick-Beweis (bau/klick-smoke.mjs). // Antwortet so, wie encoder.ts, scan.ts und handbrake.ts es am echten // HandBrake 1.11 gemessen haben: // --help → Encoder-Block „-e, --encoder" bis zur nächsten Option // --preset-list → Preset-Namen mit genau vier Leerzeichen Einrückung // --scan --json → „JSON Title Set:" + Objekt + „HandBrake has exited." // Encode → „Encoding: task 1 of 1, x %" in Stufen, Datei kopiert; // ab der zweiten Stufe mit der fps/ETA-Klammer, wie das // Binary sie formatiert (5.6.0, Restzeit). const fs = require('node:fs') const args = process.argv.slice(2) const out = (zeile) => process.stdout.write(zeile + '\n') const wert = (schalter) => { const i = args.indexOf(schalter) return i >= 0 ? args[i + 1] : undefined } if (args.includes('--help') || args.includes('-h')) { out('Usage: HandBrakeCLI [options] -i -o ') out('') out('Video Options -----------------------------------------------------------------') out('') out(' -e, --encoder Select video encoder:') out(' x264') out(' x265') out(' nvenc_h264') out(' -q, --quality Set video quality (e.g. 22.0)') out('') process.exit(0) } if (args.includes('--preset-list')) { out('General/') out(' Very Fast 1080p30') out(' Fast 1080p30') out(' HQ 1080p30 Surround') out('') process.exit(0) } if (args.includes('--scan')) { const eingabe = wert('--input') ?? wert('-i') ?? 'quelle' out('[12:00:00] hb_init: starting libhb thread') out('Scanning title 1 of 1, preview 1, 100.00 %') out('JSON Title Set: {') 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('{ "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", "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(']') out('}') out('HandBrake has exited.') process.exit(0) } const eingabe = wert('--input') ?? wert('-i') const ausgabe = wert('--output') ?? wert('-o') if (eingabe === undefined || ausgabe === undefined) { out('Missing input or output file name. Run HandBrakeCLI --help for syntax.') process.exit(1) } out('[12:00:00] hb_init: starting libhb thread') out('Scanning title 1 of 1, preview 10, 100.00 %') let schritt = 0 const stufen = [5, 30, 55, 80, 100] const tick = () => { const prozent = stufen[schritt].toFixed(2) if (schritt === 0) out(`Encoding: task 1 of 1, ${prozent} %`) else { // Formatstring aus dem Binary (1.11.2): „%.2f %% (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)" const rest = (stufen.length - 1 - schritt) * 300 out(`Encoding: task 1 of 1, ${prozent} % (48.00 fps, avg 47.50 fps, ETA 00h${String(Math.floor(rest / 60)).padStart(2, '0')}m${String(rest % 60).padStart(2, '0')}s)`) } schritt += 1 if (schritt < stufen.length) { setTimeout(tick, 500) return } fs.copyFileSync(eingabe, ausgabe) out('[12:00:05] libhb: work result = 0') out('') out('Encode done!') out('HandBrake has exited.') process.exit(0) } setTimeout(tick, 200)